+2014-05-08 Ryosuke Niwa <rniwa@webkit.org>
+
+ DYEBench should use TodoMVC to test FlightJS for consistency
+ https://bugs.webkit.org/show_bug.cgi?id=132727
+
+ Reviewed by Andreas Kling.
+
+ Add a test suite for the FlightJS version of TodoMVC, and disable FlightJS-MailClient by default.
+
+ I initially intended to include a wider variety of demo apps in DYEBench
+ but that's not happening any time soon so let us use TodoMVC for all frameworks for now.
+
+ We can add more demo apps in v2.
+
+ * DoYouEvenBench/Full.html: Increment the version to 0.10.
+ * DoYouEvenBench/InteractiveRunner.html: Don't check disabled suites by default.
+ * DoYouEvenBench/resources/tests.js:
+ * DoYouEvenBench/resources/todomvc/dependency-examples: Added.
+ * DoYouEvenBench/resources/todomvc/dependency-examples/flight: Added.
+ * DoYouEvenBench/resources/todomvc/dependency-examples/flight/.gitignore: Added.
+ * DoYouEvenBench/resources/todomvc/dependency-examples/flight/.jshintrc: Added.
+ * DoYouEvenBench/resources/todomvc/dependency-examples/flight/app: Added.
+ * DoYouEvenBench/resources/todomvc/dependency-examples/flight/app/js: Added.
+ * DoYouEvenBench/resources/todomvc/dependency-examples/flight/app/js/app.js: Added.
+ * DoYouEvenBench/resources/todomvc/dependency-examples/flight/app/js/data: Added.
+ * DoYouEvenBench/resources/todomvc/dependency-examples/flight/app/js/data/stats.js: Added.
+ * DoYouEvenBench/resources/todomvc/dependency-examples/flight/app/js/data/todos.js: Added.
+ * DoYouEvenBench/resources/todomvc/dependency-examples/flight/app/js/main.js: Added.
+ * DoYouEvenBench/resources/todomvc/dependency-examples/flight/app/js/store.js: Added.
+ * DoYouEvenBench/resources/todomvc/dependency-examples/flight/app/js/ui: Added.
+ * DoYouEvenBench/resources/todomvc/dependency-examples/flight/app/js/ui/main_selector.js: Added.
+ * DoYouEvenBench/resources/todomvc/dependency-examples/flight/app/js/ui/new_item.js: Added.
+ * DoYouEvenBench/resources/todomvc/dependency-examples/flight/app/js/ui/stats.js: Added.
+ * DoYouEvenBench/resources/todomvc/dependency-examples/flight/app/js/ui/todo_list.js: Added.
+ * DoYouEvenBench/resources/todomvc/dependency-examples/flight/app/js/ui/toggle_all.js: Added.
+ * DoYouEvenBench/resources/todomvc/dependency-examples/flight/app/js/ui/with_filters.js: Added.
+ * DoYouEvenBench/resources/todomvc/dependency-examples/flight/app/js/utils.js: Added.
+ * DoYouEvenBench/resources/todomvc/dependency-examples/flight/app/templates: Added.
+ * DoYouEvenBench/resources/todomvc/dependency-examples/flight/app/templates/stats.html: Added.
+ * DoYouEvenBench/resources/todomvc/dependency-examples/flight/app/templates/todo.html: Added.
+ * DoYouEvenBench/resources/todomvc/dependency-examples/flight/bower.json: Added.
+ * DoYouEvenBench/resources/todomvc/dependency-examples/flight/bower_components: Added.
+ * DoYouEvenBench/resources/todomvc/dependency-examples/flight/bower_components/depot: Added.
+ * DoYouEvenBench/resources/todomvc/dependency-examples/flight/bower_components/depot/depot.js: Added.
+ * DoYouEvenBench/resources/todomvc/dependency-examples/flight/bower_components/es5-shim: Added.
+ * DoYouEvenBench/resources/todomvc/dependency-examples/flight/bower_components/es5-shim/es5-sham.js: Added.
+ * DoYouEvenBench/resources/todomvc/dependency-examples/flight/bower_components/es5-shim/es5-shim.js: Added.
+ * DoYouEvenBench/resources/todomvc/dependency-examples/flight/bower_components/flight: Added.
+ * DoYouEvenBench/resources/todomvc/dependency-examples/flight/bower_components/flight/lib: Added.
+ * DoYouEvenBench/resources/todomvc/dependency-examples/flight/bower_components/flight/lib/advice.js: Added.
+ * DoYouEvenBench/resources/todomvc/dependency-examples/flight/bower_components/flight/lib/base.js: Added.
+ * DoYouEvenBench/resources/todomvc/dependency-examples/flight/bower_components/flight/lib/component.js: Added.
+ * DoYouEvenBench/resources/todomvc/dependency-examples/flight/bower_components/flight/lib/compose.js: Added.
+ * DoYouEvenBench/resources/todomvc/dependency-examples/flight/bower_components/flight/lib/debug.js: Added.
+ * DoYouEvenBench/resources/todomvc/dependency-examples/flight/bower_components/flight/lib/index.js: Added.
+ * DoYouEvenBench/resources/todomvc/dependency-examples/flight/bower_components/flight/lib/logger.js: Added.
+ * DoYouEvenBench/resources/todomvc/dependency-examples/flight/bower_components/flight/lib/registry.js: Added.
+ * DoYouEvenBench/resources/todomvc/dependency-examples/flight/bower_components/flight/lib/utils.js: Added.
+ * DoYouEvenBench/resources/todomvc/dependency-examples/flight/bower_components/jquery: Added.
+ * DoYouEvenBench/resources/todomvc/dependency-examples/flight/bower_components/jquery/jquery.js: Added.
+ * DoYouEvenBench/resources/todomvc/dependency-examples/flight/bower_components/requirejs: Added.
+ * DoYouEvenBench/resources/todomvc/dependency-examples/flight/bower_components/requirejs-text: Added.
+ * DoYouEvenBench/resources/todomvc/dependency-examples/flight/bower_components/requirejs-text/text.js: Added.
+ * DoYouEvenBench/resources/todomvc/dependency-examples/flight/bower_components/requirejs/require.js: Added.
+ * DoYouEvenBench/resources/todomvc/dependency-examples/flight/bower_components/todomvc-common: Added.
+ * DoYouEvenBench/resources/todomvc/dependency-examples/flight/bower_components/todomvc-common/base.css: Added.
+ * DoYouEvenBench/resources/todomvc/dependency-examples/flight/bower_components/todomvc-common/base.js: Added.
+ * DoYouEvenBench/resources/todomvc/dependency-examples/flight/bower_components/todomvc-common/bg.png: Added.
+ * DoYouEvenBench/resources/todomvc/dependency-examples/flight/index.html: Added.
+ * DoYouEvenBench/resources/todomvc/dependency-examples/flight/karma.conf.js: Added.
+ * DoYouEvenBench/resources/todomvc/dependency-examples/flight/package.json: Added.
+ * DoYouEvenBench/resources/todomvc/dependency-examples/flight/readme.md: Added.
+ * DoYouEvenBench/resources/todomvc/dependency-examples/flight/test: Added.
+ * DoYouEvenBench/resources/todomvc/dependency-examples/flight/test/.jshintrc: Added.
+ * DoYouEvenBench/resources/todomvc/dependency-examples/flight/test/fixture: Added.
+ * DoYouEvenBench/resources/todomvc/dependency-examples/flight/test/fixture/footer.html: Added.
+ * DoYouEvenBench/resources/todomvc/dependency-examples/flight/test/fixture/new_todo.html: Added.
+ * DoYouEvenBench/resources/todomvc/dependency-examples/flight/test/fixture/toggle_all.html: Added.
+ * DoYouEvenBench/resources/todomvc/dependency-examples/flight/test/mock: Added.
+ * DoYouEvenBench/resources/todomvc/dependency-examples/flight/test/mock/datastore.js: Added.
+ * DoYouEvenBench/resources/todomvc/dependency-examples/flight/test/spec: Added.
+ * DoYouEvenBench/resources/todomvc/dependency-examples/flight/test/spec/data: Added.
+ * DoYouEvenBench/resources/todomvc/dependency-examples/flight/test/spec/data/stats_spec.js: Added.
+ * DoYouEvenBench/resources/todomvc/dependency-examples/flight/test/spec/data/todos_spec.js: Added.
+ * DoYouEvenBench/resources/todomvc/dependency-examples/flight/test/spec/ui: Added.
+ * DoYouEvenBench/resources/todomvc/dependency-examples/flight/test/spec/ui/new_item_spec.js: Added.
+ * DoYouEvenBench/resources/todomvc/dependency-examples/flight/test/spec/ui/stats_spec.js: Added.
+ * DoYouEvenBench/resources/todomvc/dependency-examples/flight/test/spec/ui/toggle_all_spec.js: Added.
+ * DoYouEvenBench/resources/todomvc/dependency-examples/flight/test/test-main.js: Added.
+
2014-05-07 Manuel Rego Casasnovas <rego@igalia.com>
[CSS Grid Layout] Remove runtime feature
<!DOCTYPE html>
<html>
<head>
-<title>DoYouEvenBench v0.9</title>
+<title>DoYouEvenBench v0.10</title>
<style type="text/css">
caption { margin: 0; padding: 0; font-family: sans-serif; font-size: 1em; font-weight: bold; white-space: nowrap; }
#progressContainer { padding: 605px 0 10px 0; width: 800px; }
var checkbox = document.createElement('input');
checkbox.id = suite.name;
checkbox.type = 'checkbox';
- checkbox.checked = true;
+ checkbox.checked = !suite.disabled;
checkbox.onchange = (function (suite, checkbox) { return function () { suite.disabled = !checkbox.checked; } })(suite, checkbox);
checkbox.onchange();
checkboxes.push(checkbox);
]
});
+Suites.push({
+ name: 'FlightJS-TodoMVC',
+ url: 'todomvc/dependency-examples/flight/index.html',
+ prepare: function (runner, contentWindow, contentDocument) {
+ return runner.waitForElement('#appIsReady').then(function () {
+ var newTodo = contentDocument.querySelector('#new-todo');
+ newTodo.focus();
+ return newTodo;
+ });;
+ },
+ tests: [
+ new BenchmarkTestStep('Adding' + numberOfItemsToAdd + 'Items', function (newTodo, contentWindow, contentDocument) {
+ var todomvc = contentWindow.todomvc;
+ for (var i = 0; i < numberOfItemsToAdd; i++) {
+ newTodo.value = 'Something to do ' + i;
+
+ var keydownEvent = document.createEvent('Event');
+ keydownEvent.initEvent('keydown', true, true);
+ keydownEvent.which = 13; // VK_ENTER
+ newTodo.dispatchEvent(keydownEvent);
+ }
+ }),
+ new BenchmarkTestStep('CompletingAllItems', function (newTodo, contentWindow, contentDocument) {
+ var checkboxes = contentDocument.querySelectorAll('.toggle');
+ for (var i = 0; i < checkboxes.length; i++)
+ checkboxes[i].click();
+ }),
+ new BenchmarkTestStep('DeletingAllItems', function (newTodo, contentWindow, contentDocument) {
+ var deleteButtons = contentDocument.querySelectorAll('.destroy');
+ for (var i = 0; i < deleteButtons.length; i++)
+ deleteButtons[i].click();
+ }),
+ ]
+});
+
var actionCount = 50;
Suites.push({
+ disabled: true,
name: 'FlightJS-MailClient',
url: 'flightjs-example-app/index.html',
prepare: function (runner, contentWindow, contentDocument) {
--- /dev/null
+node_modules/
--- /dev/null
+{
+ "node": true,
+ "browser": true,
+ "esnext": true,
+ "bitwise": true,
+ "camelcase": true,
+ "curly": true,
+ "eqeqeq": true,
+ "immed": true,
+ "indent": 4,
+ "latedef": true,
+ "newcap": true,
+ "noarg": true,
+ "quotmark": "single",
+ "regexp": true,
+ "undef": true,
+ "unused": true,
+ "strict": true,
+ "trailing": true,
+ "smarttabs": true,
+ "white": true,
+ "validthis": true
+}
--- /dev/null
+/*global define */
+'use strict';
+
+define([
+ './data/todos',
+ './data/stats',
+ './ui/new_item',
+ './ui/todo_list',
+ './ui/stats',
+ './ui/main_selector',
+ './ui/toggle_all'
+], function (TodosData, StatsData, NewItemUI, TodoListUI, StatsUI, MainSelectorUI, ToggleAllUI) {
+ var initialize = function () {
+ StatsData.attachTo(document);
+ TodosData.attachTo(document);
+ NewItemUI.attachTo('#new-todo');
+ MainSelectorUI.attachTo('#main');
+ StatsUI.attachTo('#footer');
+ ToggleAllUI.attachTo('#toggle-all');
+ TodoListUI.attachTo('#todo-list');
+
+ var dummyNodeToNotifyAppIsReady = document.createElement('div');
+ dummyNodeToNotifyAppIsReady.id = 'appIsReady';
+ document.body.appendChild(dummyNodeToNotifyAppIsReady);
+ };
+
+ return {
+ initialize: initialize
+ };
+});
--- /dev/null
+/*global define */
+'use strict';
+
+define([
+ 'flight/lib/component',
+ '../store'
+], function (defineComponent, dataStore) {
+ function stats() {
+ this.defaultAttrs({
+ dataStore: dataStore
+ });
+
+ this.recount = function () {
+ var todos = this.attr.dataStore.all();
+ var all = todos.length;
+ var remaining = todos.reduce(function (memo, each) {
+ return memo += each.completed ? 0 : 1;
+ }, 0);
+
+ this.trigger('dataStatsCounted', {
+ all: all,
+ remaining: remaining,
+ completed: all - remaining,
+ filter: localStorage.getItem('filter') || ''
+ });
+ };
+
+ this.after('initialize', function () {
+ this.on(document, 'dataTodosLoaded', this.recount);
+ this.on(document, 'dataTodoAdded', this.recount);
+ this.on(document, 'dataTodoRemoved', this.recount);
+ this.on(document, 'dataTodoToggled', this.recount);
+ this.on(document, 'dataClearedCompleted', this.recount);
+ this.on(document, 'dataTodoToggledAll', this.recount);
+ });
+ }
+
+ return defineComponent(stats);
+});
--- /dev/null
+/*global define */
+'use strict';
+
+define([
+ 'flight/lib/component',
+ '../store'
+], function (defineComponent, dataStore) {
+ function todos() {
+ var filter;
+ this.defaultAttrs({
+ dataStore: dataStore
+ });
+
+ this.add = function (e, data) {
+ var todo = this.attr.dataStore.save({
+ title: data.title,
+ completed: false
+ });
+
+ this.trigger('dataTodoAdded', { todo: todo, filter: filter });
+ };
+
+ this.remove = function (e, data) {
+ var todo = this.attr.dataStore.destroy(data.id);
+
+ this.trigger('dataTodoRemoved', todo);
+ };
+
+ this.load = function () {
+ var todos;
+
+ filter = localStorage.getItem('filter');
+ todos = this.find();
+ this.trigger('dataTodosLoaded', { todos: todos });
+ };
+
+ this.update = function (e, data) {
+ this.attr.dataStore.save(data);
+ };
+
+ this.toggleCompleted = function (e, data) {
+ var eventType;
+ var todo = this.attr.dataStore.get(data.id);
+
+ todo.completed = !todo.completed;
+ this.attr.dataStore.save(todo);
+
+ eventType = filter ? 'dataTodoRemoved' : 'dataTodoToggled';
+
+ this.trigger(eventType, todo);
+ };
+
+ this.toggleAllCompleted = function (e, data) {
+ this.attr.dataStore.updateAll({ completed: data.completed });
+ this.trigger('dataTodoToggledAll', { todos: this.find(filter) });
+ };
+
+ this.filter = function (e, data) {
+ var todos;
+
+ localStorage.setItem('filter', data.filter);
+ filter = data.filter;
+ todos = this.find();
+
+ this.trigger('dataTodosFiltered', { todos: todos });
+ };
+
+ this.find = function () {
+ var todos;
+
+ if (filter) {
+ todos = this.attr.dataStore.find(function (each) {
+ return (typeof each[filter] !== 'undefined') ? each.completed : !each.completed;
+ });
+ } else {
+ todos = this.attr.dataStore.all();
+ }
+
+ return todos;
+ };
+
+ this.clearCompleted = function () {
+ this.attr.dataStore.destroyAll({ completed: true });
+
+ this.trigger('uiFilterRequested', { filter: filter });
+ this.trigger('dataClearedCompleted');
+ };
+
+ this.after('initialize', function () {
+ this.on(document, 'uiAddRequested', this.add);
+ this.on(document, 'uiUpdateRequested', this.update);
+ this.on(document, 'uiRemoveRequested', this.remove);
+ this.on(document, 'uiLoadRequested', this.load);
+ this.on(document, 'uiToggleRequested', this.toggleCompleted);
+ this.on(document, 'uiToggleAllRequested', this.toggleAllCompleted);
+ this.on(document, 'uiClearRequested', this.clearCompleted);
+ this.on(document, 'uiFilterRequested', this.filter);
+ });
+ }
+
+ return defineComponent(todos);
+});
--- /dev/null
+'use strict';
+
+require.config({
+ baseUrl: './',
+ paths: {
+ jquery: 'bower_components/jquery/jquery',
+ es5shim: 'bower_components/es5-shim/es5-shim',
+ es5sham: 'bower_components/es5-shim/es5-sham',
+ text: 'bower_components/requirejs-text/text',
+ flight: 'bower_components/flight',
+ depot: 'bower_components/depot/depot'
+ },
+ shim: {
+ 'app/js/app': {
+ deps: ['jquery', 'es5shim', 'es5sham']
+ }
+ }
+});
+
+require(['app/js/app'], function (App) {
+ App.initialize();
+});
--- /dev/null
+/*global define */
+
+'use strict';
+
+define([
+ 'depot'
+], function (depot) {
+ return depot('todos', { idAttribute: 'id' });
+});
--- /dev/null
+/*global define */
+'use strict';
+
+define([
+ 'flight/lib/component'
+], function (defineComponent) {
+ function mainSelector() {
+ this.toggle = function (e, data) {
+ var toggle = data.all > 0;
+ this.$node.toggle(toggle);
+ };
+
+ this.after('initialize', function () {
+ this.$node.hide();
+ this.on(document, 'dataStatsCounted', this.toggle);
+ });
+ }
+
+ return defineComponent(mainSelector);
+});
--- /dev/null
+/*global define */
+'use strict';
+
+define([
+ 'flight/lib/component'
+], function (defineComponent) {
+ function newItem() {
+ var ENTER_KEY = 13;
+
+ this.createOnEnter = function (e) {
+ if (e.which !== ENTER_KEY ||
+ !this.$node.val().trim()) {
+ return;
+ }
+
+ this.trigger('uiAddRequested', {
+ title: this.$node.val().trim()
+ });
+
+ this.$node.val('');
+ };
+
+ this.after('initialize', function () {
+ this.on('keydown', this.createOnEnter);
+ });
+ }
+
+ return defineComponent(newItem);
+});
--- /dev/null
+/*global define */
+'use strict';
+
+define([
+ 'flight/lib/component',
+ './with_filters',
+ 'text!app/templates/stats.html',
+ '../utils'
+], function (defineComponent, withFilters, statsTmpl, utils) {
+ function stats() {
+ var template = utils.tmpl(statsTmpl);
+
+ this.defaultAttrs({
+ clearCompletedSelector: '#clear-completed'
+ });
+
+ this.render = function (e, data) {
+ var toggle = data.all > 0;
+
+ this.$node.html(template(data));
+ this.$node.toggle(toggle);
+ this.markSelected(data.filter);
+ };
+
+ this.clearCompleted = function () {
+ this.trigger('uiClearRequested');
+ };
+
+ this.after('initialize', function () {
+ this.$node.hide();
+ this.on(document, 'dataStatsCounted', this.render);
+ this.on('click', { 'clearCompletedSelector': this.clearCompleted });
+ });
+ }
+
+ return defineComponent(stats, withFilters);
+});
--- /dev/null
+/*global define, $ */
+'use strict';
+
+define([
+ 'flight/lib/component',
+ 'text!app/templates/todo.html',
+ '../utils'
+], function (defineComponent, todoTmpl, utils) {
+ function todoList() {
+ var ENTER_KEY = 13;
+ var template = utils.tmpl(todoTmpl);
+
+ this.defaultAttrs({
+ destroySelector: 'button.destroy',
+ toggleSelector: 'input.toggle',
+ labelSelector: 'label',
+ editSelector: '.edit'
+ });
+
+ this.renderAll = function (e, data) {
+ this.$node.html('');
+ data.todos.forEach(function (each) {
+ this.render(e, { todo: each });
+ }, this);
+ };
+
+ this.render = function (e, data) {
+ if (e.type === 'dataTodoAdded' && data.filter === 'completed') {
+ return;
+ }
+
+ this.$node.append(template(data.todo));
+ };
+
+ this.edit = function (e, data) {
+ var $todoEl = $(data.el).parents('li');
+
+ $todoEl.addClass('editing');
+ this.select('editSelector').focus();
+ };
+
+ this.requestUpdate = function (e) {
+ var $inputEl = $(e.currentTarget);
+ var $todoEl = $inputEl.parents('li');
+ var value = $inputEl.val().trim();
+ var id = $todoEl.attr('id');
+
+ if (!$todoEl.hasClass('editing')) {
+ return;
+ }
+
+ $todoEl.removeClass('editing');
+
+ if (value) {
+ $todoEl.find('label').html(value);
+ this.trigger('uiUpdateRequested', { id: id, title: value });
+ } else {
+ this.trigger('uiRemoveRequested', { id: id });
+ }
+ };
+
+ this.requestUpdateOnEnter = function (e, data) {
+ if (e.which === ENTER_KEY) {
+ this.requestUpdate(e, data);
+ }
+ };
+
+ this.requestRemove = function (e, data) {
+ var id = $(data.el).attr('id').split('_')[1];
+ this.trigger('uiRemoveRequested', { id: id });
+ };
+
+ this.remove = function (e, data) {
+ var $todoEl = this.$node.find('#' + data.id);
+ $todoEl.remove();
+ };
+
+ this.toggle = function (e, data) {
+ var $todoEl = $(data.el).parents('li');
+
+ $todoEl.toggleClass('completed');
+ this.trigger('uiToggleRequested', { id: $todoEl.attr('id') });
+ };
+
+ this.after('initialize', function () {
+ this.on(document, 'dataTodoAdded', this.render);
+ this.on(document, 'dataTodosLoaded', this.renderAll);
+ this.on(document, 'dataTodosFiltered', this.renderAll);
+ this.on(document, 'dataTodoToggledAll', this.renderAll);
+ this.on(document, 'dataTodoRemoved', this.remove);
+
+ this.on('click', { 'destroySelector': this.requestRemove });
+ this.on('click', { 'toggleSelector': this.toggle });
+ this.on('dblclick', { 'labelSelector': this.edit });
+
+ this.$node.on('blur', '.edit', this.requestUpdate.bind(this));
+ this.$node.on('keydown', '.edit', this.requestUpdateOnEnter.bind(this));
+
+ // these don't work
+ // this.on(this.attr.editSelector, 'blur', this.requestUpdate);
+ // this.on('blur', { 'editSelector': this.requestUpdate });
+
+ this.trigger('uiLoadRequested');
+ });
+ }
+
+ return defineComponent(todoList);
+});
--- /dev/null
+/*global define */
+'use strict';
+
+define([
+ 'flight/lib/component'
+], function (defineComponent) {
+ function toggleAll() {
+ this.toggleAllComplete = function () {
+ this.trigger('uiToggleAllRequested', {
+ completed: this.$node.is(':checked')
+ });
+ };
+
+ this.toggleCheckbox = function (e, data) {
+ this.node.checked = !data.remaining;
+ };
+
+ this.after('initialize', function () {
+ this.on('click', this.toggleAllComplete);
+ this.on(document, 'dataStatsCounted', this.toggleCheckbox);
+ });
+ }
+
+ return defineComponent(toggleAll);
+});
--- /dev/null
+/*global define, $ */
+'use strict';
+
+define(function () {
+ return function withFilters() {
+ this.defaultAttrs({
+ filterSelector: '#filters a'
+ });
+
+ this.chooseFilter = function (e, data) {
+ var filter = data.el.hash.slice(2);
+
+ this.select('filterSelector').removeClass('selected');
+ $(data.el).addClass('selected');
+ this.trigger('uiFilterRequested', { filter: filter });
+ };
+
+ this.markSelected = function (filter) {
+ this.$node.find('[href="#/' + filter + '"]').addClass('selected');
+ };
+
+ this.after('initialize', function () {
+ this.on('click', { filterSelector: this.chooseFilter });
+ });
+ };
+});
--- /dev/null
+/*global define */
+'use strict';
+
+// tmpl function scooped from underscore.
+// http://documentcloud.github.com/underscore/#template
+define(function () {
+ var _ = {};
+
+ // List of HTML entities for escaping.
+ var entityMap = {
+ escape: {
+ '&': '&',
+ '<': '<',
+ '>': '>',
+ '"': '"',
+ /*jshint quotmark:false */
+ "'": ''',
+ '/': '/'
+ }
+ };
+
+ var escapeKeys = '&<>"\'/';
+ var unescapeKeys = '&|<|>|"|'|/';
+
+ // Regexes containing the keys and values listed immediately above.
+ var entityRegexes = {
+ escape: new RegExp('[' + escapeKeys + ']', 'g'),
+ unescape: new RegExp('(' + unescapeKeys + ')', 'g')
+ };
+
+ // Functions for escaping and unescaping strings to/from HTML interpolation.
+ ['escape', 'unescape'].forEach(function (method) {
+ _[method] = function (string) {
+ if (string === null || string === undefined) {
+ return '';
+ }
+
+ return ('' + string).replace(entityRegexes[method], function (match) {
+ return entityMap[method][match];
+ });
+ };
+ });
+
+ var settings = {
+ evaluate: /<%([\s\S]+?)%>/g,
+ interpolate: /<%=([\s\S]+?)%>/g,
+ escape: /<%-([\s\S]+?)%>/g
+ };
+
+ var noMatch = /(.)^/;
+ var escapes = {
+ /*jshint quotmark:false */
+ "'": "'",
+ '\\': '\\',
+ '\r': 'r',
+ '\n': 'n',
+ '\t': 't',
+ '\u2028': 'u2028',
+ '\u2029': 'u2029'
+ };
+
+ var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g;
+
+ // JavaScript micro-templating, similar to John Resig's implementation.
+ // Underscore templating handles arbitrary delimiters, preserves whitespace,
+ // and correctly escapes quotes within interpolated code.
+ var template = function (text, data) {
+ var render;
+
+ // Combine delimiters into one regular expression via alternation.
+ var matcher = new RegExp([
+ (settings.escape || noMatch).source,
+ (settings.interpolate || noMatch).source,
+ (settings.evaluate || noMatch).source
+ ].join('|') + '|$', 'g');
+
+ // Compile the template source, escaping string literals appropriately.
+ var index = 0;
+ var source = "__p+='";
+ text.replace(matcher, function (match, escape, interpolate, evaluate, offset) {
+ source += text.slice(index, offset)
+ .replace(escaper, function (match) {
+ return '\\' + escapes[match];
+ });
+
+ if (escape) {
+ source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'";
+ }
+ if (interpolate) {
+ source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'";
+ }
+ if (evaluate) {
+ source += "';\n" + evaluate + "\n__p+='";
+ }
+ index = offset + match.length;
+ return match;
+ });
+ source += "';\n";
+
+ // If a variable is not specified, place data values in local scope.
+ if (!settings.variable) {
+ source = 'with(obj||{}){\n' + source + '}\n';
+ }
+
+ source = "var __t,__p='',__j=Array.prototype.join," +
+ "print=function(){__p+=__j.call(arguments,'');};\n" +
+ source + "return __p;\n";
+
+ try {
+ /*jshint evil:true */
+ render = new Function(settings.variable || 'obj', '_', source);
+ } catch (err) {
+ err.source = source;
+ throw err;
+ }
+
+ if (data) {
+ return render(data, _);
+ }
+
+ var template = function (data) {
+ return render.call(this, data, _);
+ };
+
+ // Provide the compiled function source as a convenience for precompilation.
+ template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}';
+
+ return template;
+ };
+
+ return {
+ tmpl: template
+ };
+});
--- /dev/null
+<span id="todo-count">
+ <strong><%= remaining %></strong> <%= remaining == 1 ? 'item' : 'items' %> left
+</span>
+<ul id="filters">
+ <li>
+ <a href="#/">All</a>
+ </li>
+ <li>
+ <a href="#/active">Active</a>
+ </li>
+ <li>
+ <a href="#/completed">Completed</a>
+ </li>
+</ul>
+<% if (completed) { %>
+<button id="clear-completed">Clear completed (<%= completed %>)</button>
+<% } %>
--- /dev/null
+<li id="<%= id %>" class="<%= completed ? 'completed' : '' %>">
+ <div class="view">
+ <input class="toggle" type="checkbox" <%= completed ? 'checked' : '' %>>
+ <label><%- title %></label>
+ <button id="destroy_<%= id %>" class="destroy"></button>
+ </div>
+ <input class="edit" value="<%- title %>">
+</li>
--- /dev/null
+{
+ "name": "flight-todomvc",
+ "version": "0.0.0",
+ "dependencies": {
+ "depot": "~0.1.4",
+ "flight": "~1.1.0",
+ "jquery": "1.8.3",
+ "requirejs": "~2.1.5",
+ "todomvc-common": "~0.1.4",
+ "requirejs-text": "~2.0.10"
+ },
+ "devDependencies": {
+ "jasmine-flight": "~2.1.0",
+ "jasmine-jquery": "~1.5.8"
+ }
+}
--- /dev/null
+// depot.js v0.1.6
+
+// (c) 2013 Michal Kuklis
+// Licensed under The MIT License
+// http://opensource.org/licenses/MIT
+
+(function (name, root, factory) {
+ if (typeof exports == 'object') {
+ module.exports = factory();
+ } else if (typeof define == 'function' && define.amd) {
+ define(factory);
+ } else {
+ root[name] = factory();
+ }
+}("depot", this, function () {
+
+ "use strict";
+
+ // depot api
+
+ var api = {
+
+ save: function (record) {
+ var id;
+
+ if (!record[this.idAttribute]) {
+ record[this.idAttribute] = guid();
+ }
+
+ id = record[this.idAttribute] + '';
+
+ if (this.ids.indexOf(id) < 0) {
+ this.ids.push(id);
+ this.storageAdaptor.setItem(this.name, this.ids.join(","));
+ }
+
+ this.storageAdaptor.setItem(getKey(this.name, id), JSON.stringify(record));
+
+ return record;
+ },
+
+ update: function (id, data) {
+ if (typeof data == 'undefined') {
+ data = id;
+ id = data[this.idAttribute];
+ }
+
+ var record = this.get(id);
+
+ if (record) {
+ record = extend(record, data);
+ this.save(record);
+ }
+
+ return record;
+ },
+
+ updateAll: function (data) {
+ var records = this.all();
+
+ records.forEach(function (record) {
+ record = extend(record, data);
+ this.save(record);
+ }, this);
+
+ return records;
+ },
+
+ find: function (criteria) {
+ var key, match, record;
+ var name = this.name;
+ var self = this;
+
+ if (!criteria) return this.all();
+
+ return this.ids.reduce(function (memo, id) {
+ record = jsonData(self.storageAdaptor.getItem(getKey(name, id)));
+ match = findMatch(criteria, record);
+
+ if (match) {
+ memo.push(record);
+ }
+
+ return memo;
+ }, []);
+ },
+
+ get: function (id) {
+ return jsonData(this.storageAdaptor.getItem(getKey(this.name, id)));
+ },
+
+ all: function () {
+ var record, self = this, name = this.name;
+
+ return this.ids.reduce(function (memo, id) {
+ record = self.storageAdaptor.getItem(getKey(name, id));
+
+ if (record) {
+ memo.push(jsonData(record));
+ }
+
+ return memo;
+ }, []);
+ },
+
+ destroy: function (record) {
+ var index;
+ var id = (record[this.idAttribute]) ? record[this.idAttribute] : record;
+ var key = getKey(this.name, id);
+
+ record = jsonData(this.storageAdaptor.getItem(key));
+ this.storageAdaptor.removeItem(key);
+
+ index = this.ids.indexOf(id);
+ if (index != -1) this.ids.splice(index, 1);
+ this.storageAdaptor.setItem(this.name, this.ids.join(","));
+
+ return record;
+ },
+
+ destroyAll: function (criteria) {
+ var attr, id, match, record, key;
+
+ for (var i = this.ids.length - 1; i >= 0; i--) {
+ id = this.ids[i];
+ key = getKey(this.name, id);
+
+ if (criteria) {
+
+ record = jsonData(this.storageAdaptor.getItem(key));
+ match = findMatch(criteria, record);
+
+ if (match) {
+ this.storageAdaptor.removeItem(key);
+ this.ids.splice(i, 1);
+ }
+
+ }
+ else {
+ this.storageAdaptor.removeItem(key);
+ }
+ }
+
+ if (criteria) {
+ this.storageAdaptor.setItem(this.name, this.ids.join(","));
+ }
+ else {
+ this.storageAdaptor.removeItem(this.name);
+ this.ids = [];
+ }
+ },
+
+ size: function () {
+ return this.ids.length;
+ }
+ };
+
+ // helpers
+
+ function jsonData(data) {
+ return data && JSON.parse(data);
+ }
+
+ function getKey(name, id) {
+ return name + "-" + id;
+ }
+
+ function findMatch(criteria, record) {
+ var match, attr;
+
+ if (typeof criteria == 'function') {
+ match = criteria(record);
+ }
+ else {
+ match = true;
+ for (attr in criteria) {
+ match &= (criteria[attr] === record[attr]);
+ }
+ }
+
+ return match;
+ }
+
+ function s4() {
+ return Math.floor((1 + Math.random()) * 0x10000)
+ .toString(16).substring(1);
+ }
+
+ function guid() {
+ return s4() + s4() + '-' + s4() + '-' + s4() +
+ '-' +s4() + '-' + s4() + s4() + s4();
+ }
+
+ function extend(dest, source) {
+ for (var key in source) {
+ if (source.hasOwnProperty(key)) {
+ dest[key] = source[key];
+ }
+ }
+
+ return dest;
+ }
+
+ function depot(name, options) {
+ var store, ids;
+
+ var data = {};
+ var localStorage = {
+ getItem: function (key) { return data[key]; },
+ setItem: function (key, value) {
+ if (!data[key])
+ this.length++;
+ data[key] = value;
+ },
+ removeItem: function (key) {
+ delete data[key];
+ this.length--;
+ },
+ key: function (index) {
+ var i = 0;
+ for (var key in data) {
+ if (i == index)
+ return key;
+ i++;
+ }
+ return null;
+ },
+ length: 0,
+ };
+
+ options = extend({
+ idAttribute: '_id',
+ storageAdaptor: localStorage
+ }, options);
+
+ if (!options.storageAdaptor) throw new Error("No storage adaptor was found");
+
+ store = options.storageAdaptor.getItem(name);
+ ids = (store && store.split(",")) || [];
+
+ return Object.create(api, {
+ name: { value: name },
+ store: { value: store },
+ ids: { value: ids, writable: true },
+ idAttribute: { value: options.idAttribute },
+ storageAdaptor: { value: options.storageAdaptor }
+ });
+ }
+
+ return depot;
+}));
--- /dev/null
+// Copyright 2009-2012 by contributors, MIT License
+// vim: ts=4 sts=4 sw=4 expandtab
+
+// Module systems magic dance
+(function (definition) {
+ // RequireJS
+ if (typeof define == "function") {
+ define(definition);
+ // YUI3
+ } else if (typeof YUI == "function") {
+ YUI.add("es5-sham", definition);
+ // CommonJS and <script>
+ } else {
+ definition();
+ }
+})(function () {
+
+// ES5 15.2.3.2
+// http://es5.github.com/#x15.2.3.2
+if (!Object.getPrototypeOf) {
+ // https://github.com/kriskowal/es5-shim/issues#issue/2
+ // http://ejohn.org/blog/objectgetprototypeof/
+ // recommended by fschaefer on github
+ Object.getPrototypeOf = function getPrototypeOf(object) {
+ return object.__proto__ || (
+ object.constructor
+ ? object.constructor.prototype
+ : prototypeOfObject
+ );
+ };
+}
+
+// ES5 15.2.3.3
+// http://es5.github.com/#x15.2.3.3
+if (!Object.getOwnPropertyDescriptor) {
+ var ERR_NON_OBJECT = "Object.getOwnPropertyDescriptor called on a non-object: ";
+
+ Object.getOwnPropertyDescriptor = function getOwnPropertyDescriptor(object, property) {
+ if ((typeof object != "object" && typeof object != "function") || object === null) {
+ throw new TypeError(ERR_NON_OBJECT + object);
+ }
+ // If object does not owns property return undefined immediately.
+ if (!owns(object, property)) {
+ return;
+ }
+
+ // If object has a property then it's for sure both `enumerable` and
+ // `configurable`.
+ var descriptor = { enumerable: true, configurable: true };
+
+ // If JS engine supports accessor properties then property may be a
+ // getter or setter.
+ if (supportsAccessors) {
+ // Unfortunately `__lookupGetter__` will return a getter even
+ // if object has own non getter property along with a same named
+ // inherited getter. To avoid misbehavior we temporary remove
+ // `__proto__` so that `__lookupGetter__` will return getter only
+ // if it's owned by an object.
+ var prototype = object.__proto__;
+ object.__proto__ = prototypeOfObject;
+
+ var getter = lookupGetter(object, property);
+ var setter = lookupSetter(object, property);
+
+ // Once we have getter and setter we can put values back.
+ object.__proto__ = prototype;
+
+ if (getter || setter) {
+ if (getter) {
+ descriptor.get = getter;
+ }
+ if (setter) {
+ descriptor.set = setter;
+ }
+ // If it was accessor property we're done and return here
+ // in order to avoid adding `value` to the descriptor.
+ return descriptor;
+ }
+ }
+
+ // If we got this far we know that object has an own property that is
+ // not an accessor so we set it as a value and return descriptor.
+ descriptor.value = object[property];
+ return descriptor;
+ };
+}
+
+// ES5 15.2.3.4
+// http://es5.github.com/#x15.2.3.4
+if (!Object.getOwnPropertyNames) {
+ Object.getOwnPropertyNames = function getOwnPropertyNames(object) {
+ return Object.keys(object);
+ };
+}
+
+// ES5 15.2.3.5
+// http://es5.github.com/#x15.2.3.5
+if (!Object.create) {
+ Object.create = function create(prototype, properties) {
+ var object;
+ if (prototype === null) {
+ object = { "__proto__": null };
+ } else {
+ if (typeof prototype != "object") {
+ throw new TypeError("typeof prototype["+(typeof prototype)+"] != 'object'");
+ }
+ var Type = function () {};
+ Type.prototype = prototype;
+ object = new Type();
+ // IE has no built-in implementation of `Object.getPrototypeOf`
+ // neither `__proto__`, but this manually setting `__proto__` will
+ // guarantee that `Object.getPrototypeOf` will work as expected with
+ // objects created using `Object.create`
+ object.__proto__ = prototype;
+ }
+ if (properties !== void 0) {
+ Object.defineProperties(object, properties);
+ }
+ return object;
+ };
+}
+
+// ES5 15.2.3.6
+// http://es5.github.com/#x15.2.3.6
+
+// Patch for WebKit and IE8 standard mode
+// Designed by hax <hax.github.com>
+// related issue: https://github.com/kriskowal/es5-shim/issues#issue/5
+// IE8 Reference:
+// http://msdn.microsoft.com/en-us/library/dd282900.aspx
+// http://msdn.microsoft.com/en-us/library/dd229916.aspx
+// WebKit Bugs:
+// https://bugs.webkit.org/show_bug.cgi?id=36423
+
+function doesDefinePropertyWork(object) {
+ try {
+ Object.defineProperty(object, "sentinel", {});
+ return "sentinel" in object;
+ } catch (exception) {
+ // returns falsy
+ }
+}
+
+// check whether defineProperty works if it's given. Otherwise,
+// shim partially.
+if (Object.defineProperty) {
+ var definePropertyWorksOnObject = doesDefinePropertyWork({});
+ var definePropertyWorksOnDom = typeof document == "undefined" ||
+ doesDefinePropertyWork(document.createElement("div"));
+ if (!definePropertyWorksOnObject || !definePropertyWorksOnDom) {
+ var definePropertyFallback = Object.defineProperty;
+ }
+}
+
+if (!Object.defineProperty || definePropertyFallback) {
+ var ERR_NON_OBJECT_DESCRIPTOR = "Property description must be an object: ";
+ var ERR_NON_OBJECT_TARGET = "Object.defineProperty called on non-object: "
+ var ERR_ACCESSORS_NOT_SUPPORTED = "getters & setters can not be defined " +
+ "on this javascript engine";
+
+ Object.defineProperty = function defineProperty(object, property, descriptor) {
+ if ((typeof object != "object" && typeof object != "function") || object === null) {
+ throw new TypeError(ERR_NON_OBJECT_TARGET + object);
+ }
+ if ((typeof descriptor != "object" && typeof descriptor != "function") || descriptor === null) {
+ throw new TypeError(ERR_NON_OBJECT_DESCRIPTOR + descriptor);
+ }
+ // make a valiant attempt to use the real defineProperty
+ // for I8's DOM elements.
+ if (definePropertyFallback) {
+ try {
+ return definePropertyFallback.call(Object, object, property, descriptor);
+ } catch (exception) {
+ // try the shim if the real one doesn't work
+ }
+ }
+
+ // If it's a data property.
+ if (owns(descriptor, "value")) {
+ // fail silently if "writable", "enumerable", or "configurable"
+ // are requested but not supported
+ /*
+ // alternate approach:
+ if ( // can't implement these features; allow false but not true
+ !(owns(descriptor, "writable") ? descriptor.writable : true) ||
+ !(owns(descriptor, "enumerable") ? descriptor.enumerable : true) ||
+ !(owns(descriptor, "configurable") ? descriptor.configurable : true)
+ )
+ throw new RangeError(
+ "This implementation of Object.defineProperty does not " +
+ "support configurable, enumerable, or writable."
+ );
+ */
+
+ if (supportsAccessors && (lookupGetter(object, property) ||
+ lookupSetter(object, property)))
+ {
+ // As accessors are supported only on engines implementing
+ // `__proto__` we can safely override `__proto__` while defining
+ // a property to make sure that we don't hit an inherited
+ // accessor.
+ var prototype = object.__proto__;
+ object.__proto__ = prototypeOfObject;
+ // Deleting a property anyway since getter / setter may be
+ // defined on object itself.
+ delete object[property];
+ object[property] = descriptor.value;
+ // Setting original `__proto__` back now.
+ object.__proto__ = prototype;
+ } else {
+ object[property] = descriptor.value;
+ }
+ } else {
+ if (!supportsAccessors) {
+ throw new TypeError(ERR_ACCESSORS_NOT_SUPPORTED);
+ }
+ // If we got that far then getters and setters can be defined !!
+ if (owns(descriptor, "get")) {
+ defineGetter(object, property, descriptor.get);
+ }
+ if (owns(descriptor, "set")) {
+ defineSetter(object, property, descriptor.set);
+ }
+ }
+ return object;
+ };
+}
+
+// ES5 15.2.3.7
+// http://es5.github.com/#x15.2.3.7
+if (!Object.defineProperties) {
+ Object.defineProperties = function defineProperties(object, properties) {
+ for (var property in properties) {
+ if (owns(properties, property) && property != "__proto__") {
+ Object.defineProperty(object, property, properties[property]);
+ }
+ }
+ return object;
+ };
+}
+
+// ES5 15.2.3.8
+// http://es5.github.com/#x15.2.3.8
+if (!Object.seal) {
+ Object.seal = function seal(object) {
+ // this is misleading and breaks feature-detection, but
+ // allows "securable" code to "gracefully" degrade to working
+ // but insecure code.
+ return object;
+ };
+}
+
+// ES5 15.2.3.9
+// http://es5.github.com/#x15.2.3.9
+if (!Object.freeze) {
+ Object.freeze = function freeze(object) {
+ // this is misleading and breaks feature-detection, but
+ // allows "securable" code to "gracefully" degrade to working
+ // but insecure code.
+ return object;
+ };
+}
+
+// detect a Rhino bug and patch it
+try {
+ Object.freeze(function () {});
+} catch (exception) {
+ Object.freeze = (function freeze(freezeObject) {
+ return function freeze(object) {
+ if (typeof object == "function") {
+ return object;
+ } else {
+ return freezeObject(object);
+ }
+ };
+ })(Object.freeze);
+}
+
+// ES5 15.2.3.10
+// http://es5.github.com/#x15.2.3.10
+if (!Object.preventExtensions) {
+ Object.preventExtensions = function preventExtensions(object) {
+ // this is misleading and breaks feature-detection, but
+ // allows "securable" code to "gracefully" degrade to working
+ // but insecure code.
+ return object;
+ };
+}
+
+// ES5 15.2.3.11
+// http://es5.github.com/#x15.2.3.11
+if (!Object.isSealed) {
+ Object.isSealed = function isSealed(object) {
+ return false;
+ };
+}
+
+// ES5 15.2.3.12
+// http://es5.github.com/#x15.2.3.12
+if (!Object.isFrozen) {
+ Object.isFrozen = function isFrozen(object) {
+ return false;
+ };
+}
+
+// ES5 15.2.3.13
+// http://es5.github.com/#x15.2.3.13
+if (!Object.isExtensible) {
+ Object.isExtensible = function isExtensible(object) {
+ // 1. If Type(O) is not Object throw a TypeError exception.
+ if (Object(object) !== object) {
+ throw new TypeError(); // TODO message
+ }
+ // 2. Return the Boolean value of the [[Extensible]] internal property of O.
+ var name = '';
+ while (owns(object, name)) {
+ name += '?';
+ }
+ object[name] = true;
+ var returnValue = owns(object, name);
+ delete object[name];
+ return returnValue;
+ };
+}
+
+});
--- /dev/null
+// Copyright 2009-2012 by contributors, MIT License
+// vim: ts=4 sts=4 sw=4 expandtab
+
+// Module systems magic dance
+(function (definition) {
+ // RequireJS
+ if (typeof define == "function") {
+ define(definition);
+ // YUI3
+ } else if (typeof YUI == "function") {
+ YUI.add("es5", definition);
+ // CommonJS and <script>
+ } else {
+ definition();
+ }
+})(function () {
+
+/**
+ * Brings an environment as close to ECMAScript 5 compliance
+ * as is possible with the facilities of erstwhile engines.
+ *
+ * Annotated ES5: http://es5.github.com/ (specific links below)
+ * ES5 Spec: http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf
+ * Required reading: http://javascriptweblog.wordpress.com/2011/12/05/extending-javascript-natives/
+ */
+
+//
+// Function
+// ========
+//
+
+// ES-5 15.3.4.5
+// http://es5.github.com/#x15.3.4.5
+
+if (!Function.prototype.bind) {
+ Function.prototype.bind = function bind(that) { // .length is 1
+ // 1. Let Target be the this value.
+ var target = this;
+ // 2. If IsCallable(Target) is false, throw a TypeError exception.
+ if (typeof target != "function") {
+ throw new TypeError("Function.prototype.bind called on incompatible " + target);
+ }
+ // 3. Let A be a new (possibly empty) internal list of all of the
+ // argument values provided after thisArg (arg1, arg2 etc), in order.
+ // XXX slicedArgs will stand in for "A" if used
+ var args = slice.call(arguments, 1); // for normal call
+ // 4. Let F be a new native ECMAScript object.
+ // 11. Set the [[Prototype]] internal property of F to the standard
+ // built-in Function prototype object as specified in 15.3.3.1.
+ // 12. Set the [[Call]] internal property of F as described in
+ // 15.3.4.5.1.
+ // 13. Set the [[Construct]] internal property of F as described in
+ // 15.3.4.5.2.
+ // 14. Set the [[HasInstance]] internal property of F as described in
+ // 15.3.4.5.3.
+ var bound = function () {
+
+ if (this instanceof bound) {
+ // 15.3.4.5.2 [[Construct]]
+ // When the [[Construct]] internal method of a function object,
+ // F that was created using the bind function is called with a
+ // list of arguments ExtraArgs, the following steps are taken:
+ // 1. Let target be the value of F's [[TargetFunction]]
+ // internal property.
+ // 2. If target has no [[Construct]] internal method, a
+ // TypeError exception is thrown.
+ // 3. Let boundArgs be the value of F's [[BoundArgs]] internal
+ // property.
+ // 4. Let args be a new list containing the same values as the
+ // list boundArgs in the same order followed by the same
+ // values as the list ExtraArgs in the same order.
+ // 5. Return the result of calling the [[Construct]] internal
+ // method of target providing args as the arguments.
+
+ var F = function(){};
+ F.prototype = target.prototype;
+ var self = new F;
+
+ var result = target.apply(
+ self,
+ args.concat(slice.call(arguments))
+ );
+ if (Object(result) === result) {
+ return result;
+ }
+ return self;
+
+ } else {
+ // 15.3.4.5.1 [[Call]]
+ // When the [[Call]] internal method of a function object, F,
+ // which was created using the bind function is called with a
+ // this value and a list of arguments ExtraArgs, the following
+ // steps are taken:
+ // 1. Let boundArgs be the value of F's [[BoundArgs]] internal
+ // property.
+ // 2. Let boundThis be the value of F's [[BoundThis]] internal
+ // property.
+ // 3. Let target be the value of F's [[TargetFunction]] internal
+ // property.
+ // 4. Let args be a new list containing the same values as the
+ // list boundArgs in the same order followed by the same
+ // values as the list ExtraArgs in the same order.
+ // 5. Return the result of calling the [[Call]] internal method
+ // of target providing boundThis as the this value and
+ // providing args as the arguments.
+
+ // equiv: target.call(this, ...boundArgs, ...args)
+ return target.apply(
+ that,
+ args.concat(slice.call(arguments))
+ );
+
+ }
+
+ };
+ // XXX bound.length is never writable, so don't even try
+ //
+ // 15. If the [[Class]] internal property of Target is "Function", then
+ // a. Let L be the length property of Target minus the length of A.
+ // b. Set the length own property of F to either 0 or L, whichever is
+ // larger.
+ // 16. Else set the length own property of F to 0.
+ // 17. Set the attributes of the length own property of F to the values
+ // specified in 15.3.5.1.
+
+ // TODO
+ // 18. Set the [[Extensible]] internal property of F to true.
+
+ // TODO
+ // 19. Let thrower be the [[ThrowTypeError]] function Object (13.2.3).
+ // 20. Call the [[DefineOwnProperty]] internal method of F with
+ // arguments "caller", PropertyDescriptor {[[Get]]: thrower, [[Set]]:
+ // thrower, [[Enumerable]]: false, [[Configurable]]: false}, and
+ // false.
+ // 21. Call the [[DefineOwnProperty]] internal method of F with
+ // arguments "arguments", PropertyDescriptor {[[Get]]: thrower,
+ // [[Set]]: thrower, [[Enumerable]]: false, [[Configurable]]: false},
+ // and false.
+
+ // TODO
+ // NOTE Function objects created using Function.prototype.bind do not
+ // have a prototype property or the [[Code]], [[FormalParameters]], and
+ // [[Scope]] internal properties.
+ // XXX can't delete prototype in pure-js.
+
+ // 22. Return F.
+ return bound;
+ };
+}
+
+// Shortcut to an often accessed properties, in order to avoid multiple
+// dereference that costs universally.
+// _Please note: Shortcuts are defined after `Function.prototype.bind` as we
+// us it in defining shortcuts.
+var call = Function.prototype.call;
+var prototypeOfArray = Array.prototype;
+var prototypeOfObject = Object.prototype;
+var slice = prototypeOfArray.slice;
+// Having a toString local variable name breaks in Opera so use _toString.
+var _toString = call.bind(prototypeOfObject.toString);
+var owns = call.bind(prototypeOfObject.hasOwnProperty);
+
+// If JS engine supports accessors creating shortcuts.
+var defineGetter;
+var defineSetter;
+var lookupGetter;
+var lookupSetter;
+var supportsAccessors;
+if ((supportsAccessors = owns(prototypeOfObject, "__defineGetter__"))) {
+ defineGetter = call.bind(prototypeOfObject.__defineGetter__);
+ defineSetter = call.bind(prototypeOfObject.__defineSetter__);
+ lookupGetter = call.bind(prototypeOfObject.__lookupGetter__);
+ lookupSetter = call.bind(prototypeOfObject.__lookupSetter__);
+}
+
+//
+// Array
+// =====
+//
+
+// ES5 15.4.3.2
+// http://es5.github.com/#x15.4.3.2
+// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/isArray
+if (!Array.isArray) {
+ Array.isArray = function isArray(obj) {
+ return _toString(obj) == "[object Array]";
+ };
+}
+
+// The IsCallable() check in the Array functions
+// has been replaced with a strict check on the
+// internal class of the object to trap cases where
+// the provided function was actually a regular
+// expression literal, which in V8 and
+// JavaScriptCore is a typeof "function". Only in
+// V8 are regular expression literals permitted as
+// reduce parameters, so it is desirable in the
+// general case for the shim to match the more
+// strict and common behavior of rejecting regular
+// expressions.
+
+// ES5 15.4.4.18
+// http://es5.github.com/#x15.4.4.18
+// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/array/forEach
+if (!Array.prototype.forEach) {
+ Array.prototype.forEach = function forEach(fun /*, thisp*/) {
+ var self = toObject(this),
+ thisp = arguments[1],
+ i = -1,
+ length = self.length >>> 0;
+
+ // If no callback function or if callback is not a callable function
+ if (_toString(fun) != "[object Function]") {
+ throw new TypeError(); // TODO message
+ }
+
+ while (++i < length) {
+ if (i in self) {
+ // Invoke the callback function with call, passing arguments:
+ // context, property value, property key, thisArg object context
+ fun.call(thisp, self[i], i, self);
+ }
+ }
+ };
+}
+
+// ES5 15.4.4.19
+// http://es5.github.com/#x15.4.4.19
+// https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/map
+if (!Array.prototype.map) {
+ Array.prototype.map = function map(fun /*, thisp*/) {
+ var self = toObject(this),
+ length = self.length >>> 0,
+ result = Array(length),
+ thisp = arguments[1];
+
+ // If no callback function or if callback is not a callable function
+ if (_toString(fun) != "[object Function]") {
+ throw new TypeError(fun + " is not a function");
+ }
+
+ for (var i = 0; i < length; i++) {
+ if (i in self)
+ result[i] = fun.call(thisp, self[i], i, self);
+ }
+ return result;
+ };
+}
+
+// ES5 15.4.4.20
+// http://es5.github.com/#x15.4.4.20
+// https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/filter
+if (!Array.prototype.filter) {
+ Array.prototype.filter = function filter(fun /*, thisp */) {
+ var self = toObject(this),
+ length = self.length >>> 0,
+ result = [],
+ value,
+ thisp = arguments[1];
+
+ // If no callback function or if callback is not a callable function
+ if (_toString(fun) != "[object Function]") {
+ throw new TypeError(fun + " is not a function");
+ }
+
+ for (var i = 0; i < length; i++) {
+ if (i in self) {
+ value = self[i];
+ if (fun.call(thisp, value, i, self)) {
+ result.push(value);
+ }
+ }
+ }
+ return result;
+ };
+}
+
+// ES5 15.4.4.16
+// http://es5.github.com/#x15.4.4.16
+// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/every
+if (!Array.prototype.every) {
+ Array.prototype.every = function every(fun /*, thisp */) {
+ var self = toObject(this),
+ length = self.length >>> 0,
+ thisp = arguments[1];
+
+ // If no callback function or if callback is not a callable function
+ if (_toString(fun) != "[object Function]") {
+ throw new TypeError(fun + " is not a function");
+ }
+
+ for (var i = 0; i < length; i++) {
+ if (i in self && !fun.call(thisp, self[i], i, self)) {
+ return false;
+ }
+ }
+ return true;
+ };
+}
+
+// ES5 15.4.4.17
+// http://es5.github.com/#x15.4.4.17
+// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/some
+if (!Array.prototype.some) {
+ Array.prototype.some = function some(fun /*, thisp */) {
+ var self = toObject(this),
+ length = self.length >>> 0,
+ thisp = arguments[1];
+
+ // If no callback function or if callback is not a callable function
+ if (_toString(fun) != "[object Function]") {
+ throw new TypeError(fun + " is not a function");
+ }
+
+ for (var i = 0; i < length; i++) {
+ if (i in self && fun.call(thisp, self[i], i, self)) {
+ return true;
+ }
+ }
+ return false;
+ };
+}
+
+// ES5 15.4.4.21
+// http://es5.github.com/#x15.4.4.21
+// https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/reduce
+if (!Array.prototype.reduce) {
+ Array.prototype.reduce = function reduce(fun /*, initial*/) {
+ var self = toObject(this),
+ length = self.length >>> 0;
+
+ // If no callback function or if callback is not a callable function
+ if (_toString(fun) != "[object Function]") {
+ throw new TypeError(fun + " is not a function");
+ }
+
+ // no value to return if no initial value and an empty array
+ if (!length && arguments.length == 1) {
+ throw new TypeError('reduce of empty array with no initial value');
+ }
+
+ var i = 0;
+ var result;
+ if (arguments.length >= 2) {
+ result = arguments[1];
+ } else {
+ do {
+ if (i in self) {
+ result = self[i++];
+ break;
+ }
+
+ // if array contains no values, no initial value to return
+ if (++i >= length) {
+ throw new TypeError('reduce of empty array with no initial value');
+ }
+ } while (true);
+ }
+
+ for (; i < length; i++) {
+ if (i in self) {
+ result = fun.call(void 0, result, self[i], i, self);
+ }
+ }
+
+ return result;
+ };
+}
+
+// ES5 15.4.4.22
+// http://es5.github.com/#x15.4.4.22
+// https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/reduceRight
+if (!Array.prototype.reduceRight) {
+ Array.prototype.reduceRight = function reduceRight(fun /*, initial*/) {
+ var self = toObject(this),
+ length = self.length >>> 0;
+
+ // If no callback function or if callback is not a callable function
+ if (_toString(fun) != "[object Function]") {
+ throw new TypeError(fun + " is not a function");
+ }
+
+ // no value to return if no initial value, empty array
+ if (!length && arguments.length == 1) {
+ throw new TypeError('reduceRight of empty array with no initial value');
+ }
+
+ var result, i = length - 1;
+ if (arguments.length >= 2) {
+ result = arguments[1];
+ } else {
+ do {
+ if (i in self) {
+ result = self[i--];
+ break;
+ }
+
+ // if array contains no values, no initial value to return
+ if (--i < 0) {
+ throw new TypeError('reduceRight of empty array with no initial value');
+ }
+ } while (true);
+ }
+
+ do {
+ if (i in this) {
+ result = fun.call(void 0, result, self[i], i, self);
+ }
+ } while (i--);
+
+ return result;
+ };
+}
+
+// ES5 15.4.4.14
+// http://es5.github.com/#x15.4.4.14
+// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/indexOf
+if (!Array.prototype.indexOf) {
+ Array.prototype.indexOf = function indexOf(sought /*, fromIndex */ ) {
+ var self = toObject(this),
+ length = self.length >>> 0;
+
+ if (!length) {
+ return -1;
+ }
+
+ var i = 0;
+ if (arguments.length > 1) {
+ i = toInteger(arguments[1]);
+ }
+
+ // handle negative indices
+ i = i >= 0 ? i : Math.max(0, length + i);
+ for (; i < length; i++) {
+ if (i in self && self[i] === sought) {
+ return i;
+ }
+ }
+ return -1;
+ };
+}
+
+// ES5 15.4.4.15
+// http://es5.github.com/#x15.4.4.15
+// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/lastIndexOf
+if (!Array.prototype.lastIndexOf) {
+ Array.prototype.lastIndexOf = function lastIndexOf(sought /*, fromIndex */) {
+ var self = toObject(this),
+ length = self.length >>> 0;
+
+ if (!length) {
+ return -1;
+ }
+ var i = length - 1;
+ if (arguments.length > 1) {
+ i = Math.min(i, toInteger(arguments[1]));
+ }
+ // handle negative indices
+ i = i >= 0 ? i : length - Math.abs(i);
+ for (; i >= 0; i--) {
+ if (i in self && sought === self[i]) {
+ return i;
+ }
+ }
+ return -1;
+ };
+}
+
+//
+// Object
+// ======
+//
+
+// ES5 15.2.3.14
+// http://es5.github.com/#x15.2.3.14
+if (!Object.keys) {
+ // http://whattheheadsaid.com/2010/10/a-safer-object-keys-compatibility-implementation
+ var hasDontEnumBug = true,
+ dontEnums = [
+ "toString",
+ "toLocaleString",
+ "valueOf",
+ "hasOwnProperty",
+ "isPrototypeOf",
+ "propertyIsEnumerable",
+ "constructor"
+ ],
+ dontEnumsLength = dontEnums.length;
+
+ for (var key in {"toString": null}) {
+ hasDontEnumBug = false;
+ }
+
+ Object.keys = function keys(object) {
+
+ if ((typeof object != "object" && typeof object != "function") || object === null) {
+ throw new TypeError("Object.keys called on a non-object");
+ }
+
+ var keys = [];
+ for (var name in object) {
+ if (owns(object, name)) {
+ keys.push(name);
+ }
+ }
+
+ if (hasDontEnumBug) {
+ for (var i = 0, ii = dontEnumsLength; i < ii; i++) {
+ var dontEnum = dontEnums[i];
+ if (owns(object, dontEnum)) {
+ keys.push(dontEnum);
+ }
+ }
+ }
+ return keys;
+ };
+
+}
+
+//
+// Date
+// ====
+//
+
+// ES5 15.9.5.43
+// http://es5.github.com/#x15.9.5.43
+// This function returns a String value represent the instance in time
+// represented by this Date object. The format of the String is the Date Time
+// string format defined in 15.9.1.15. All fields are present in the String.
+// The time zone is always UTC, denoted by the suffix Z. If the time value of
+// this object is not a finite Number a RangeError exception is thrown.
+if (!Date.prototype.toISOString || (new Date(-62198755200000).toISOString().indexOf('-000001') === -1)) {
+ Date.prototype.toISOString = function toISOString() {
+ var result, length, value, year;
+ if (!isFinite(this)) {
+ throw new RangeError("Date.prototype.toISOString called on non-finite value.");
+ }
+
+ // the date time string format is specified in 15.9.1.15.
+ result = [this.getUTCMonth() + 1, this.getUTCDate(),
+ this.getUTCHours(), this.getUTCMinutes(), this.getUTCSeconds()];
+ year = this.getUTCFullYear();
+ year = (year < 0 ? '-' : (year > 9999 ? '+' : '')) + ('00000' + Math.abs(year)).slice(0 <= year && year <= 9999 ? -4 : -6);
+
+ length = result.length;
+ while (length--) {
+ value = result[length];
+ // pad months, days, hours, minutes, and seconds to have two digits.
+ if (value < 10) {
+ result[length] = "0" + value;
+ }
+ }
+ // pad milliseconds to have three digits.
+ return year + "-" + result.slice(0, 2).join("-") + "T" + result.slice(2).join(":") + "." +
+ ("000" + this.getUTCMilliseconds()).slice(-3) + "Z";
+ }
+}
+
+// ES5 15.9.4.4
+// http://es5.github.com/#x15.9.4.4
+if (!Date.now) {
+ Date.now = function now() {
+ return new Date().getTime();
+ };
+}
+
+// ES5 15.9.5.44
+// http://es5.github.com/#x15.9.5.44
+// This function provides a String representation of a Date object for use by
+// JSON.stringify (15.12.3).
+if (!Date.prototype.toJSON) {
+ Date.prototype.toJSON = function toJSON(key) {
+ // When the toJSON method is called with argument key, the following
+ // steps are taken:
+
+ // 1. Let O be the result of calling ToObject, giving it the this
+ // value as its argument.
+ // 2. Let tv be ToPrimitive(O, hint Number).
+ // 3. If tv is a Number and is not finite, return null.
+ // XXX
+ // 4. Let toISO be the result of calling the [[Get]] internal method of
+ // O with argument "toISOString".
+ // 5. If IsCallable(toISO) is false, throw a TypeError exception.
+ if (typeof this.toISOString != "function") {
+ throw new TypeError('toISOString property is not callable');
+ }
+ // 6. Return the result of calling the [[Call]] internal method of
+ // toISO with O as the this value and an empty argument list.
+ return this.toISOString();
+
+ // NOTE 1 The argument is ignored.
+
+ // NOTE 2 The toJSON function is intentionally generic; it does not
+ // require that its this value be a Date object. Therefore, it can be
+ // transferred to other kinds of objects for use as a method. However,
+ // it does require that any such object have a toISOString method. An
+ // object is free to use the argument key to filter its
+ // stringification.
+ };
+}
+
+// ES5 15.9.4.2
+// http://es5.github.com/#x15.9.4.2
+// based on work shared by Daniel Friesen (dantman)
+// http://gist.github.com/303249
+if (!Date.parse || Date.parse("+275760-09-13T00:00:00.000Z") !== 8.64e15) {
+ // XXX global assignment won't work in embeddings that use
+ // an alternate object for the context.
+ Date = (function(NativeDate) {
+
+ // Date.length === 7
+ var Date = function Date(Y, M, D, h, m, s, ms) {
+ var length = arguments.length;
+ if (this instanceof NativeDate) {
+ var date = length == 1 && String(Y) === Y ? // isString(Y)
+ // We explicitly pass it through parse:
+ new NativeDate(Date.parse(Y)) :
+ // We have to manually make calls depending on argument
+ // length here
+ length >= 7 ? new NativeDate(Y, M, D, h, m, s, ms) :
+ length >= 6 ? new NativeDate(Y, M, D, h, m, s) :
+ length >= 5 ? new NativeDate(Y, M, D, h, m) :
+ length >= 4 ? new NativeDate(Y, M, D, h) :
+ length >= 3 ? new NativeDate(Y, M, D) :
+ length >= 2 ? new NativeDate(Y, M) :
+ length >= 1 ? new NativeDate(Y) :
+ new NativeDate();
+ // Prevent mixups with unfixed Date object
+ date.constructor = Date;
+ return date;
+ }
+ return NativeDate.apply(this, arguments);
+ };
+
+ // 15.9.1.15 Date Time String Format.
+ var isoDateExpression = new RegExp("^" +
+ "(\\d{4}|[\+\-]\\d{6})" + // four-digit year capture or sign + 6-digit extended year
+ "(?:-(\\d{2})" + // optional month capture
+ "(?:-(\\d{2})" + // optional day capture
+ "(?:" + // capture hours:minutes:seconds.milliseconds
+ "T(\\d{2})" + // hours capture
+ ":(\\d{2})" + // minutes capture
+ "(?:" + // optional :seconds.milliseconds
+ ":(\\d{2})" + // seconds capture
+ "(?:\\.(\\d{3}))?" + // milliseconds capture
+ ")?" +
+ "(?:" + // capture UTC offset component
+ "Z|" + // UTC capture
+ "(?:" + // offset specifier +/-hours:minutes
+ "([-+])" + // sign capture
+ "(\\d{2})" + // hours offset capture
+ ":(\\d{2})" + // minutes offset capture
+ ")" +
+ ")?)?)?)?" +
+ "$");
+
+ // Copy any custom methods a 3rd party library may have added
+ for (var key in NativeDate) {
+ Date[key] = NativeDate[key];
+ }
+
+ // Copy "native" methods explicitly; they may be non-enumerable
+ Date.now = NativeDate.now;
+ Date.UTC = NativeDate.UTC;
+ Date.prototype = NativeDate.prototype;
+ Date.prototype.constructor = Date;
+
+ // Upgrade Date.parse to handle simplified ISO 8601 strings
+ Date.parse = function parse(string) {
+ var match = isoDateExpression.exec(string);
+ if (match) {
+ match.shift(); // kill match[0], the full match
+ // parse months, days, hours, minutes, seconds, and milliseconds
+ for (var i = 1; i < 7; i++) {
+ // provide default values if necessary
+ match[i] = +(match[i] || (i < 3 ? 1 : 0));
+ // match[1] is the month. Months are 0-11 in JavaScript
+ // `Date` objects, but 1-12 in ISO notation, so we
+ // decrement.
+ if (i == 1) {
+ match[i]--;
+ }
+ }
+
+ // parse the UTC offset component
+ var minuteOffset = +match.pop(), hourOffset = +match.pop(), sign = match.pop();
+
+ // compute the explicit time zone offset if specified
+ var offset = 0;
+ if (sign) {
+ // detect invalid offsets and return early
+ if (hourOffset > 23 || minuteOffset > 59) {
+ return NaN;
+ }
+
+ // express the provided time zone offset in minutes. The offset is
+ // negative for time zones west of UTC; positive otherwise.
+ offset = (hourOffset * 60 + minuteOffset) * 6e4 * (sign == "+" ? -1 : 1);
+ }
+
+ // Date.UTC for years between 0 and 99 converts year to 1900 + year
+ // The Gregorian calendar has a 400-year cycle, so
+ // to Date.UTC(year + 400, .... ) - 12622780800000 == Date.UTC(year, ...),
+ // where 12622780800000 - number of milliseconds in Gregorian calendar 400 years
+ var year = +match[0];
+ if (0 <= year && year <= 99) {
+ match[0] = year + 400;
+ return NativeDate.UTC.apply(this, match) + offset - 12622780800000;
+ }
+
+ // compute a new UTC date value, accounting for the optional offset
+ return NativeDate.UTC.apply(this, match) + offset;
+ }
+ return NativeDate.parse.apply(this, arguments);
+ };
+
+ return Date;
+ })(Date);
+}
+
+//
+// String
+// ======
+//
+
+// ES5 15.5.4.20
+// http://es5.github.com/#x15.5.4.20
+var ws = "\x09\x0A\x0B\x0C\x0D\x20\xA0\u1680\u180E\u2000\u2001\u2002\u2003" +
+ "\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\u2028" +
+ "\u2029\uFEFF";
+if (!String.prototype.trim || ws.trim()) {
+ // http://blog.stevenlevithan.com/archives/faster-trim-javascript
+ // http://perfectionkills.com/whitespace-deviations/
+ ws = "[" + ws + "]";
+ var trimBeginRegexp = new RegExp("^" + ws + ws + "*"),
+ trimEndRegexp = new RegExp(ws + ws + "*$");
+ String.prototype.trim = function trim() {
+ if (this === undefined || this === null) {
+ throw new TypeError("can't convert "+this+" to object");
+ }
+ return String(this).replace(trimBeginRegexp, "").replace(trimEndRegexp, "");
+ };
+}
+
+//
+// Util
+// ======
+//
+
+// ES5 9.4
+// http://es5.github.com/#x9.4
+// http://jsperf.com/to-integer
+var toInteger = function (n) {
+ n = +n;
+ if (n !== n) { // isNaN
+ n = 0;
+ } else if (n !== 0 && n !== (1/0) && n !== -(1/0)) {
+ n = (n > 0 || -1) * Math.floor(Math.abs(n));
+ }
+ return n;
+};
+
+var prepareString = "a"[0] != "a";
+ // ES5 9.9
+ // http://es5.github.com/#x9.9
+var toObject = function (o) {
+ if (o == null) { // this matches both null and undefined
+ throw new TypeError("can't convert "+o+" to object");
+ }
+ // If the implementation doesn't support by-index access of
+ // string characters (ex. IE < 9), split the string
+ if (prepareString && typeof o == "string" && o) {
+ return o.split("");
+ }
+ return Object(o);
+};
+
+});
--- /dev/null
+// ==========================================
+// Copyright 2013 Twitter, Inc
+// Licensed under The MIT License
+// http://opensource.org/licenses/MIT
+// ==========================================
+
+define(
+
+ [
+ './compose'
+ ],
+
+ function(compose) {
+ 'use strict';
+
+ var advice = {
+
+ around: function(base, wrapped) {
+ return function composedAround() {
+ // unpacking arguments by hand benchmarked faster
+ var i = 0, l = arguments.length, args = new Array(l + 1);
+ args[0] = base.bind(this);
+ for (; i < l; i++) args[i + 1] = arguments[i];
+
+ return wrapped.apply(this, args);
+ };
+ },
+
+ before: function(base, before) {
+ var beforeFn = (typeof before == 'function') ? before : before.obj[before.fnName];
+ return function composedBefore() {
+ beforeFn.apply(this, arguments);
+ return base.apply(this, arguments);
+ };
+ },
+
+ after: function(base, after) {
+ var afterFn = (typeof after == 'function') ? after : after.obj[after.fnName];
+ return function composedAfter() {
+ var res = (base.unbound || base).apply(this, arguments);
+ afterFn.apply(this, arguments);
+ return res;
+ };
+ },
+
+ // a mixin that allows other mixins to augment existing functions by adding additional
+ // code before, after or around.
+ withAdvice: function() {
+ ['before', 'after', 'around'].forEach(function(m) {
+ this[m] = function(method, fn) {
+
+ compose.unlockProperty(this, method, function() {
+ if (typeof this[method] == 'function') {
+ this[method] = advice[m](this[method], fn);
+ } else {
+ this[method] = fn;
+ }
+
+ return this[method];
+ });
+
+ };
+ }, this);
+ }
+ };
+
+ return advice;
+ }
+);
--- /dev/null
+// ==========================================
+// Copyright 2013 Twitter, Inc
+// Licensed under The MIT License
+// http://opensource.org/licenses/MIT
+// ==========================================
+
+define(
+
+ [
+ './utils',
+ './registry',
+ './debug'
+ ],
+
+ function(utils, registry, debug) {
+ 'use strict';
+
+ // common mixin allocates basic functionality - used by all component prototypes
+ // callback context is bound to component
+ var componentId = 0;
+
+ function teardownInstance(instanceInfo){
+ instanceInfo.events.slice().forEach(function(event) {
+ var args = [event.type];
+
+ event.element && args.unshift(event.element);
+ (typeof event.callback == 'function') && args.push(event.callback);
+
+ this.off.apply(this, args);
+ }, instanceInfo.instance);
+ }
+
+ function checkSerializable(type, data) {
+ try {
+ window.postMessage(data, '*');
+ } catch(e) {
+ console.log('unserializable data for event',type,':',data);
+ throw new Error(
+ ['The event', type, 'on component', this.toString(), 'was triggered with non-serializable data'].join(' ')
+ );
+ }
+ }
+
+ function withBase() {
+
+ // delegate trigger, bind and unbind to an element
+ // if $element not supplied, use component's node
+ // other arguments are passed on
+ // event can be either a string specifying the type
+ // of the event, or a hash specifying both the type
+ // and a default function to be called.
+ this.trigger = function() {
+ var $element, type, data, event, defaultFn;
+ var lastIndex = arguments.length - 1, lastArg = arguments[lastIndex];
+
+ if (typeof lastArg != 'string' && !(lastArg && lastArg.defaultBehavior)) {
+ lastIndex--;
+ data = lastArg;
+ }
+
+ if (lastIndex == 1) {
+ $element = $(arguments[0]);
+ event = arguments[1];
+ } else {
+ $element = this.$node;
+ event = arguments[0];
+ }
+
+ if (event.defaultBehavior) {
+ defaultFn = event.defaultBehavior;
+ event = $.Event(event.type);
+ }
+
+ type = event.type || event;
+
+ if (debug.enabled && window.postMessage) {
+ checkSerializable.call(this, type, data);
+ }
+
+ if (typeof this.attr.eventData === 'object') {
+ data = $.extend(true, {}, this.attr.eventData, data);
+ }
+
+ $element.trigger((event || type), data);
+
+ if (defaultFn && !event.isDefaultPrevented()) {
+ (this[defaultFn] || defaultFn).call(this);
+ }
+
+ return $element;
+ };
+
+ this.on = function() {
+ var $element, type, callback, originalCb;
+ var lastIndex = arguments.length - 1, origin = arguments[lastIndex];
+
+ if (typeof origin == 'object') {
+ //delegate callback
+ originalCb = utils.delegate(
+ this.resolveDelegateRules(origin)
+ );
+ } else {
+ originalCb = origin;
+ }
+
+ if (lastIndex == 2) {
+ $element = $(arguments[0]);
+ type = arguments[1];
+ } else {
+ $element = this.$node;
+ type = arguments[0];
+ }
+
+ if (typeof originalCb != 'function' && typeof originalCb != 'object') {
+ throw new Error('Unable to bind to "' + type + '" because the given callback is not a function or an object');
+ }
+
+ callback = originalCb.bind(this);
+ callback.target = originalCb;
+
+ // if the original callback is already branded by jQuery's guid, copy it to the context-bound version
+ if (originalCb.guid) {
+ callback.guid = originalCb.guid;
+ }
+
+ $element.on(type, callback);
+
+ // get jquery's guid from our bound fn, so unbinding will work
+ originalCb.guid = callback.guid;
+
+ return callback;
+ };
+
+ this.off = function() {
+ var $element, type, callback;
+ var lastIndex = arguments.length - 1;
+
+ if (typeof arguments[lastIndex] == 'function') {
+ callback = arguments[lastIndex];
+ lastIndex -= 1;
+ }
+
+ if (lastIndex == 1) {
+ $element = $(arguments[0]);
+ type = arguments[1];
+ } else {
+ $element = this.$node;
+ type = arguments[0];
+ }
+
+ return $element.off(type, callback);
+ };
+
+ this.resolveDelegateRules = function(ruleInfo) {
+ var rules = {};
+
+ Object.keys(ruleInfo).forEach(function(r) {
+ if (!(r in this.attr)) {
+ throw new Error('Component "' + this.toString() + '" wants to listen on "' + r + '" but no such attribute was defined.');
+ }
+ rules[this.attr[r]] = ruleInfo[r];
+ }, this);
+
+ return rules;
+ };
+
+ this.defaultAttrs = function(defaults) {
+ utils.push(this.defaults, defaults, true) || (this.defaults = defaults);
+ };
+
+ this.select = function(attributeKey) {
+ return this.$node.find(this.attr[attributeKey]);
+ };
+
+ this.initialize = function(node, attrs) {
+ attrs || (attrs = {});
+ //only assign identity if there isn't one (initialize can be called multiple times)
+ this.identity || (this.identity = componentId++);
+
+ if (!node) {
+ throw new Error('Component needs a node');
+ }
+
+ if (node.jquery) {
+ this.node = node[0];
+ this.$node = node;
+ } else {
+ this.node = node;
+ this.$node = $(node);
+ }
+
+ // merge defaults with supplied options
+ // put options in attr.__proto__ to avoid merge overhead
+ var attr = Object.create(attrs);
+ for (var key in this.defaults) {
+ if (!attrs.hasOwnProperty(key)) {
+ attr[key] = this.defaults[key];
+ }
+ }
+
+ this.attr = attr;
+
+ Object.keys(this.defaults || {}).forEach(function(key) {
+ if (this.defaults[key] === null && this.attr[key] === null) {
+ throw new Error('Required attribute "' + key + '" not specified in attachTo for component "' + this.toString() + '".');
+ }
+ }, this);
+
+ return this;
+ };
+
+ this.teardown = function() {
+ teardownInstance(registry.findInstanceInfo(this));
+ };
+ }
+
+ return withBase;
+ }
+);
--- /dev/null
+// ==========================================
+// Copyright 2013 Twitter, Inc
+// Licensed under The MIT License
+// http://opensource.org/licenses/MIT
+// ==========================================
+
+define(
+
+ [
+ './advice',
+ './utils',
+ './compose',
+ './base',
+ './registry',
+ './logger',
+ './debug'
+ ],
+
+ function(advice, utils, compose, withBase, registry, withLogging, debug) {
+ 'use strict';
+
+ var functionNameRegEx = /function (.*?)\s?\(/;
+
+ // teardown for all instances of this constructor
+ function teardownAll() {
+ var componentInfo = registry.findComponentInfo(this);
+
+ componentInfo && Object.keys(componentInfo.instances).forEach(function(k) {
+ var info = componentInfo.instances[k];
+ info.instance.teardown();
+ });
+ }
+
+ function checkSerializable(type, data) {
+ try {
+ window.postMessage(data, '*');
+ } catch(e) {
+ console.log('unserializable data for event',type,':',data);
+ throw new Error(
+ ['The event', type, 'on component', this.toString(), 'was triggered with non-serializable data'].join(' ')
+ );
+ }
+ }
+
+ function attachTo(selector/*, options args */) {
+ // unpacking arguments by hand benchmarked faster
+ var l = arguments.length;
+ var args = new Array(l - 1);
+ for (var i = 1; i < l; i++) args[i - 1] = arguments[i];
+
+ if (!selector) {
+ throw new Error('Component needs to be attachTo\'d a jQuery object, native node or selector string');
+ }
+
+ var options = utils.merge.apply(utils, args);
+ var componentInfo = registry.findComponentInfo(this);
+
+ $(selector).each(function(i, node) {
+ if (componentInfo && componentInfo.isAttachedTo(node)) {
+ // already attached
+ return;
+ }
+
+ (new this).initialize(node, options);
+ }.bind(this));
+ }
+
+ // define the constructor for a custom component type
+ // takes an unlimited number of mixin functions as arguments
+ // typical api call with 3 mixins: define(timeline, withTweetCapability, withScrollCapability);
+ function define(/*mixins*/) {
+ // unpacking arguments by hand benchmarked faster
+ var l = arguments.length;
+ // add three for common mixins
+ var mixins = new Array(l + 3);
+ for (var i = 0; i < l; i++) mixins[i] = arguments[i];
+
+ var Component = function() {};
+
+ Component.toString = Component.prototype.toString = function() {
+ var prettyPrintMixins = mixins.map(function(mixin) {
+ if (mixin.name == null) {
+ // function name property not supported by this browser, use regex
+ var m = mixin.toString().match(functionNameRegEx);
+ return (m && m[1]) ? m[1] : '';
+ } else {
+ return (mixin.name != 'withBase') ? mixin.name : '';
+ }
+ }).filter(Boolean).join(', ');
+ return prettyPrintMixins;
+ };
+
+ if (debug.enabled) {
+ Component.describe = Component.prototype.describe = Component.toString();
+ }
+
+ // 'options' is optional hash to be merged with 'defaults' in the component definition
+ Component.attachTo = attachTo;
+ Component.teardownAll = teardownAll;
+
+ // prepend common mixins to supplied list, then mixin all flavors
+ if (debug.enabled) {
+ mixins.unshift(withLogging);
+ }
+ mixins.unshift(withBase, advice.withAdvice, registry.withRegistration);
+ compose.mixin(Component.prototype, mixins);
+
+ return Component;
+ }
+
+ define.teardownAll = function() {
+ registry.components.slice().forEach(function(c) {
+ c.component.teardownAll();
+ });
+ registry.reset();
+ };
+
+ return define;
+ }
+);
--- /dev/null
+// ==========================================
+// Copyright 2013 Twitter, Inc
+// Licensed under The MIT License
+// http://opensource.org/licenses/MIT
+// ==========================================
+
+define(
+
+ [
+ './utils',
+ './debug'
+ ],
+
+ function(utils, debug) {
+ 'use strict';
+
+ //enumerables are shims - getOwnPropertyDescriptor shim doesn't work
+ var canWriteProtect = debug.enabled && !utils.isEnumerable(Object, 'getOwnPropertyDescriptor');
+ //whitelist of unlockable property names
+ var dontLock = ['mixedIn'];
+
+ if (canWriteProtect) {
+ //IE8 getOwnPropertyDescriptor is built-in but throws exeption on non DOM objects
+ try {
+ Object.getOwnPropertyDescriptor(Object, 'keys');
+ } catch(e) {
+ canWriteProtect = false;
+ }
+ }
+
+ function setPropertyWritability(obj, isWritable) {
+ if (!canWriteProtect) {
+ return;
+ }
+
+ var props = Object.create(null);
+
+ Object.keys(obj).forEach(
+ function (key) {
+ if (dontLock.indexOf(key) < 0) {
+ var desc = Object.getOwnPropertyDescriptor(obj, key);
+ desc.writable = isWritable;
+ props[key] = desc;
+ }
+ }
+ );
+
+ Object.defineProperties(obj, props);
+ }
+
+ function unlockProperty(obj, prop, op) {
+ var writable;
+
+ if (!canWriteProtect || !obj.hasOwnProperty(prop)) {
+ op.call(obj);
+ return;
+ }
+
+ writable = Object.getOwnPropertyDescriptor(obj, prop).writable;
+ Object.defineProperty(obj, prop, { writable: true });
+ op.call(obj);
+ Object.defineProperty(obj, prop, { writable: writable });
+ }
+
+ function mixin(base, mixins) {
+ base.mixedIn = base.hasOwnProperty('mixedIn') ? base.mixedIn : [];
+
+ mixins.forEach(function(mixin) {
+ if (base.mixedIn.indexOf(mixin) == -1) {
+ setPropertyWritability(base, false);
+ mixin.call(base);
+ base.mixedIn.push(mixin);
+ }
+ });
+
+ setPropertyWritability(base, true);
+ }
+
+ return {
+ mixin: mixin,
+ unlockProperty: unlockProperty
+ };
+
+ }
+);
--- /dev/null
+// ==========================================
+// Copyright 2013 Twitter, Inc
+// Licensed under The MIT License
+// http://opensource.org/licenses/MIT
+// ==========================================
+
+define(
+
+ [],
+
+ function() {
+ 'use strict';
+
+ //******************************************************************************************
+ // Search object model
+ //******************************************************************************************
+
+ function traverse(util, searchTerm, options) {
+ options = options || {};
+ var obj = options.obj || window;
+ var path = options.path || ((obj==window) ? 'window' : '');
+ var props = Object.keys(obj);
+ props.forEach(function(prop) {
+ if ((tests[util] || util)(searchTerm, obj, prop)){
+ console.log([path, '.', prop].join(''), '->', ['(', typeof obj[prop], ')'].join(''), obj[prop]);
+ }
+ if (Object.prototype.toString.call(obj[prop]) == '[object Object]' && (obj[prop] != obj) && path.split('.').indexOf(prop) == -1) {
+ traverse(util, searchTerm, {obj: obj[prop], path: [path,prop].join('.')});
+ }
+ });
+ }
+
+ function search(util, expected, searchTerm, options) {
+ if (!expected || typeof searchTerm == expected) {
+ traverse(util, searchTerm, options);
+ } else {
+ console.error([searchTerm, 'must be', expected].join(' '));
+ }
+ }
+
+ var tests = {
+ 'name': function(searchTerm, obj, prop) {return searchTerm == prop;},
+ 'nameContains': function(searchTerm, obj, prop) {return prop.indexOf(searchTerm) > -1;},
+ 'type': function(searchTerm, obj, prop) {return obj[prop] instanceof searchTerm;},
+ 'value': function(searchTerm, obj, prop) {return obj[prop] === searchTerm;},
+ 'valueCoerced': function(searchTerm, obj, prop) {return obj[prop] == searchTerm;}
+ };
+
+ function byName(searchTerm, options) {search('name', 'string', searchTerm, options);}
+ function byNameContains(searchTerm, options) {search('nameContains', 'string', searchTerm, options);}
+ function byType(searchTerm, options) {search('type', 'function', searchTerm, options);}
+ function byValue(searchTerm, options) {search('value', null, searchTerm, options);}
+ function byValueCoerced(searchTerm, options) {search('valueCoerced', null, searchTerm, options);}
+ function custom(fn, options) {traverse(fn, null, options);}
+
+ //******************************************************************************************
+ // Event logging
+ //******************************************************************************************
+
+ var ALL = 'all'; //no filter
+
+ //no logging by default
+ var defaultEventNamesFilter = [];
+ var defaultActionsFilter = [];
+
+ var logFilter = retrieveLogFilter();
+
+ function filterEventLogsByAction(/*actions*/) {
+ var actions = [].slice.call(arguments);
+
+ logFilter.eventNames.length || (logFilter.eventNames = ALL);
+ logFilter.actions = actions.length ? actions : ALL;
+ saveLogFilter();
+ }
+
+ function filterEventLogsByName(/*eventNames*/) {
+ var eventNames = [].slice.call(arguments);
+
+ logFilter.actions.length || (logFilter.actions = ALL);
+ logFilter.eventNames = eventNames.length ? eventNames : ALL;
+ saveLogFilter();
+ }
+
+ function hideAllEventLogs() {
+ logFilter.actions = [];
+ logFilter.eventNames = [];
+ saveLogFilter();
+ }
+
+ function showAllEventLogs() {
+ logFilter.actions = ALL;
+ logFilter.eventNames = ALL;
+ saveLogFilter();
+ }
+
+ function saveLogFilter() {
+ if (window.localStorage) {
+ localStorage.setItem('logFilter_eventNames', logFilter.eventNames);
+ localStorage.setItem('logFilter_actions', logFilter.actions);
+ }
+ }
+
+ function retrieveLogFilter() {
+ var result = {
+ eventNames: (window.localStorage && localStorage.getItem('logFilter_eventNames')) || defaultEventNamesFilter,
+ actions: (window.localStorage && localStorage.getItem('logFilter_actions')) || defaultActionsFilter
+ };
+
+ // reconstitute arrays
+ Object.keys(result).forEach(function(k) {
+ var thisProp = result[k];
+ if (typeof thisProp == 'string' && thisProp !== ALL) {
+ result[k] = thisProp.split(',');
+ }
+ });
+ return result;
+ }
+
+ return {
+
+ enable: function(enable) {
+ this.enabled = !!enable;
+
+ if (enable && window.console) {
+ console.info('Booting in DEBUG mode');
+ console.info('You can configure event logging with DEBUG.events.logAll()/logNone()/logByName()/logByAction()');
+ }
+
+ window.DEBUG = this;
+ },
+
+ find: {
+ byName: byName,
+ byNameContains: byNameContains,
+ byType: byType,
+ byValue: byValue,
+ byValueCoerced: byValueCoerced,
+ custom: custom
+ },
+
+ events: {
+ logFilter: logFilter,
+
+ // Accepts any number of action args
+ // e.g. DEBUG.events.logByAction("on", "off")
+ logByAction: filterEventLogsByAction,
+
+ // Accepts any number of event name args (inc. regex or wildcards)
+ // e.g. DEBUG.events.logByName(/ui.*/, "*Thread*");
+ logByName: filterEventLogsByName,
+
+ logAll: showAllEventLogs,
+ logNone: hideAllEventLogs
+ }
+ };
+ }
+);
--- /dev/null
+// ==========================================
+// Copyright 2013 Twitter, Inc
+// Licensed under The MIT License
+// http://opensource.org/licenses/MIT
+// ==========================================
+
+define(
+
+ [
+ './advice',
+ './component',
+ './compose',
+ './logger',
+ './registry',
+ './utils'
+ ],
+
+ function(advice, component, compose, logger, registry, utils) {
+ 'use strict';
+
+ return {
+ advice: advice,
+ component: component,
+ compose: compose,
+ logger: logger,
+ registry: registry,
+ utils: utils
+ };
+
+ }
+);
--- /dev/null
+// ==========================================
+// Copyright 2013 Twitter, Inc
+// Licensed under The MIT License
+// http://opensource.org/licenses/MIT
+// ==========================================
+
+define(
+
+ [
+ './utils'
+ ],
+
+ function(utils) {
+ 'use strict';
+
+ var actionSymbols = {
+ on: '<-',
+ trigger: '->',
+ off: 'x '
+ };
+
+ function elemToString(elem) {
+ var tagStr = elem.tagName ? elem.tagName.toLowerCase() : elem.toString();
+ var classStr = elem.className ? '.' + (elem.className) : '';
+ var result = tagStr + classStr;
+ return elem.tagName ? ['\'', '\''].join(result) : result;
+ }
+
+ function log(action, component, eventArgs) {
+ var name, elem, fn, logFilter, toRegExp, actionLoggable, nameLoggable;
+
+ if (typeof eventArgs[eventArgs.length-1] == 'function') {
+ fn = eventArgs.pop();
+ fn = fn.unbound || fn; // use unbound version if any (better info)
+ }
+
+ if (typeof eventArgs[eventArgs.length - 1] == 'object') {
+ eventArgs.pop(); // trigger data arg - not logged right now
+ }
+
+ if (eventArgs.length == 2) {
+ elem = eventArgs[0];
+ name = eventArgs[1];
+ } else {
+ elem = component.$node[0];
+ name = eventArgs[0];
+ }
+
+ if (window.DEBUG && window.DEBUG.enabled) {
+ logFilter = DEBUG.events.logFilter;
+
+ // no regex for you, actions...
+ actionLoggable = logFilter.actions == 'all' || (logFilter.actions.indexOf(action) > -1);
+ // event name filter allow wildcards or regex...
+ toRegExp = function(expr) {
+ return expr.test ? expr : new RegExp('^' + expr.replace(/\*/g, '.*') + '$');
+ };
+ nameLoggable =
+ logFilter.eventNames == 'all' ||
+ logFilter.eventNames.some(function(e) {return toRegExp(e).test(name);});
+
+ if (actionLoggable && nameLoggable) {
+ console.info(
+ actionSymbols[action],
+ action,
+ '[' + name + ']',
+ elemToString(elem),
+ component.constructor.describe.split(' ').slice(0,3).join(' ') // two mixins only
+ );
+ }
+ }
+ }
+
+ function withLogging() {
+ this.before('trigger', function() {
+ log('trigger', this, utils.toArray(arguments));
+ });
+ this.before('on', function() {
+ log('on', this, utils.toArray(arguments));
+ });
+ this.before('off', function() {
+ log('off', this, utils.toArray(arguments));
+ });
+ }
+
+ return withLogging;
+ }
+);
--- /dev/null
+// ==========================================
+// Copyright 2013 Twitter, Inc
+// Licensed under The MIT License
+// http://opensource.org/licenses/MIT
+// ==========================================
+
+define(
+
+ [],
+
+ function() {
+ 'use strict';
+
+ function parseEventArgs(instance, args) {
+ var element, type, callback;
+ var end = args.length;
+
+ if (typeof args[end - 1] === 'function') {
+ end -= 1;
+ callback = args[end];
+ }
+
+ if (typeof args[end - 1] === 'object') {
+ end -= 1;
+ }
+
+ if (end == 2) {
+ element = args[0];
+ type = args[1];
+ } else {
+ element = instance.node;
+ type = args[0];
+ }
+
+ return {
+ element: element,
+ type: type,
+ callback: callback
+ };
+ }
+
+ function matchEvent(a, b) {
+ return (
+ (a.element == b.element) &&
+ (a.type == b.type) &&
+ (b.callback == null || (a.callback == b.callback))
+ );
+ }
+
+ function Registry() {
+
+ var registry = this;
+
+ (this.reset = function() {
+ this.components = [];
+ this.allInstances = {};
+ this.events = [];
+ }).call(this);
+
+ function ComponentInfo(component) {
+ this.component = component;
+ this.attachedTo = [];
+ this.instances = {};
+
+ this.addInstance = function(instance) {
+ var instanceInfo = new InstanceInfo(instance);
+ this.instances[instance.identity] = instanceInfo;
+ this.attachedTo.push(instance.node);
+
+ return instanceInfo;
+ };
+
+ this.removeInstance = function(instance) {
+ delete this.instances[instance.identity];
+ var indexOfNode = this.attachedTo.indexOf(instance.node);
+ (indexOfNode > -1) && this.attachedTo.splice(indexOfNode, 1);
+
+ if (!Object.keys(this.instances).length) {
+ //if I hold no more instances remove me from registry
+ registry.removeComponentInfo(this);
+ }
+ };
+
+ this.isAttachedTo = function(node) {
+ return this.attachedTo.indexOf(node) > -1;
+ };
+ }
+
+ function InstanceInfo(instance) {
+ this.instance = instance;
+ this.events = [];
+
+ this.addBind = function(event) {
+ this.events.push(event);
+ registry.events.push(event);
+ };
+
+ this.removeBind = function(event) {
+ for (var i = 0, e; e = this.events[i]; i++) {
+ if (matchEvent(e, event)) {
+ this.events.splice(i, 1);
+ }
+ }
+ };
+ }
+
+ this.addInstance = function(instance) {
+ var component = this.findComponentInfo(instance);
+
+ if (!component) {
+ component = new ComponentInfo(instance.constructor);
+ this.components.push(component);
+ }
+
+ var inst = component.addInstance(instance);
+
+ this.allInstances[instance.identity] = inst;
+
+ return component;
+ };
+
+ this.removeInstance = function(instance) {
+ var index, instInfo = this.findInstanceInfo(instance);
+
+ //remove from component info
+ var componentInfo = this.findComponentInfo(instance);
+ componentInfo && componentInfo.removeInstance(instance);
+
+ //remove from registry
+ delete this.allInstances[instance.identity];
+ };
+
+ this.removeComponentInfo = function(componentInfo) {
+ var index = this.components.indexOf(componentInfo);
+ (index > -1) && this.components.splice(index, 1);
+ };
+
+ this.findComponentInfo = function(which) {
+ var component = which.attachTo ? which : which.constructor;
+
+ for (var i = 0, c; c = this.components[i]; i++) {
+ if (c.component === component) {
+ return c;
+ }
+ }
+
+ return null;
+ };
+
+ this.findInstanceInfo = function(instance) {
+ return this.allInstances[instance.identity] || null;
+ };
+
+ this.findInstanceInfoByNode = function(node) {
+ var result = [];
+ Object.keys(this.allInstances).forEach(function(k) {
+ var thisInstanceInfo = this.allInstances[k];
+ if (thisInstanceInfo.instance.node === node) {
+ result.push(thisInstanceInfo);
+ }
+ }, this);
+ return result;
+ };
+
+ this.on = function(componentOn) {
+ var instance = registry.findInstanceInfo(this), boundCallback;
+
+ // unpacking arguments by hand benchmarked faster
+ var l = arguments.length, i = 1;
+ var otherArgs = new Array(l - 1);
+ for (; i < l; i++) otherArgs[i - 1] = arguments[i];
+
+ if (instance) {
+ boundCallback = componentOn.apply(null, otherArgs);
+ if (boundCallback) {
+ otherArgs[otherArgs.length-1] = boundCallback;
+ }
+ var event = parseEventArgs(this, otherArgs);
+ instance.addBind(event);
+ }
+ };
+
+ this.off = function(/*el, type, callback*/) {
+ var event = parseEventArgs(this, arguments),
+ instance = registry.findInstanceInfo(this);
+
+ if (instance) {
+ instance.removeBind(event);
+ }
+
+ //remove from global event registry
+ for (var i = 0, e; e = registry.events[i]; i++) {
+ if (matchEvent(e, event)) {
+ registry.events.splice(i, 1);
+ }
+ }
+ };
+
+ // debug tools may want to add advice to trigger
+ registry.trigger = function() {};
+
+ this.teardown = function() {
+ registry.removeInstance(this);
+ };
+
+ this.withRegistration = function() {
+ this.after('initialize', function() {
+ registry.addInstance(this);
+ });
+
+ this.around('on', registry.on);
+ this.after('off', registry.off);
+ //debug tools may want to add advice to trigger
+ window.DEBUG && DEBUG.enabled && this.after('trigger', registry.trigger);
+ this.after('teardown', {obj: registry, fnName: 'teardown'});
+ };
+
+ }
+
+ return new Registry;
+ }
+);
--- /dev/null
+// ==========================================
+// Copyright 2013 Twitter, Inc
+// Licensed under The MIT License
+// http://opensource.org/licenses/MIT
+// ==========================================
+
+define(
+
+ [],
+
+ function() {
+ 'use strict';
+
+ var arry = [];
+ var DEFAULT_INTERVAL = 100;
+
+ var utils = {
+
+ isDomObj: function(obj) {
+ return !!(obj.nodeType || (obj === window));
+ },
+
+ toArray: function(obj, from) {
+ return arry.slice.call(obj, from);
+ },
+
+ // returns new object representing multiple objects merged together
+ // optional final argument is boolean which specifies if merge is recursive
+ // original objects are unmodified
+ //
+ // usage:
+ // var base = {a:2, b:6};
+ // var extra = {b:3, c:4};
+ // merge(base, extra); //{a:2, b:3, c:4}
+ // base; //{a:2, b:6}
+ //
+ // var base = {a:2, b:6};
+ // var extra = {b:3, c:4};
+ // var extraExtra = {a:4, d:9};
+ // merge(base, extra, extraExtra); //{a:4, b:3, c:4. d: 9}
+ // base; //{a:2, b:6}
+ //
+ // var base = {a:2, b:{bb:4, cc:5}};
+ // var extra = {a:4, b:{cc:7, dd:1}};
+ // merge(base, extra, true); //{a:4, b:{bb:4, cc:7, dd:1}}
+ // base; //{a:2, b:6}
+
+ merge: function(/*obj1, obj2,....deepCopy*/) {
+ // unpacking arguments by hand benchmarked faster
+ var l = arguments.length,
+ i = 0,
+ args = new Array(l + 1);
+ for (; i < l; i++) args[i + 1] = arguments[i];
+
+ if (l === 0) {
+ return {};
+ }
+
+ //start with empty object so a copy is created
+ args[0] = {};
+
+ if (args[args.length - 1] === true) {
+ //jquery extend requires deep copy as first arg
+ args.pop();
+ args.unshift(true);
+ }
+
+ return $.extend.apply(undefined, args);
+ },
+
+ // updates base in place by copying properties of extra to it
+ // optionally clobber protected
+ // usage:
+ // var base = {a:2, b:6};
+ // var extra = {c:4};
+ // push(base, extra); //{a:2, b:6, c:4}
+ // base; //{a:2, b:6, c:4}
+ //
+ // var base = {a:2, b:6};
+ // var extra = {b: 4 c:4};
+ // push(base, extra, true); //Error ("utils.push attempted to overwrite 'b' while running in protected mode")
+ // base; //{a:2, b:6}
+ //
+ // objects with the same key will merge recursively when protect is false
+ // eg:
+ // var base = {a:16, b:{bb:4, cc:10}};
+ // var extra = {b:{cc:25, dd:19}, c:5};
+ // push(base, extra); //{a:16, {bb:4, cc:25, dd:19}, c:5}
+ //
+ push: function(base, extra, protect) {
+ if (base) {
+ Object.keys(extra || {}).forEach(function(key) {
+ if (base[key] && protect) {
+ throw new Error('utils.push attempted to overwrite "' + key + '" while running in protected mode');
+ }
+
+ if (typeof base[key] == 'object' && typeof extra[key] == 'object') {
+ // recurse
+ this.push(base[key], extra[key]);
+ } else {
+ // no protect, so extra wins
+ base[key] = extra[key];
+ }
+ }, this);
+ }
+
+ return base;
+ },
+
+ isEnumerable: function(obj, property) {
+ return Object.keys(obj).indexOf(property) > -1;
+ },
+
+ // build a function from other function(s)
+ // utils.compose(a,b,c) -> a(b(c()));
+ // implementation lifted from underscore.js (c) 2009-2012 Jeremy Ashkenas
+ compose: function() {
+ var funcs = arguments;
+
+ return function() {
+ var args = arguments;
+
+ for (var i = funcs.length-1; i >= 0; i--) {
+ args = [funcs[i].apply(this, args)];
+ }
+
+ return args[0];
+ };
+ },
+
+ // Can only unique arrays of homogeneous primitives, e.g. an array of only strings, an array of only booleans, or an array of only numerics
+ uniqueArray: function(array) {
+ var u = {}, a = [];
+
+ for (var i = 0, l = array.length; i < l; ++i) {
+ if (u.hasOwnProperty(array[i])) {
+ continue;
+ }
+
+ a.push(array[i]);
+ u[array[i]] = 1;
+ }
+
+ return a;
+ },
+
+ debounce: function(func, wait, immediate) {
+ if (typeof wait != 'number') {
+ wait = DEFAULT_INTERVAL;
+ }
+
+ var timeout, result;
+
+ return function() {
+ var context = this, args = arguments;
+ var later = function() {
+ timeout = null;
+ if (!immediate) {
+ result = func.apply(context, args);
+ }
+ };
+ var callNow = immediate && !timeout;
+
+ clearTimeout(timeout);
+ timeout = setTimeout(later, wait);
+
+ if (callNow) {
+ result = func.apply(context, args);
+ }
+
+ return result;
+ };
+ },
+
+ throttle: function(func, wait) {
+ if (typeof wait != 'number') {
+ wait = DEFAULT_INTERVAL;
+ }
+
+ var context, args, timeout, throttling, more, result;
+ var whenDone = this.debounce(function(){
+ more = throttling = false;
+ }, wait);
+
+ return function() {
+ context = this; args = arguments;
+ var later = function() {
+ timeout = null;
+ if (more) {
+ result = func.apply(context, args);
+ }
+ whenDone();
+ };
+
+ if (!timeout) {
+ timeout = setTimeout(later, wait);
+ }
+
+ if (throttling) {
+ more = true;
+ } else {
+ throttling = true;
+ result = func.apply(context, args);
+ }
+
+ whenDone();
+ return result;
+ };
+ },
+
+ countThen: function(num, base) {
+ return function() {
+ if (!--num) { return base.apply(this, arguments); }
+ };
+ },
+
+ delegate: function(rules) {
+ return function(e, data) {
+ var target = $(e.target), parent;
+
+ Object.keys(rules).forEach(function(selector) {
+ if ((parent = target.closest(selector)).length) {
+ data = data || {};
+ data.el = parent[0];
+ return rules[selector].apply(this, [e, data]);
+ }
+ }, this);
+ };
+ }
+
+ };
+
+ return utils;
+ }
+);
--- /dev/null
+/*!
+ * jQuery JavaScript Library v1.8.3
+ * http://jquery.com/
+ *
+ * Includes Sizzle.js
+ * http://sizzlejs.com/
+ *
+ * Copyright 2012 jQuery Foundation and other contributors
+ * Released under the MIT license
+ * http://jquery.org/license
+ *
+ * Date: Tue Nov 13 2012 08:20:33 GMT-0500 (Eastern Standard Time)
+ */
+(function( window, undefined ) {
+var
+ // A central reference to the root jQuery(document)
+ rootjQuery,
+
+ // The deferred used on DOM ready
+ readyList,
+
+ // Use the correct document accordingly with window argument (sandbox)
+ document = window.document,
+ location = window.location,
+ navigator = window.navigator,
+
+ // Map over jQuery in case of overwrite
+ _jQuery = window.jQuery,
+
+ // Map over the $ in case of overwrite
+ _$ = window.$,
+
+ // Save a reference to some core methods
+ core_push = Array.prototype.push,
+ core_slice = Array.prototype.slice,
+ core_indexOf = Array.prototype.indexOf,
+ core_toString = Object.prototype.toString,
+ core_hasOwn = Object.prototype.hasOwnProperty,
+ core_trim = String.prototype.trim,
+
+ // Define a local copy of jQuery
+ jQuery = function( selector, context ) {
+ // The jQuery object is actually just the init constructor 'enhanced'
+ return new jQuery.fn.init( selector, context, rootjQuery );
+ },
+
+ // Used for matching numbers
+ core_pnum = /[\-+]?(?:\d*\.|)\d+(?:[eE][\-+]?\d+|)/.source,
+
+ // Used for detecting and trimming whitespace
+ core_rnotwhite = /\S/,
+ core_rspace = /\s+/,
+
+ // Make sure we trim BOM and NBSP (here's looking at you, Safari 5.0 and IE)
+ rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,
+
+ // A simple way to check for HTML strings
+ // Prioritize #id over <tag> to avoid XSS via location.hash (#9521)
+ rquickExpr = /^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,
+
+ // Match a standalone tag
+ rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>|)$/,
+
+ // JSON RegExp
+ rvalidchars = /^[\],:{}\s]*$/,
+ rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g,
+ rvalidescape = /\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,
+ rvalidtokens = /"[^"\\\r\n]*"|true|false|null|-?(?:\d\d*\.|)\d+(?:[eE][\-+]?\d+|)/g,
+
+ // Matches dashed string for camelizing
+ rmsPrefix = /^-ms-/,
+ rdashAlpha = /-([\da-z])/gi,
+
+ // Used by jQuery.camelCase as callback to replace()
+ fcamelCase = function( all, letter ) {
+ return ( letter + "" ).toUpperCase();
+ },
+
+ // The ready event handler and self cleanup method
+ DOMContentLoaded = function() {
+ if ( document.addEventListener ) {
+ document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false );
+ jQuery.ready();
+ } else if ( document.readyState === "complete" ) {
+ // we're here because readyState === "complete" in oldIE
+ // which is good enough for us to call the dom ready!
+ document.detachEvent( "onreadystatechange", DOMContentLoaded );
+ jQuery.ready();
+ }
+ },
+
+ // [[Class]] -> type pairs
+ class2type = {};
+
+jQuery.fn = jQuery.prototype = {
+ constructor: jQuery,
+ init: function( selector, context, rootjQuery ) {
+ var match, elem, ret, doc;
+
+ // Handle $(""), $(null), $(undefined), $(false)
+ if ( !selector ) {
+ return this;
+ }
+
+ // Handle $(DOMElement)
+ if ( selector.nodeType ) {
+ this.context = this[0] = selector;
+ this.length = 1;
+ return this;
+ }
+
+ // Handle HTML strings
+ if ( typeof selector === "string" ) {
+ if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) {
+ // Assume that strings that start and end with <> are HTML and skip the regex check
+ match = [ null, selector, null ];
+
+ } else {
+ match = rquickExpr.exec( selector );
+ }
+
+ // Match html or make sure no context is specified for #id
+ if ( match && (match[1] || !context) ) {
+
+ // HANDLE: $(html) -> $(array)
+ if ( match[1] ) {
+ context = context instanceof jQuery ? context[0] : context;
+ doc = ( context && context.nodeType ? context.ownerDocument || context : document );
+
+ // scripts is true for back-compat
+ selector = jQuery.parseHTML( match[1], doc, true );
+ if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) {
+ this.attr.call( selector, context, true );
+ }
+
+ return jQuery.merge( this, selector );
+
+ // HANDLE: $(#id)
+ } else {
+ elem = document.getElementById( match[2] );
+
+ // Check parentNode to catch when Blackberry 4.6 returns
+ // nodes that are no longer in the document #6963
+ if ( elem && elem.parentNode ) {
+ // Handle the case where IE and Opera return items
+ // by name instead of ID
+ if ( elem.id !== match[2] ) {
+ return rootjQuery.find( selector );
+ }
+
+ // Otherwise, we inject the element directly into the jQuery object
+ this.length = 1;
+ this[0] = elem;
+ }
+
+ this.context = document;
+ this.selector = selector;
+ return this;
+ }
+
+ // HANDLE: $(expr, $(...))
+ } else if ( !context || context.jquery ) {
+ return ( context || rootjQuery ).find( selector );
+
+ // HANDLE: $(expr, context)
+ // (which is just equivalent to: $(context).find(expr)
+ } else {
+ return this.constructor( context ).find( selector );
+ }
+
+ // HANDLE: $(function)
+ // Shortcut for document ready
+ } else if ( jQuery.isFunction( selector ) ) {
+ return rootjQuery.ready( selector );
+ }
+
+ if ( selector.selector !== undefined ) {
+ this.selector = selector.selector;
+ this.context = selector.context;
+ }
+
+ return jQuery.makeArray( selector, this );
+ },
+
+ // Start with an empty selector
+ selector: "",
+
+ // The current version of jQuery being used
+ jquery: "1.8.3",
+
+ // The default length of a jQuery object is 0
+ length: 0,
+
+ // The number of elements contained in the matched element set
+ size: function() {
+ return this.length;
+ },
+
+ toArray: function() {
+ return core_slice.call( this );
+ },
+
+ // Get the Nth element in the matched element set OR
+ // Get the whole matched element set as a clean array
+ get: function( num ) {
+ return num == null ?
+
+ // Return a 'clean' array
+ this.toArray() :
+
+ // Return just the object
+ ( num < 0 ? this[ this.length + num ] : this[ num ] );
+ },
+
+ // Take an array of elements and push it onto the stack
+ // (returning the new matched element set)
+ pushStack: function( elems, name, selector ) {
+
+ // Build a new jQuery matched element set
+ var ret = jQuery.merge( this.constructor(), elems );
+
+ // Add the old object onto the stack (as a reference)
+ ret.prevObject = this;
+
+ ret.context = this.context;
+
+ if ( name === "find" ) {
+ ret.selector = this.selector + ( this.selector ? " " : "" ) + selector;
+ } else if ( name ) {
+ ret.selector = this.selector + "." + name + "(" + selector + ")";
+ }
+
+ // Return the newly-formed element set
+ return ret;
+ },
+
+ // Execute a callback for every element in the matched set.
+ // (You can seed the arguments with an array of args, but this is
+ // only used internally.)
+ each: function( callback, args ) {
+ return jQuery.each( this, callback, args );
+ },
+
+ ready: function( fn ) {
+ // Add the callback
+ jQuery.ready.promise().done( fn );
+
+ return this;
+ },
+
+ eq: function( i ) {
+ i = +i;
+ return i === -1 ?
+ this.slice( i ) :
+ this.slice( i, i + 1 );
+ },
+
+ first: function() {
+ return this.eq( 0 );
+ },
+
+ last: function() {
+ return this.eq( -1 );
+ },
+
+ slice: function() {
+ return this.pushStack( core_slice.apply( this, arguments ),
+ "slice", core_slice.call(arguments).join(",") );
+ },
+
+ map: function( callback ) {
+ return this.pushStack( jQuery.map(this, function( elem, i ) {
+ return callback.call( elem, i, elem );
+ }));
+ },
+
+ end: function() {
+ return this.prevObject || this.constructor(null);
+ },
+
+ // For internal use only.
+ // Behaves like an Array's method, not like a jQuery method.
+ push: core_push,
+ sort: [].sort,
+ splice: [].splice
+};
+
+// Give the init function the jQuery prototype for later instantiation
+jQuery.fn.init.prototype = jQuery.fn;
+
+jQuery.extend = jQuery.fn.extend = function() {
+ var options, name, src, copy, copyIsArray, clone,
+ target = arguments[0] || {},
+ i = 1,
+ length = arguments.length,
+ deep = false;
+
+ // Handle a deep copy situation
+ if ( typeof target === "boolean" ) {
+ deep = target;
+ target = arguments[1] || {};
+ // skip the boolean and the target
+ i = 2;
+ }
+
+ // Handle case when target is a string or something (possible in deep copy)
+ if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
+ target = {};
+ }
+
+ // extend jQuery itself if only one argument is passed
+ if ( length === i ) {
+ target = this;
+ --i;
+ }
+
+ for ( ; i < length; i++ ) {
+ // Only deal with non-null/undefined values
+ if ( (options = arguments[ i ]) != null ) {
+ // Extend the base object
+ for ( name in options ) {
+ src = target[ name ];
+ copy = options[ name ];
+
+ // Prevent never-ending loop
+ if ( target === copy ) {
+ continue;
+ }
+
+ // Recurse if we're merging plain objects or arrays
+ if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
+ if ( copyIsArray ) {
+ copyIsArray = false;
+ clone = src && jQuery.isArray(src) ? src : [];
+
+ } else {
+ clone = src && jQuery.isPlainObject(src) ? src : {};
+ }
+
+ // Never move original objects, clone them
+ target[ name ] = jQuery.extend( deep, clone, copy );
+
+ // Don't bring in undefined values
+ } else if ( copy !== undefined ) {
+ target[ name ] = copy;
+ }
+ }
+ }
+ }
+
+ // Return the modified object
+ return target;
+};
+
+jQuery.extend({
+ noConflict: function( deep ) {
+ if ( window.$ === jQuery ) {
+ window.$ = _$;
+ }
+
+ if ( deep && window.jQuery === jQuery ) {
+ window.jQuery = _jQuery;
+ }
+
+ return jQuery;
+ },
+
+ // Is the DOM ready to be used? Set to true once it occurs.
+ isReady: false,
+
+ // A counter to track how many items to wait for before
+ // the ready event fires. See #6781
+ readyWait: 1,
+
+ // Hold (or release) the ready event
+ holdReady: function( hold ) {
+ if ( hold ) {
+ jQuery.readyWait++;
+ } else {
+ jQuery.ready( true );
+ }
+ },
+
+ // Handle when the DOM is ready
+ ready: function( wait ) {
+
+ // Abort if there are pending holds or we're already ready
+ if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) {
+ return;
+ }
+
+ // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
+ if ( !document.body ) {
+ return setTimeout( jQuery.ready, 1 );
+ }
+
+ // Remember that the DOM is ready
+ jQuery.isReady = true;
+
+ // If a normal DOM Ready event fired, decrement, and wait if need be
+ if ( wait !== true && --jQuery.readyWait > 0 ) {
+ return;
+ }
+
+ // If there are functions bound, to execute
+ readyList.resolveWith( document, [ jQuery ] );
+
+ // Trigger any bound ready events
+ if ( jQuery.fn.trigger ) {
+ jQuery( document ).trigger("ready").off("ready");
+ }
+ },
+
+ // See test/unit/core.js for details concerning isFunction.
+ // Since version 1.3, DOM methods and functions like alert
+ // aren't supported. They return false on IE (#2968).
+ isFunction: function( obj ) {
+ return jQuery.type(obj) === "function";
+ },
+
+ isArray: Array.isArray || function( obj ) {
+ return jQuery.type(obj) === "array";
+ },
+
+ isWindow: function( obj ) {
+ return obj != null && obj == obj.window;
+ },
+
+ isNumeric: function( obj ) {
+ return !isNaN( parseFloat(obj) ) && isFinite( obj );
+ },
+
+ type: function( obj ) {
+ return obj == null ?
+ String( obj ) :
+ class2type[ core_toString.call(obj) ] || "object";
+ },
+
+ isPlainObject: function( obj ) {
+ // Must be an Object.
+ // Because of IE, we also have to check the presence of the constructor property.
+ // Make sure that DOM nodes and window objects don't pass through, as well
+ if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) {
+ return false;
+ }
+
+ try {
+ // Not own constructor property must be Object
+ if ( obj.constructor &&
+ !core_hasOwn.call(obj, "constructor") &&
+ !core_hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) {
+ return false;
+ }
+ } catch ( e ) {
+ // IE8,9 Will throw exceptions on certain host objects #9897
+ return false;
+ }
+
+ // Own properties are enumerated firstly, so to speed up,
+ // if last one is own, then all properties are own.
+
+ var key;
+ for ( key in obj ) {}
+
+ return key === undefined || core_hasOwn.call( obj, key );
+ },
+
+ isEmptyObject: function( obj ) {
+ var name;
+ for ( name in obj ) {
+ return false;
+ }
+ return true;
+ },
+
+ error: function( msg ) {
+ throw new Error( msg );
+ },
+
+ // data: string of html
+ // context (optional): If specified, the fragment will be created in this context, defaults to document
+ // scripts (optional): If true, will include scripts passed in the html string
+ parseHTML: function( data, context, scripts ) {
+ var parsed;
+ if ( !data || typeof data !== "string" ) {
+ return null;
+ }
+ if ( typeof context === "boolean" ) {
+ scripts = context;
+ context = 0;
+ }
+ context = context || document;
+
+ // Single tag
+ if ( (parsed = rsingleTag.exec( data )) ) {
+ return [ context.createElement( parsed[1] ) ];
+ }
+
+ parsed = jQuery.buildFragment( [ data ], context, scripts ? null : [] );
+ return jQuery.merge( [],
+ (parsed.cacheable ? jQuery.clone( parsed.fragment ) : parsed.fragment).childNodes );
+ },
+
+ parseJSON: function( data ) {
+ if ( !data || typeof data !== "string") {
+ return null;
+ }
+
+ // Make sure leading/trailing whitespace is removed (IE can't handle it)
+ data = jQuery.trim( data );
+
+ // Attempt to parse using the native JSON parser first
+ if ( window.JSON && window.JSON.parse ) {
+ return window.JSON.parse( data );
+ }
+
+ // Make sure the incoming data is actual JSON
+ // Logic borrowed from http://json.org/json2.js
+ if ( rvalidchars.test( data.replace( rvalidescape, "@" )
+ .replace( rvalidtokens, "]" )
+ .replace( rvalidbraces, "")) ) {
+
+ return ( new Function( "return " + data ) )();
+
+ }
+ jQuery.error( "Invalid JSON: " + data );
+ },
+
+ // Cross-browser xml parsing
+ parseXML: function( data ) {
+ var xml, tmp;
+ if ( !data || typeof data !== "string" ) {
+ return null;
+ }
+ try {
+ if ( window.DOMParser ) { // Standard
+ tmp = new DOMParser();
+ xml = tmp.parseFromString( data , "text/xml" );
+ } else { // IE
+ xml = new ActiveXObject( "Microsoft.XMLDOM" );
+ xml.async = "false";
+ xml.loadXML( data );
+ }
+ } catch( e ) {
+ xml = undefined;
+ }
+ if ( !xml || !xml.documentElement || xml.getElementsByTagName( "parsererror" ).length ) {
+ jQuery.error( "Invalid XML: " + data );
+ }
+ return xml;
+ },
+
+ noop: function() {},
+
+ // Evaluates a script in a global context
+ // Workarounds based on findings by Jim Driscoll
+ // http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context
+ globalEval: function( data ) {
+ if ( data && core_rnotwhite.test( data ) ) {
+ // We use execScript on Internet Explorer
+ // We use an anonymous function so that context is window
+ // rather than jQuery in Firefox
+ ( window.execScript || function( data ) {
+ window[ "eval" ].call( window, data );
+ } )( data );
+ }
+ },
+
+ // Convert dashed to camelCase; used by the css and data modules
+ // Microsoft forgot to hump their vendor prefix (#9572)
+ camelCase: function( string ) {
+ return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );
+ },
+
+ nodeName: function( elem, name ) {
+ return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase();
+ },
+
+ // args is for internal usage only
+ each: function( obj, callback, args ) {
+ var name,
+ i = 0,
+ length = obj.length,
+ isObj = length === undefined || jQuery.isFunction( obj );
+
+ if ( args ) {
+ if ( isObj ) {
+ for ( name in obj ) {
+ if ( callback.apply( obj[ name ], args ) === false ) {
+ break;
+ }
+ }
+ } else {
+ for ( ; i < length; ) {
+ if ( callback.apply( obj[ i++ ], args ) === false ) {
+ break;
+ }
+ }
+ }
+
+ // A special, fast, case for the most common use of each
+ } else {
+ if ( isObj ) {
+ for ( name in obj ) {
+ if ( callback.call( obj[ name ], name, obj[ name ] ) === false ) {
+ break;
+ }
+ }
+ } else {
+ for ( ; i < length; ) {
+ if ( callback.call( obj[ i ], i, obj[ i++ ] ) === false ) {
+ break;
+ }
+ }
+ }
+ }
+
+ return obj;
+ },
+
+ // Use native String.trim function wherever possible
+ trim: core_trim && !core_trim.call("\uFEFF\xA0") ?
+ function( text ) {
+ return text == null ?
+ "" :
+ core_trim.call( text );
+ } :
+
+ // Otherwise use our own trimming functionality
+ function( text ) {
+ return text == null ?
+ "" :
+ ( text + "" ).replace( rtrim, "" );
+ },
+
+ // results is for internal usage only
+ makeArray: function( arr, results ) {
+ var type,
+ ret = results || [];
+
+ if ( arr != null ) {
+ // The window, strings (and functions) also have 'length'
+ // Tweaked logic slightly to handle Blackberry 4.7 RegExp issues #6930
+ type = jQuery.type( arr );
+
+ if ( arr.length == null || type === "string" || type === "function" || type === "regexp" || jQuery.isWindow( arr ) ) {
+ core_push.call( ret, arr );
+ } else {
+ jQuery.merge( ret, arr );
+ }
+ }
+
+ return ret;
+ },
+
+ inArray: function( elem, arr, i ) {
+ var len;
+
+ if ( arr ) {
+ if ( core_indexOf ) {
+ return core_indexOf.call( arr, elem, i );
+ }
+
+ len = arr.length;
+ i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0;
+
+ for ( ; i < len; i++ ) {
+ // Skip accessing in sparse arrays
+ if ( i in arr && arr[ i ] === elem ) {
+ return i;
+ }
+ }
+ }
+
+ return -1;
+ },
+
+ merge: function( first, second ) {
+ var l = second.length,
+ i = first.length,
+ j = 0;
+
+ if ( typeof l === "number" ) {
+ for ( ; j < l; j++ ) {
+ first[ i++ ] = second[ j ];
+ }
+
+ } else {
+ while ( second[j] !== undefined ) {
+ first[ i++ ] = second[ j++ ];
+ }
+ }
+
+ first.length = i;
+
+ return first;
+ },
+
+ grep: function( elems, callback, inv ) {
+ var retVal,
+ ret = [],
+ i = 0,
+ length = elems.length;
+ inv = !!inv;
+
+ // Go through the array, only saving the items
+ // that pass the validator function
+ for ( ; i < length; i++ ) {
+ retVal = !!callback( elems[ i ], i );
+ if ( inv !== retVal ) {
+ ret.push( elems[ i ] );
+ }
+ }
+
+ return ret;
+ },
+
+ // arg is for internal usage only
+ map: function( elems, callback, arg ) {
+ var value, key,
+ ret = [],
+ i = 0,
+ length = elems.length,
+ // jquery objects are treated as arrays
+ isArray = elems instanceof jQuery || length !== undefined && typeof length === "number" && ( ( length > 0 && elems[ 0 ] && elems[ length -1 ] ) || length === 0 || jQuery.isArray( elems ) ) ;
+
+ // Go through the array, translating each of the items to their
+ if ( isArray ) {
+ for ( ; i < length; i++ ) {
+ value = callback( elems[ i ], i, arg );
+
+ if ( value != null ) {
+ ret[ ret.length ] = value;
+ }
+ }
+
+ // Go through every key on the object,
+ } else {
+ for ( key in elems ) {
+ value = callback( elems[ key ], key, arg );
+
+ if ( value != null ) {
+ ret[ ret.length ] = value;
+ }
+ }
+ }
+
+ // Flatten any nested arrays
+ return ret.concat.apply( [], ret );
+ },
+
+ // A global GUID counter for objects
+ guid: 1,
+
+ // Bind a function to a context, optionally partially applying any
+ // arguments.
+ proxy: function( fn, context ) {
+ var tmp, args, proxy;
+
+ if ( typeof context === "string" ) {
+ tmp = fn[ context ];
+ context = fn;
+ fn = tmp;
+ }
+
+ // Quick check to determine if target is callable, in the spec
+ // this throws a TypeError, but we will just return undefined.
+ if ( !jQuery.isFunction( fn ) ) {
+ return undefined;
+ }
+
+ // Simulated bind
+ args = core_slice.call( arguments, 2 );
+ proxy = function() {
+ return fn.apply( context, args.concat( core_slice.call( arguments ) ) );
+ };
+
+ // Set the guid of unique handler to the same of original handler, so it can be removed
+ proxy.guid = fn.guid = fn.guid || jQuery.guid++;
+
+ return proxy;
+ },
+
+ // Multifunctional method to get and set values of a collection
+ // The value/s can optionally be executed if it's a function
+ access: function( elems, fn, key, value, chainable, emptyGet, pass ) {
+ var exec,
+ bulk = key == null,
+ i = 0,
+ length = elems.length;
+
+ // Sets many values
+ if ( key && typeof key === "object" ) {
+ for ( i in key ) {
+ jQuery.access( elems, fn, i, key[i], 1, emptyGet, value );
+ }
+ chainable = 1;
+
+ // Sets one value
+ } else if ( value !== undefined ) {
+ // Optionally, function values get executed if exec is true
+ exec = pass === undefined && jQuery.isFunction( value );
+
+ if ( bulk ) {
+ // Bulk operations only iterate when executing function values
+ if ( exec ) {
+ exec = fn;
+ fn = function( elem, key, value ) {
+ return exec.call( jQuery( elem ), value );
+ };
+
+ // Otherwise they run against the entire set
+ } else {
+ fn.call( elems, value );
+ fn = null;
+ }
+ }
+
+ if ( fn ) {
+ for (; i < length; i++ ) {
+ fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass );
+ }
+ }
+
+ chainable = 1;
+ }
+
+ return chainable ?
+ elems :
+
+ // Gets
+ bulk ?
+ fn.call( elems ) :
+ length ? fn( elems[0], key ) : emptyGet;
+ },
+
+ now: function() {
+ return ( new Date() ).getTime();
+ }
+});
+
+jQuery.ready.promise = function( obj ) {
+ if ( !readyList ) {
+
+ readyList = jQuery.Deferred();
+
+ // Catch cases where $(document).ready() is called after the browser event has already occurred.
+ // we once tried to use readyState "interactive" here, but it caused issues like the one
+ // discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15
+ if ( document.readyState === "complete" ) {
+ // Handle it asynchronously to allow scripts the opportunity to delay ready
+ setTimeout( jQuery.ready, 1 );
+
+ // Standards-based browsers support DOMContentLoaded
+ } else if ( document.addEventListener ) {
+ // Use the handy event callback
+ document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false );
+
+ // A fallback to window.onload, that will always work
+ window.addEventListener( "load", jQuery.ready, false );
+
+ // If IE event model is used
+ } else {
+ // Ensure firing before onload, maybe late but safe also for iframes
+ document.attachEvent( "onreadystatechange", DOMContentLoaded );
+
+ // A fallback to window.onload, that will always work
+ window.attachEvent( "onload", jQuery.ready );
+
+ // If IE and not a frame
+ // continually check to see if the document is ready
+ var top = false;
+
+ try {
+ top = window.frameElement == null && document.documentElement;
+ } catch(e) {}
+
+ if ( top && top.doScroll ) {
+ (function doScrollCheck() {
+ if ( !jQuery.isReady ) {
+
+ try {
+ // Use the trick by Diego Perini
+ // http://javascript.nwbox.com/IEContentLoaded/
+ top.doScroll("left");
+ } catch(e) {
+ return setTimeout( doScrollCheck, 50 );
+ }
+
+ // and execute any waiting functions
+ jQuery.ready();
+ }
+ })();
+ }
+ }
+ }
+ return readyList.promise( obj );
+};
+
+// Populate the class2type map
+jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "), function(i, name) {
+ class2type[ "[object " + name + "]" ] = name.toLowerCase();
+});
+
+// All jQuery objects should point back to these
+rootjQuery = jQuery(document);
+// String to Object options format cache
+var optionsCache = {};
+
+// Convert String-formatted options into Object-formatted ones and store in cache
+function createOptions( options ) {
+ var object = optionsCache[ options ] = {};
+ jQuery.each( options.split( core_rspace ), function( _, flag ) {
+ object[ flag ] = true;
+ });
+ return object;
+}
+
+/*
+ * Create a callback list using the following parameters:
+ *
+ * options: an optional list of space-separated options that will change how
+ * the callback list behaves or a more traditional option object
+ *
+ * By default a callback list will act like an event callback list and can be
+ * "fired" multiple times.
+ *
+ * Possible options:
+ *
+ * once: will ensure the callback list can only be fired once (like a Deferred)
+ *
+ * memory: will keep track of previous values and will call any callback added
+ * after the list has been fired right away with the latest "memorized"
+ * values (like a Deferred)
+ *
+ * unique: will ensure a callback can only be added once (no duplicate in the list)
+ *
+ * stopOnFalse: interrupt callings when a callback returns false
+ *
+ */
+jQuery.Callbacks = function( options ) {
+
+ // Convert options from String-formatted to Object-formatted if needed
+ // (we check in cache first)
+ options = typeof options === "string" ?
+ ( optionsCache[ options ] || createOptions( options ) ) :
+ jQuery.extend( {}, options );
+
+ var // Last fire value (for non-forgettable lists)
+ memory,
+ // Flag to know if list was already fired
+ fired,
+ // Flag to know if list is currently firing
+ firing,
+ // First callback to fire (used internally by add and fireWith)
+ firingStart,
+ // End of the loop when firing
+ firingLength,
+ // Index of currently firing callback (modified by remove if needed)
+ firingIndex,
+ // Actual callback list
+ list = [],
+ // Stack of fire calls for repeatable lists
+ stack = !options.once && [],
+ // Fire callbacks
+ fire = function( data ) {
+ memory = options.memory && data;
+ fired = true;
+ firingIndex = firingStart || 0;
+ firingStart = 0;
+ firingLength = list.length;
+ firing = true;
+ for ( ; list && firingIndex < firingLength; firingIndex++ ) {
+ if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {
+ memory = false; // To prevent further calls using add
+ break;
+ }
+ }
+ firing = false;
+ if ( list ) {
+ if ( stack ) {
+ if ( stack.length ) {
+ fire( stack.shift() );
+ }
+ } else if ( memory ) {
+ list = [];
+ } else {
+ self.disable();
+ }
+ }
+ },
+ // Actual Callbacks object
+ self = {
+ // Add a callback or a collection of callbacks to the list
+ add: function() {
+ if ( list ) {
+ // First, we save the current length
+ var start = list.length;
+ (function add( args ) {
+ jQuery.each( args, function( _, arg ) {
+ var type = jQuery.type( arg );
+ if ( type === "function" ) {
+ if ( !options.unique || !self.has( arg ) ) {
+ list.push( arg );
+ }
+ } else if ( arg && arg.length && type !== "string" ) {
+ // Inspect recursively
+ add( arg );
+ }
+ });
+ })( arguments );
+ // Do we need to add the callbacks to the
+ // current firing batch?
+ if ( firing ) {
+ firingLength = list.length;
+ // With memory, if we're not firing then
+ // we should call right away
+ } else if ( memory ) {
+ firingStart = start;
+ fire( memory );
+ }
+ }
+ return this;
+ },
+ // Remove a callback from the list
+ remove: function() {
+ if ( list ) {
+ jQuery.each( arguments, function( _, arg ) {
+ var index;
+ while( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
+ list.splice( index, 1 );
+ // Handle firing indexes
+ if ( firing ) {
+ if ( index <= firingLength ) {
+ firingLength--;
+ }
+ if ( index <= firingIndex ) {
+ firingIndex--;
+ }
+ }
+ }
+ });
+ }
+ return this;
+ },
+ // Control if a given callback is in the list
+ has: function( fn ) {
+ return jQuery.inArray( fn, list ) > -1;
+ },
+ // Remove all callbacks from the list
+ empty: function() {
+ list = [];
+ return this;
+ },
+ // Have the list do nothing anymore
+ disable: function() {
+ list = stack = memory = undefined;
+ return this;
+ },
+ // Is it disabled?
+ disabled: function() {
+ return !list;
+ },
+ // Lock the list in its current state
+ lock: function() {
+ stack = undefined;
+ if ( !memory ) {
+ self.disable();
+ }
+ return this;
+ },
+ // Is it locked?
+ locked: function() {
+ return !stack;
+ },
+ // Call all callbacks with the given context and arguments
+ fireWith: function( context, args ) {
+ args = args || [];
+ args = [ context, args.slice ? args.slice() : args ];
+ if ( list && ( !fired || stack ) ) {
+ if ( firing ) {
+ stack.push( args );
+ } else {
+ fire( args );
+ }
+ }
+ return this;
+ },
+ // Call all the callbacks with the given arguments
+ fire: function() {
+ self.fireWith( this, arguments );
+ return this;
+ },
+ // To know if the callbacks have already been called at least once
+ fired: function() {
+ return !!fired;
+ }
+ };
+
+ return self;
+};
+jQuery.extend({
+
+ Deferred: function( func ) {
+ var tuples = [
+ // action, add listener, listener list, final state
+ [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ],
+ [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ],
+ [ "notify", "progress", jQuery.Callbacks("memory") ]
+ ],
+ state = "pending",
+ promise = {
+ state: function() {
+ return state;
+ },
+ always: function() {
+ deferred.done( arguments ).fail( arguments );
+ return this;
+ },
+ then: function( /* fnDone, fnFail, fnProgress */ ) {
+ var fns = arguments;
+ return jQuery.Deferred(function( newDefer ) {
+ jQuery.each( tuples, function( i, tuple ) {
+ var action = tuple[ 0 ],
+ fn = fns[ i ];
+ // deferred[ done | fail | progress ] for forwarding actions to newDefer
+ deferred[ tuple[1] ]( jQuery.isFunction( fn ) ?
+ function() {
+ var returned = fn.apply( this, arguments );
+ if ( returned && jQuery.isFunction( returned.promise ) ) {
+ returned.promise()
+ .done( newDefer.resolve )
+ .fail( newDefer.reject )
+ .progress( newDefer.notify );
+ } else {
+ newDefer[ action + "With" ]( this === deferred ? newDefer : this, [ returned ] );
+ }
+ } :
+ newDefer[ action ]
+ );
+ });
+ fns = null;
+ }).promise();
+ },
+ // Get a promise for this deferred
+ // If obj is provided, the promise aspect is added to the object
+ promise: function( obj ) {
+ return obj != null ? jQuery.extend( obj, promise ) : promise;
+ }
+ },
+ deferred = {};
+
+ // Keep pipe for back-compat
+ promise.pipe = promise.then;
+
+ // Add list-specific methods
+ jQuery.each( tuples, function( i, tuple ) {
+ var list = tuple[ 2 ],
+ stateString = tuple[ 3 ];
+
+ // promise[ done | fail | progress ] = list.add
+ promise[ tuple[1] ] = list.add;
+
+ // Handle state
+ if ( stateString ) {
+ list.add(function() {
+ // state = [ resolved | rejected ]
+ state = stateString;
+
+ // [ reject_list | resolve_list ].disable; progress_list.lock
+ }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );
+ }
+
+ // deferred[ resolve | reject | notify ] = list.fire
+ deferred[ tuple[0] ] = list.fire;
+ deferred[ tuple[0] + "With" ] = list.fireWith;
+ });
+
+ // Make the deferred a promise
+ promise.promise( deferred );
+
+ // Call given func if any
+ if ( func ) {
+ func.call( deferred, deferred );
+ }
+
+ // All done!
+ return deferred;
+ },
+
+ // Deferred helper
+ when: function( subordinate /* , ..., subordinateN */ ) {
+ var i = 0,
+ resolveValues = core_slice.call( arguments ),
+ length = resolveValues.length,
+
+ // the count of uncompleted subordinates
+ remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0,
+
+ // the master Deferred. If resolveValues consist of only a single Deferred, just use that.
+ deferred = remaining === 1 ? subordinate : jQuery.Deferred(),
+
+ // Update function for both resolve and progress values
+ updateFunc = function( i, contexts, values ) {
+ return function( value ) {
+ contexts[ i ] = this;
+ values[ i ] = arguments.length > 1 ? core_slice.call( arguments ) : value;
+ if( values === progressValues ) {
+ deferred.notifyWith( contexts, values );
+ } else if ( !( --remaining ) ) {
+ deferred.resolveWith( contexts, values );
+ }
+ };
+ },
+
+ progressValues, progressContexts, resolveContexts;
+
+ // add listeners to Deferred subordinates; treat others as resolved
+ if ( length > 1 ) {
+ progressValues = new Array( length );
+ progressContexts = new Array( length );
+ resolveContexts = new Array( length );
+ for ( ; i < length; i++ ) {
+ if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) {
+ resolveValues[ i ].promise()
+ .done( updateFunc( i, resolveContexts, resolveValues ) )
+ .fail( deferred.reject )
+ .progress( updateFunc( i, progressContexts, progressValues ) );
+ } else {
+ --remaining;
+ }
+ }
+ }
+
+ // if we're not waiting on anything, resolve the master
+ if ( !remaining ) {
+ deferred.resolveWith( resolveContexts, resolveValues );
+ }
+
+ return deferred.promise();
+ }
+});
+jQuery.support = (function() {
+
+ var support,
+ all,
+ a,
+ select,
+ opt,
+ input,
+ fragment,
+ eventName,
+ i,
+ isSupported,
+ clickFn,
+ div = document.createElement("div");
+
+ // Setup
+ div.setAttribute( "className", "t" );
+ div.innerHTML = " <link/><table></table><a href='/a'>a</a><input type='checkbox'/>";
+
+ // Support tests won't run in some limited or non-browser environments
+ all = div.getElementsByTagName("*");
+ a = div.getElementsByTagName("a")[ 0 ];
+ if ( !all || !a || !all.length ) {
+ return {};
+ }
+
+ // First batch of tests
+ select = document.createElement("select");
+ opt = select.appendChild( document.createElement("option") );
+ input = div.getElementsByTagName("input")[ 0 ];
+
+ a.style.cssText = "top:1px;float:left;opacity:.5";
+ support = {
+ // IE strips leading whitespace when .innerHTML is used
+ leadingWhitespace: ( div.firstChild.nodeType === 3 ),
+
+ // Make sure that tbody elements aren't automatically inserted
+ // IE will insert them into empty tables
+ tbody: !div.getElementsByTagName("tbody").length,
+
+ // Make sure that link elements get serialized correctly by innerHTML
+ // This requires a wrapper element in IE
+ htmlSerialize: !!div.getElementsByTagName("link").length,
+
+ // Get the style information from getAttribute
+ // (IE uses .cssText instead)
+ style: /top/.test( a.getAttribute("style") ),
+
+ // Make sure that URLs aren't manipulated
+ // (IE normalizes it by default)
+ hrefNormalized: ( a.getAttribute("href") === "/a" ),
+
+ // Make sure that element opacity exists
+ // (IE uses filter instead)
+ // Use a regex to work around a WebKit issue. See #5145
+ opacity: /^0.5/.test( a.style.opacity ),
+
+ // Verify style float existence
+ // (IE uses styleFloat instead of cssFloat)
+ cssFloat: !!a.style.cssFloat,
+
+ // Make sure that if no value is specified for a checkbox
+ // that it defaults to "on".
+ // (WebKit defaults to "" instead)
+ checkOn: ( input.value === "on" ),
+
+ // Make sure that a selected-by-default option has a working selected property.
+ // (WebKit defaults to false instead of true, IE too, if it's in an optgroup)
+ optSelected: opt.selected,
+
+ // Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7)
+ getSetAttribute: div.className !== "t",
+
+ // Tests for enctype support on a form (#6743)
+ enctype: !!document.createElement("form").enctype,
+
+ // Makes sure cloning an html5 element does not cause problems
+ // Where outerHTML is undefined, this still works
+ html5Clone: document.createElement("nav").cloneNode( true ).outerHTML !== "<:nav></:nav>",
+
+ // jQuery.support.boxModel DEPRECATED in 1.8 since we don't support Quirks Mode
+ boxModel: ( document.compatMode === "CSS1Compat" ),
+
+ // Will be defined later
+ submitBubbles: true,
+ changeBubbles: true,
+ focusinBubbles: false,
+ deleteExpando: true,
+ noCloneEvent: true,
+ inlineBlockNeedsLayout: false,
+ shrinkWrapBlocks: false,
+ reliableMarginRight: true,
+ boxSizingReliable: true,
+ pixelPosition: false
+ };
+
+ // Make sure checked status is properly cloned
+ input.checked = true;
+ support.noCloneChecked = input.cloneNode( true ).checked;
+
+ // Make sure that the options inside disabled selects aren't marked as disabled
+ // (WebKit marks them as disabled)
+ select.disabled = true;
+ support.optDisabled = !opt.disabled;
+
+ // Test to see if it's possible to delete an expando from an element
+ // Fails in Internet Explorer
+ try {
+ delete div.test;
+ } catch( e ) {
+ support.deleteExpando = false;
+ }
+
+ if ( !div.addEventListener && div.attachEvent && div.fireEvent ) {
+ div.attachEvent( "onclick", clickFn = function() {
+ // Cloning a node shouldn't copy over any
+ // bound event handlers (IE does this)
+ support.noCloneEvent = false;
+ });
+ div.cloneNode( true ).fireEvent("onclick");
+ div.detachEvent( "onclick", clickFn );
+ }
+
+ // Check if a radio maintains its value
+ // after being appended to the DOM
+ input = document.createElement("input");
+ input.value = "t";
+ input.setAttribute( "type", "radio" );
+ support.radioValue = input.value === "t";
+
+ input.setAttribute( "checked", "checked" );
+
+ // #11217 - WebKit loses check when the name is after the checked attribute
+ input.setAttribute( "name", "t" );
+
+ div.appendChild( input );
+ fragment = document.createDocumentFragment();
+ fragment.appendChild( div.lastChild );
+
+ // WebKit doesn't clone checked state correctly in fragments
+ support.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked;
+
+ // Check if a disconnected checkbox will retain its checked
+ // value of true after appended to the DOM (IE6/7)
+ support.appendChecked = input.checked;
+
+ fragment.removeChild( input );
+ fragment.appendChild( div );
+
+ // Technique from Juriy Zaytsev
+ // http://perfectionkills.com/detecting-event-support-without-browser-sniffing/
+ // We only care about the case where non-standard event systems
+ // are used, namely in IE. Short-circuiting here helps us to
+ // avoid an eval call (in setAttribute) which can cause CSP
+ // to go haywire. See: https://developer.mozilla.org/en/Security/CSP
+ if ( div.attachEvent ) {
+ for ( i in {
+ submit: true,
+ change: true,
+ focusin: true
+ }) {
+ eventName = "on" + i;
+ isSupported = ( eventName in div );
+ if ( !isSupported ) {
+ div.setAttribute( eventName, "return;" );
+ isSupported = ( typeof div[ eventName ] === "function" );
+ }
+ support[ i + "Bubbles" ] = isSupported;
+ }
+ }
+
+ // Run tests that need a body at doc ready
+ jQuery(function() {
+ var container, div, tds, marginDiv,
+ divReset = "padding:0;margin:0;border:0;display:block;overflow:hidden;",
+ body = document.getElementsByTagName("body")[0];
+
+ if ( !body ) {
+ // Return for frameset docs that don't have a body
+ return;
+ }
+
+ container = document.createElement("div");
+ container.style.cssText = "visibility:hidden;border:0;width:0;height:0;position:static;top:0;margin-top:1px";
+ body.insertBefore( container, body.firstChild );
+
+ // Construct the test element
+ div = document.createElement("div");
+ container.appendChild( div );
+
+ // Check if table cells still have offsetWidth/Height when they are set
+ // to display:none and there are still other visible table cells in a
+ // table row; if so, offsetWidth/Height are not reliable for use when
+ // determining if an element has been hidden directly using
+ // display:none (it is still safe to use offsets if a parent element is
+ // hidden; don safety goggles and see bug #4512 for more information).
+ // (only IE 8 fails this test)
+ div.innerHTML = "<table><tr><td></td><td>t</td></tr></table>";
+ tds = div.getElementsByTagName("td");
+ tds[ 0 ].style.cssText = "padding:0;margin:0;border:0;display:none";
+ isSupported = ( tds[ 0 ].offsetHeight === 0 );
+
+ tds[ 0 ].style.display = "";
+ tds[ 1 ].style.display = "none";
+
+ // Check if empty table cells still have offsetWidth/Height
+ // (IE <= 8 fail this test)
+ support.reliableHiddenOffsets = isSupported && ( tds[ 0 ].offsetHeight === 0 );
+
+ // Check box-sizing and margin behavior
+ div.innerHTML = "";
+ div.style.cssText = "box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;";
+ support.boxSizing = ( div.offsetWidth === 4 );
+ support.doesNotIncludeMarginInBodyOffset = ( body.offsetTop !== 1 );
+
+ // NOTE: To any future maintainer, we've window.getComputedStyle
+ // because jsdom on node.js will break without it.
+ if ( window.getComputedStyle ) {
+ support.pixelPosition = ( window.getComputedStyle( div, null ) || {} ).top !== "1%";
+ support.boxSizingReliable = ( window.getComputedStyle( div, null ) || { width: "4px" } ).width === "4px";
+
+ // Check if div with explicit width and no margin-right incorrectly
+ // gets computed margin-right based on width of container. For more
+ // info see bug #3333
+ // Fails in WebKit before Feb 2011 nightlies
+ // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
+ marginDiv = document.createElement("div");
+ marginDiv.style.cssText = div.style.cssText = divReset;
+ marginDiv.style.marginRight = marginDiv.style.width = "0";
+ div.style.width = "1px";
+ div.appendChild( marginDiv );
+ support.reliableMarginRight =
+ !parseFloat( ( window.getComputedStyle( marginDiv, null ) || {} ).marginRight );
+ }
+
+ if ( typeof div.style.zoom !== "undefined" ) {
+ // Check if natively block-level elements act like inline-block
+ // elements when setting their display to 'inline' and giving
+ // them layout
+ // (IE < 8 does this)
+ div.innerHTML = "";
+ div.style.cssText = divReset + "width:1px;padding:1px;display:inline;zoom:1";
+ support.inlineBlockNeedsLayout = ( div.offsetWidth === 3 );
+
+ // Check if elements with layout shrink-wrap their children
+ // (IE 6 does this)
+ div.style.display = "block";
+ div.style.overflow = "visible";
+ div.innerHTML = "<div></div>";
+ div.firstChild.style.width = "5px";
+ support.shrinkWrapBlocks = ( div.offsetWidth !== 3 );
+
+ container.style.zoom = 1;
+ }
+
+ // Null elements to avoid leaks in IE
+ body.removeChild( container );
+ container = div = tds = marginDiv = null;
+ });
+
+ // Null elements to avoid leaks in IE
+ fragment.removeChild( div );
+ all = a = select = opt = input = fragment = div = null;
+
+ return support;
+})();
+var rbrace = /(?:\{[\s\S]*\}|\[[\s\S]*\])$/,
+ rmultiDash = /([A-Z])/g;
+
+jQuery.extend({
+ cache: {},
+
+ deletedIds: [],
+
+ // Remove at next major release (1.9/2.0)
+ uuid: 0,
+
+ // Unique for each copy of jQuery on the page
+ // Non-digits removed to match rinlinejQuery
+ expando: "jQuery" + ( jQuery.fn.jquery + Math.random() ).replace( /\D/g, "" ),
+
+ // The following elements throw uncatchable exceptions if you
+ // attempt to add expando properties to them.
+ noData: {
+ "embed": true,
+ // Ban all objects except for Flash (which handle expandos)
+ "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",
+ "applet": true
+ },
+
+ hasData: function( elem ) {
+ elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ];
+ return !!elem && !isEmptyDataObject( elem );
+ },
+
+ data: function( elem, name, data, pvt /* Internal Use Only */ ) {
+ if ( !jQuery.acceptData( elem ) ) {
+ return;
+ }
+
+ var thisCache, ret,
+ internalKey = jQuery.expando,
+ getByName = typeof name === "string",
+
+ // We have to handle DOM nodes and JS objects differently because IE6-7
+ // can't GC object references properly across the DOM-JS boundary
+ isNode = elem.nodeType,
+
+ // Only DOM nodes need the global jQuery cache; JS object data is
+ // attached directly to the object so GC can occur automatically
+ cache = isNode ? jQuery.cache : elem,
+
+ // Only defining an ID for JS objects if its cache already exists allows
+ // the code to shortcut on the same path as a DOM node with no cache
+ id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey;
+
+ // Avoid doing any more work than we need to when trying to get data on an
+ // object that has no data at all
+ if ( (!id || !cache[id] || (!pvt && !cache[id].data)) && getByName && data === undefined ) {
+ return;
+ }
+
+ if ( !id ) {
+ // Only DOM nodes need a new unique ID for each element since their data
+ // ends up in the global cache
+ if ( isNode ) {
+ elem[ internalKey ] = id = jQuery.deletedIds.pop() || jQuery.guid++;
+ } else {
+ id = internalKey;
+ }
+ }
+
+ if ( !cache[ id ] ) {
+ cache[ id ] = {};
+
+ // Avoids exposing jQuery metadata on plain JS objects when the object
+ // is serialized using JSON.stringify
+ if ( !isNode ) {
+ cache[ id ].toJSON = jQuery.noop;
+ }
+ }
+
+ // An object can be passed to jQuery.data instead of a key/value pair; this gets
+ // shallow copied over onto the existing cache
+ if ( typeof name === "object" || typeof name === "function" ) {
+ if ( pvt ) {
+ cache[ id ] = jQuery.extend( cache[ id ], name );
+ } else {
+ cache[ id ].data = jQuery.extend( cache[ id ].data, name );
+ }
+ }
+
+ thisCache = cache[ id ];
+
+ // jQuery data() is stored in a separate object inside the object's internal data
+ // cache in order to avoid key collisions between internal data and user-defined
+ // data.
+ if ( !pvt ) {
+ if ( !thisCache.data ) {
+ thisCache.data = {};
+ }
+
+ thisCache = thisCache.data;
+ }
+
+ if ( data !== undefined ) {
+ thisCache[ jQuery.camelCase( name ) ] = data;
+ }
+
+ // Check for both converted-to-camel and non-converted data property names
+ // If a data property was specified
+ if ( getByName ) {
+
+ // First Try to find as-is property data
+ ret = thisCache[ name ];
+
+ // Test for null|undefined property data
+ if ( ret == null ) {
+
+ // Try to find the camelCased property
+ ret = thisCache[ jQuery.camelCase( name ) ];
+ }
+ } else {
+ ret = thisCache;
+ }
+
+ return ret;
+ },
+
+ removeData: function( elem, name, pvt /* Internal Use Only */ ) {
+ if ( !jQuery.acceptData( elem ) ) {
+ return;
+ }
+
+ var thisCache, i, l,
+
+ isNode = elem.nodeType,
+
+ // See jQuery.data for more information
+ cache = isNode ? jQuery.cache : elem,
+ id = isNode ? elem[ jQuery.expando ] : jQuery.expando;
+
+ // If there is already no cache entry for this object, there is no
+ // purpose in continuing
+ if ( !cache[ id ] ) {
+ return;
+ }
+
+ if ( name ) {
+
+ thisCache = pvt ? cache[ id ] : cache[ id ].data;
+
+ if ( thisCache ) {
+
+ // Support array or space separated string names for data keys
+ if ( !jQuery.isArray( name ) ) {
+
+ // try the string as a key before any manipulation
+ if ( name in thisCache ) {
+ name = [ name ];
+ } else {
+
+ // split the camel cased version by spaces unless a key with the spaces exists
+ name = jQuery.camelCase( name );
+ if ( name in thisCache ) {
+ name = [ name ];
+ } else {
+ name = name.split(" ");
+ }
+ }
+ }
+
+ for ( i = 0, l = name.length; i < l; i++ ) {
+ delete thisCache[ name[i] ];
+ }
+
+ // If there is no data left in the cache, we want to continue
+ // and let the cache object itself get destroyed
+ if ( !( pvt ? isEmptyDataObject : jQuery.isEmptyObject )( thisCache ) ) {
+ return;
+ }
+ }
+ }
+
+ // See jQuery.data for more information
+ if ( !pvt ) {
+ delete cache[ id ].data;
+
+ // Don't destroy the parent cache unless the internal data object
+ // had been the only thing left in it
+ if ( !isEmptyDataObject( cache[ id ] ) ) {
+ return;
+ }
+ }
+
+ // Destroy the cache
+ if ( isNode ) {
+ jQuery.cleanData( [ elem ], true );
+
+ // Use delete when supported for expandos or `cache` is not a window per isWindow (#10080)
+ } else if ( jQuery.support.deleteExpando || cache != cache.window ) {
+ delete cache[ id ];
+
+ // When all else fails, null
+ } else {
+ cache[ id ] = null;
+ }
+ },
+
+ // For internal use only.
+ _data: function( elem, name, data ) {
+ return jQuery.data( elem, name, data, true );
+ },
+
+ // A method for determining if a DOM node can handle the data expando
+ acceptData: function( elem ) {
+ var noData = elem.nodeName && jQuery.noData[ elem.nodeName.toLowerCase() ];
+
+ // nodes accept data unless otherwise specified; rejection can be conditional
+ return !noData || noData !== true && elem.getAttribute("classid") === noData;
+ }
+});
+
+jQuery.fn.extend({
+ data: function( key, value ) {
+ var parts, part, attr, name, l,
+ elem = this[0],
+ i = 0,
+ data = null;
+
+ // Gets all values
+ if ( key === undefined ) {
+ if ( this.length ) {
+ data = jQuery.data( elem );
+
+ if ( elem.nodeType === 1 && !jQuery._data( elem, "parsedAttrs" ) ) {
+ attr = elem.attributes;
+ for ( l = attr.length; i < l; i++ ) {
+ name = attr[i].name;
+
+ if ( !name.indexOf( "data-" ) ) {
+ name = jQuery.camelCase( name.substring(5) );
+
+ dataAttr( elem, name, data[ name ] );
+ }
+ }
+ jQuery._data( elem, "parsedAttrs", true );
+ }
+ }
+
+ return data;
+ }
+
+ // Sets multiple values
+ if ( typeof key === "object" ) {
+ return this.each(function() {
+ jQuery.data( this, key );
+ });
+ }
+
+ parts = key.split( ".", 2 );
+ parts[1] = parts[1] ? "." + parts[1] : "";
+ part = parts[1] + "!";
+
+ return jQuery.access( this, function( value ) {
+
+ if ( value === undefined ) {
+ data = this.triggerHandler( "getData" + part, [ parts[0] ] );
+
+ // Try to fetch any internally stored data first
+ if ( data === undefined && elem ) {
+ data = jQuery.data( elem, key );
+ data = dataAttr( elem, key, data );
+ }
+
+ return data === undefined && parts[1] ?
+ this.data( parts[0] ) :
+ data;
+ }
+
+ parts[1] = value;
+ this.each(function() {
+ var self = jQuery( this );
+
+ self.triggerHandler( "setData" + part, parts );
+ jQuery.data( this, key, value );
+ self.triggerHandler( "changeData" + part, parts );
+ });
+ }, null, value, arguments.length > 1, null, false );
+ },
+
+ removeData: function( key ) {
+ return this.each(function() {
+ jQuery.removeData( this, key );
+ });
+ }
+});
+
+function dataAttr( elem, key, data ) {
+ // If nothing was found internally, try to fetch any
+ // data from the HTML5 data-* attribute
+ if ( data === undefined && elem.nodeType === 1 ) {
+
+ var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase();
+
+ data = elem.getAttribute( name );
+
+ if ( typeof data === "string" ) {
+ try {
+ data = data === "true" ? true :
+ data === "false" ? false :
+ data === "null" ? null :
+ // Only convert to a number if it doesn't change the string
+ +data + "" === data ? +data :
+ rbrace.test( data ) ? jQuery.parseJSON( data ) :
+ data;
+ } catch( e ) {}
+
+ // Make sure we set the data so it isn't changed later
+ jQuery.data( elem, key, data );
+
+ } else {
+ data = undefined;
+ }
+ }
+
+ return data;
+}
+
+// checks a cache object for emptiness
+function isEmptyDataObject( obj ) {
+ var name;
+ for ( name in obj ) {
+
+ // if the public data object is empty, the private is still empty
+ if ( name === "data" && jQuery.isEmptyObject( obj[name] ) ) {
+ continue;
+ }
+ if ( name !== "toJSON" ) {
+ return false;
+ }
+ }
+
+ return true;
+}
+jQuery.extend({
+ queue: function( elem, type, data ) {
+ var queue;
+
+ if ( elem ) {
+ type = ( type || "fx" ) + "queue";
+ queue = jQuery._data( elem, type );
+
+ // Speed up dequeue by getting out quickly if this is just a lookup
+ if ( data ) {
+ if ( !queue || jQuery.isArray(data) ) {
+ queue = jQuery._data( elem, type, jQuery.makeArray(data) );
+ } else {
+ queue.push( data );
+ }
+ }
+ return queue || [];
+ }
+ },
+
+ dequeue: function( elem, type ) {
+ type = type || "fx";
+
+ var queue = jQuery.queue( elem, type ),
+ startLength = queue.length,
+ fn = queue.shift(),
+ hooks = jQuery._queueHooks( elem, type ),
+ next = function() {
+ jQuery.dequeue( elem, type );
+ };
+
+ // If the fx queue is dequeued, always remove the progress sentinel
+ if ( fn === "inprogress" ) {
+ fn = queue.shift();
+ startLength--;
+ }
+
+ if ( fn ) {
+
+ // Add a progress sentinel to prevent the fx queue from being
+ // automatically dequeued
+ if ( type === "fx" ) {
+ queue.unshift( "inprogress" );
+ }
+
+ // clear up the last queue stop function
+ delete hooks.stop;
+ fn.call( elem, next, hooks );
+ }
+
+ if ( !startLength && hooks ) {
+ hooks.empty.fire();
+ }
+ },
+
+ // not intended for public consumption - generates a queueHooks object, or returns the current one
+ _queueHooks: function( elem, type ) {
+ var key = type + "queueHooks";
+ return jQuery._data( elem, key ) || jQuery._data( elem, key, {
+ empty: jQuery.Callbacks("once memory").add(function() {
+ jQuery.removeData( elem, type + "queue", true );
+ jQuery.removeData( elem, key, true );
+ })
+ });
+ }
+});
+
+jQuery.fn.extend({
+ queue: function( type, data ) {
+ var setter = 2;
+
+ if ( typeof type !== "string" ) {
+ data = type;
+ type = "fx";
+ setter--;
+ }
+
+ if ( arguments.length < setter ) {
+ return jQuery.queue( this[0], type );
+ }
+
+ return data === undefined ?
+ this :
+ this.each(function() {
+ var queue = jQuery.queue( this, type, data );
+
+ // ensure a hooks for this queue
+ jQuery._queueHooks( this, type );
+
+ if ( type === "fx" && queue[0] !== "inprogress" ) {
+ jQuery.dequeue( this, type );
+ }
+ });
+ },
+ dequeue: function( type ) {
+ return this.each(function() {
+ jQuery.dequeue( this, type );
+ });
+ },
+ // Based off of the plugin by Clint Helfers, with permission.
+ // http://blindsignals.com/index.php/2009/07/jquery-delay/
+ delay: function( time, type ) {
+ time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time;
+ type = type || "fx";
+
+ return this.queue( type, function( next, hooks ) {
+ var timeout = setTimeout( next, time );
+ hooks.stop = function() {
+ clearTimeout( timeout );
+ };
+ });
+ },
+ clearQueue: function( type ) {
+ return this.queue( type || "fx", [] );
+ },
+ // Get a promise resolved when queues of a certain type
+ // are emptied (fx is the type by default)
+ promise: function( type, obj ) {
+ var tmp,
+ count = 1,
+ defer = jQuery.Deferred(),
+ elements = this,
+ i = this.length,
+ resolve = function() {
+ if ( !( --count ) ) {
+ defer.resolveWith( elements, [ elements ] );
+ }
+ };
+
+ if ( typeof type !== "string" ) {
+ obj = type;
+ type = undefined;
+ }
+ type = type || "fx";
+
+ while( i-- ) {
+ tmp = jQuery._data( elements[ i ], type + "queueHooks" );
+ if ( tmp && tmp.empty ) {
+ count++;
+ tmp.empty.add( resolve );
+ }
+ }
+ resolve();
+ return defer.promise( obj );
+ }
+});
+var nodeHook, boolHook, fixSpecified,
+ rclass = /[\t\r\n]/g,
+ rreturn = /\r/g,
+ rtype = /^(?:button|input)$/i,
+ rfocusable = /^(?:button|input|object|select|textarea)$/i,
+ rclickable = /^a(?:rea|)$/i,
+ rboolean = /^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,
+ getSetAttribute = jQuery.support.getSetAttribute;
+
+jQuery.fn.extend({
+ attr: function( name, value ) {
+ return jQuery.access( this, jQuery.attr, name, value, arguments.length > 1 );
+ },
+
+ removeAttr: function( name ) {
+ return this.each(function() {
+ jQuery.removeAttr( this, name );
+ });
+ },
+
+ prop: function( name, value ) {
+ return jQuery.access( this, jQuery.prop, name, value, arguments.length > 1 );
+ },
+
+ removeProp: function( name ) {
+ name = jQuery.propFix[ name ] || name;
+ return this.each(function() {
+ // try/catch handles cases where IE balks (such as removing a property on window)
+ try {
+ this[ name ] = undefined;
+ delete this[ name ];
+ } catch( e ) {}
+ });
+ },
+
+ addClass: function( value ) {
+ var classNames, i, l, elem,
+ setClass, c, cl;
+
+ if ( jQuery.isFunction( value ) ) {
+ return this.each(function( j ) {
+ jQuery( this ).addClass( value.call(this, j, this.className) );
+ });
+ }
+
+ if ( value && typeof value === "string" ) {
+ classNames = value.split( core_rspace );
+
+ for ( i = 0, l = this.length; i < l; i++ ) {
+ elem = this[ i ];
+
+ if ( elem.nodeType === 1 ) {
+ if ( !elem.className && classNames.length === 1 ) {
+ elem.className = value;
+
+ } else {
+ setClass = " " + elem.className + " ";
+
+ for ( c = 0, cl = classNames.length; c < cl; c++ ) {
+ if ( setClass.indexOf( " " + classNames[ c ] + " " ) < 0 ) {
+ setClass += classNames[ c ] + " ";
+ }
+ }
+ elem.className = jQuery.trim( setClass );
+ }
+ }
+ }
+ }
+
+ return this;
+ },
+
+ removeClass: function( value ) {
+ var removes, className, elem, c, cl, i, l;
+
+ if ( jQuery.isFunction( value ) ) {
+ return this.each(function( j ) {
+ jQuery( this ).removeClass( value.call(this, j, this.className) );
+ });
+ }
+ if ( (value && typeof value === "string") || value === undefined ) {
+ removes = ( value || "" ).split( core_rspace );
+
+ for ( i = 0, l = this.length; i < l; i++ ) {
+ elem = this[ i ];
+ if ( elem.nodeType === 1 && elem.className ) {
+
+ className = (" " + elem.className + " ").replace( rclass, " " );
+
+ // loop over each item in the removal list
+ for ( c = 0, cl = removes.length; c < cl; c++ ) {
+ // Remove until there is nothing to remove,
+ while ( className.indexOf(" " + removes[ c ] + " ") >= 0 ) {
+ className = className.replace( " " + removes[ c ] + " " , " " );
+ }
+ }
+ elem.className = value ? jQuery.trim( className ) : "";
+ }
+ }
+ }
+
+ return this;
+ },
+
+ toggleClass: function( value, stateVal ) {
+ var type = typeof value,
+ isBool = typeof stateVal === "boolean";
+
+ if ( jQuery.isFunction( value ) ) {
+ return this.each(function( i ) {
+ jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal );
+ });
+ }
+
+ return this.each(function() {
+ if ( type === "string" ) {
+ // toggle individual class names
+ var className,
+ i = 0,
+ self = jQuery( this ),
+ state = stateVal,
+ classNames = value.split( core_rspace );
+
+ while ( (className = classNames[ i++ ]) ) {
+ // check each className given, space separated list
+ state = isBool ? state : !self.hasClass( className );
+ self[ state ? "addClass" : "removeClass" ]( className );
+ }
+
+ } else if ( type === "undefined" || type === "boolean" ) {
+ if ( this.className ) {
+ // store className if set
+ jQuery._data( this, "__className__", this.className );
+ }
+
+ // toggle whole className
+ this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || "";
+ }
+ });
+ },
+
+ hasClass: function( selector ) {
+ var className = " " + selector + " ",
+ i = 0,
+ l = this.length;
+ for ( ; i < l; i++ ) {
+ if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) >= 0 ) {
+ return true;
+ }
+ }
+
+ return false;
+ },
+
+ val: function( value ) {
+ var hooks, ret, isFunction,
+ elem = this[0];
+
+ if ( !arguments.length ) {
+ if ( elem ) {
+ hooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ];
+
+ if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) {
+ return ret;
+ }
+
+ ret = elem.value;
+
+ return typeof ret === "string" ?
+ // handle most common string cases
+ ret.replace(rreturn, "") :
+ // handle cases where value is null/undef or number
+ ret == null ? "" : ret;
+ }
+
+ return;
+ }
+
+ isFunction = jQuery.isFunction( value );
+
+ return this.each(function( i ) {
+ var val,
+ self = jQuery(this);
+
+ if ( this.nodeType !== 1 ) {
+ return;
+ }
+
+ if ( isFunction ) {
+ val = value.call( this, i, self.val() );
+ } else {
+ val = value;
+ }
+
+ // Treat null/undefined as ""; convert numbers to string
+ if ( val == null ) {
+ val = "";
+ } else if ( typeof val === "number" ) {
+ val += "";
+ } else if ( jQuery.isArray( val ) ) {
+ val = jQuery.map(val, function ( value ) {
+ return value == null ? "" : value + "";
+ });
+ }
+
+ hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ];
+
+ // If set returns undefined, fall back to normal setting
+ if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) {
+ this.value = val;
+ }
+ });
+ }
+});
+
+jQuery.extend({
+ valHooks: {
+ option: {
+ get: function( elem ) {
+ // attributes.value is undefined in Blackberry 4.7 but
+ // uses .value. See #6932
+ var val = elem.attributes.value;
+ return !val || val.specified ? elem.value : elem.text;
+ }
+ },
+ select: {
+ get: function( elem ) {
+ var value, option,
+ options = elem.options,
+ index = elem.selectedIndex,
+ one = elem.type === "select-one" || index < 0,
+ values = one ? null : [],
+ max = one ? index + 1 : options.length,
+ i = index < 0 ?
+ max :
+ one ? index : 0;
+
+ // Loop through all the selected options
+ for ( ; i < max; i++ ) {
+ option = options[ i ];
+
+ // oldIE doesn't update selected after form reset (#2551)
+ if ( ( option.selected || i === index ) &&
+ // Don't return options that are disabled or in a disabled optgroup
+ ( jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null ) &&
+ ( !option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" ) ) ) {
+
+ // Get the specific value for the option
+ value = jQuery( option ).val();
+
+ // We don't need an array for one selects
+ if ( one ) {
+ return value;
+ }
+
+ // Multi-Selects return an array
+ values.push( value );
+ }
+ }
+
+ return values;
+ },
+
+ set: function( elem, value ) {
+ var values = jQuery.makeArray( value );
+
+ jQuery(elem).find("option").each(function() {
+ this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0;
+ });
+
+ if ( !values.length ) {
+ elem.selectedIndex = -1;
+ }
+ return values;
+ }
+ }
+ },
+
+ // Unused in 1.8, left in so attrFn-stabbers won't die; remove in 1.9
+ attrFn: {},
+
+ attr: function( elem, name, value, pass ) {
+ var ret, hooks, notxml,
+ nType = elem.nodeType;
+
+ // don't get/set attributes on text, comment and attribute nodes
+ if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
+ return;
+ }
+
+ if ( pass && jQuery.isFunction( jQuery.fn[ name ] ) ) {
+ return jQuery( elem )[ name ]( value );
+ }
+
+ // Fallback to prop when attributes are not supported
+ if ( typeof elem.getAttribute === "undefined" ) {
+ return jQuery.prop( elem, name, value );
+ }
+
+ notxml = nType !== 1 || !jQuery.isXMLDoc( elem );
+
+ // All attributes are lowercase
+ // Grab necessary hook if one is defined
+ if ( notxml ) {
+ name = name.toLowerCase();
+ hooks = jQuery.attrHooks[ name ] || ( rboolean.test( name ) ? boolHook : nodeHook );
+ }
+
+ if ( value !== undefined ) {
+
+ if ( value === null ) {
+ jQuery.removeAttr( elem, name );
+ return;
+
+ } else if ( hooks && "set" in hooks && notxml && (ret = hooks.set( elem, value, name )) !== undefined ) {
+ return ret;
+
+ } else {
+ elem.setAttribute( name, value + "" );
+ return value;
+ }
+
+ } else if ( hooks && "get" in hooks && notxml && (ret = hooks.get( elem, name )) !== null ) {
+ return ret;
+
+ } else {
+
+ ret = elem.getAttribute( name );
+
+ // Non-existent attributes return null, we normalize to undefined
+ return ret === null ?
+ undefined :
+ ret;
+ }
+ },
+
+ removeAttr: function( elem, value ) {
+ var propName, attrNames, name, isBool,
+ i = 0;
+
+ if ( value && elem.nodeType === 1 ) {
+
+ attrNames = value.split( core_rspace );
+
+ for ( ; i < attrNames.length; i++ ) {
+ name = attrNames[ i ];
+
+ if ( name ) {
+ propName = jQuery.propFix[ name ] || name;
+ isBool = rboolean.test( name );
+
+ // See #9699 for explanation of this approach (setting first, then removal)
+ // Do not do this for boolean attributes (see #10870)
+ if ( !isBool ) {
+ jQuery.attr( elem, name, "" );
+ }
+ elem.removeAttribute( getSetAttribute ? name : propName );
+
+ // Set corresponding property to false for boolean attributes
+ if ( isBool && propName in elem ) {
+ elem[ propName ] = false;
+ }
+ }
+ }
+ }
+ },
+
+ attrHooks: {
+ type: {
+ set: function( elem, value ) {
+ // We can't allow the type property to be changed (since it causes problems in IE)
+ if ( rtype.test( elem.nodeName ) && elem.parentNode ) {
+ jQuery.error( "type property can't be changed" );
+ } else if ( !jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input") ) {
+ // Setting the type on a radio button after the value resets the value in IE6-9
+ // Reset value to it's default in case type is set after value
+ // This is for element creation
+ var val = elem.value;
+ elem.setAttribute( "type", value );
+ if ( val ) {
+ elem.value = val;
+ }
+ return value;
+ }
+ }
+ },
+ // Use the value property for back compat
+ // Use the nodeHook for button elements in IE6/7 (#1954)
+ value: {
+ get: function( elem, name ) {
+ if ( nodeHook && jQuery.nodeName( elem, "button" ) ) {
+ return nodeHook.get( elem, name );
+ }
+ return name in elem ?
+ elem.value :
+ null;
+ },
+ set: function( elem, value, name ) {
+ if ( nodeHook && jQuery.nodeName( elem, "button" ) ) {
+ return nodeHook.set( elem, value, name );
+ }
+ // Does not return so that setAttribute is also used
+ elem.value = value;
+ }
+ }
+ },
+
+ propFix: {
+ tabindex: "tabIndex",
+ readonly: "readOnly",
+ "for": "htmlFor",
+ "class": "className",
+ maxlength: "maxLength",
+ cellspacing: "cellSpacing",
+ cellpadding: "cellPadding",
+ rowspan: "rowSpan",
+ colspan: "colSpan",
+ usemap: "useMap",
+ frameborder: "frameBorder",
+ contenteditable: "contentEditable"
+ },
+
+ prop: function( elem, name, value ) {
+ var ret, hooks, notxml,
+ nType = elem.nodeType;
+
+ // don't get/set properties on text, comment and attribute nodes
+ if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
+ return;
+ }
+
+ notxml = nType !== 1 || !jQuery.isXMLDoc( elem );
+
+ if ( notxml ) {
+ // Fix name and attach hooks
+ name = jQuery.propFix[ name ] || name;
+ hooks = jQuery.propHooks[ name ];
+ }
+
+ if ( value !== undefined ) {
+ if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) {
+ return ret;
+
+ } else {
+ return ( elem[ name ] = value );
+ }
+
+ } else {
+ if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) {
+ return ret;
+
+ } else {
+ return elem[ name ];
+ }
+ }
+ },
+
+ propHooks: {
+ tabIndex: {
+ get: function( elem ) {
+ // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set
+ // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
+ var attributeNode = elem.getAttributeNode("tabindex");
+
+ return attributeNode && attributeNode.specified ?
+ parseInt( attributeNode.value, 10 ) :
+ rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ?
+ 0 :
+ undefined;
+ }
+ }
+ }
+});
+
+// Hook for boolean attributes
+boolHook = {
+ get: function( elem, name ) {
+ // Align boolean attributes with corresponding properties
+ // Fall back to attribute presence where some booleans are not supported
+ var attrNode,
+ property = jQuery.prop( elem, name );
+ return property === true || typeof property !== "boolean" && ( attrNode = elem.getAttributeNode(name) ) && attrNode.nodeValue !== false ?
+ name.toLowerCase() :
+ undefined;
+ },
+ set: function( elem, value, name ) {
+ var propName;
+ if ( value === false ) {
+ // Remove boolean attributes when set to false
+ jQuery.removeAttr( elem, name );
+ } else {
+ // value is true since we know at this point it's type boolean and not false
+ // Set boolean attributes to the same name and set the DOM property
+ propName = jQuery.propFix[ name ] || name;
+ if ( propName in elem ) {
+ // Only set the IDL specifically if it already exists on the element
+ elem[ propName ] = true;
+ }
+
+ elem.setAttribute( name, name.toLowerCase() );
+ }
+ return name;
+ }
+};
+
+// IE6/7 do not support getting/setting some attributes with get/setAttribute
+if ( !getSetAttribute ) {
+
+ fixSpecified = {
+ name: true,
+ id: true,
+ coords: true
+ };
+
+ // Use this for any attribute in IE6/7
+ // This fixes almost every IE6/7 issue
+ nodeHook = jQuery.valHooks.button = {
+ get: function( elem, name ) {
+ var ret;
+ ret = elem.getAttributeNode( name );
+ return ret && ( fixSpecified[ name ] ? ret.value !== "" : ret.specified ) ?
+ ret.value :
+ undefined;
+ },
+ set: function( elem, value, name ) {
+ // Set the existing or create a new attribute node
+ var ret = elem.getAttributeNode( name );
+ if ( !ret ) {
+ ret = document.createAttribute( name );
+ elem.setAttributeNode( ret );
+ }
+ return ( ret.value = value + "" );
+ }
+ };
+
+ // Set width and height to auto instead of 0 on empty string( Bug #8150 )
+ // This is for removals
+ jQuery.each([ "width", "height" ], function( i, name ) {
+ jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], {
+ set: function( elem, value ) {
+ if ( value === "" ) {
+ elem.setAttribute( name, "auto" );
+ return value;
+ }
+ }
+ });
+ });
+
+ // Set contenteditable to false on removals(#10429)
+ // Setting to empty string throws an error as an invalid value
+ jQuery.attrHooks.contenteditable = {
+ get: nodeHook.get,
+ set: function( elem, value, name ) {
+ if ( value === "" ) {
+ value = "false";
+ }
+ nodeHook.set( elem, value, name );
+ }
+ };
+}
+
+
+// Some attributes require a special call on IE
+if ( !jQuery.support.hrefNormalized ) {
+ jQuery.each([ "href", "src", "width", "height" ], function( i, name ) {
+ jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], {
+ get: function( elem ) {
+ var ret = elem.getAttribute( name, 2 );
+ return ret === null ? undefined : ret;
+ }
+ });
+ });
+}
+
+if ( !jQuery.support.style ) {
+ jQuery.attrHooks.style = {
+ get: function( elem ) {
+ // Return undefined in the case of empty string
+ // Normalize to lowercase since IE uppercases css property names
+ return elem.style.cssText.toLowerCase() || undefined;
+ },
+ set: function( elem, value ) {
+ return ( elem.style.cssText = value + "" );
+ }
+ };
+}
+
+// Safari mis-reports the default selected property of an option
+// Accessing the parent's selectedIndex property fixes it
+if ( !jQuery.support.optSelected ) {
+ jQuery.propHooks.selected = jQuery.extend( jQuery.propHooks.selected, {
+ get: function( elem ) {
+ var parent = elem.parentNode;
+
+ if ( parent ) {
+ parent.selectedIndex;
+
+ // Make sure that it also works with optgroups, see #5701
+ if ( parent.parentNode ) {
+ parent.parentNode.selectedIndex;
+ }
+ }
+ return null;
+ }
+ });
+}
+
+// IE6/7 call enctype encoding
+if ( !jQuery.support.enctype ) {
+ jQuery.propFix.enctype = "encoding";
+}
+
+// Radios and checkboxes getter/setter
+if ( !jQuery.support.checkOn ) {
+ jQuery.each([ "radio", "checkbox" ], function() {
+ jQuery.valHooks[ this ] = {
+ get: function( elem ) {
+ // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified
+ return elem.getAttribute("value") === null ? "on" : elem.value;
+ }
+ };
+ });
+}
+jQuery.each([ "radio", "checkbox" ], function() {
+ jQuery.valHooks[ this ] = jQuery.extend( jQuery.valHooks[ this ], {
+ set: function( elem, value ) {
+ if ( jQuery.isArray( value ) ) {
+ return ( elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0 );
+ }
+ }
+ });
+});
+var rformElems = /^(?:textarea|input|select)$/i,
+ rtypenamespace = /^([^\.]*|)(?:\.(.+)|)$/,
+ rhoverHack = /(?:^|\s)hover(\.\S+|)\b/,
+ rkeyEvent = /^key/,
+ rmouseEvent = /^(?:mouse|contextmenu)|click/,
+ rfocusMorph = /^(?:focusinfocus|focusoutblur)$/,
+ hoverHack = function( events ) {
+ return jQuery.event.special.hover ? events : events.replace( rhoverHack, "mouseenter$1 mouseleave$1" );
+ };
+
+/*
+ * Helper functions for managing events -- not part of the public interface.
+ * Props to Dean Edwards' addEvent library for many of the ideas.
+ */
+jQuery.event = {
+
+ add: function( elem, types, handler, data, selector ) {
+
+ var elemData, eventHandle, events,
+ t, tns, type, namespaces, handleObj,
+ handleObjIn, handlers, special;
+
+ // Don't attach events to noData or text/comment nodes (allow plain objects tho)
+ if ( elem.nodeType === 3 || elem.nodeType === 8 || !types || !handler || !(elemData = jQuery._data( elem )) ) {
+ return;
+ }
+
+ // Caller can pass in an object of custom data in lieu of the handler
+ if ( handler.handler ) {
+ handleObjIn = handler;
+ handler = handleObjIn.handler;
+ selector = handleObjIn.selector;
+ }
+
+ // Make sure that the handler has a unique ID, used to find/remove it later
+ if ( !handler.guid ) {
+ handler.guid = jQuery.guid++;
+ }
+
+ // Init the element's event structure and main handler, if this is the first
+ events = elemData.events;
+ if ( !events ) {
+ elemData.events = events = {};
+ }
+ eventHandle = elemData.handle;
+ if ( !eventHandle ) {
+ elemData.handle = eventHandle = function( e ) {
+ // Discard the second event of a jQuery.event.trigger() and
+ // when an event is called after a page has unloaded
+ return typeof jQuery !== "undefined" && (!e || jQuery.event.triggered !== e.type) ?
+ jQuery.event.dispatch.apply( eventHandle.elem, arguments ) :
+ undefined;
+ };
+ // Add elem as a property of the handle fn to prevent a memory leak with IE non-native events
+ eventHandle.elem = elem;
+ }
+
+ // Handle multiple events separated by a space
+ // jQuery(...).bind("mouseover mouseout", fn);
+ types = jQuery.trim( hoverHack(types) ).split( " " );
+ for ( t = 0; t < types.length; t++ ) {
+
+ tns = rtypenamespace.exec( types[t] ) || [];
+ type = tns[1];
+ namespaces = ( tns[2] || "" ).split( "." ).sort();
+
+ // If event changes its type, use the special event handlers for the changed type
+ special = jQuery.event.special[ type ] || {};
+
+ // If selector defined, determine special event api type, otherwise given type
+ type = ( selector ? special.delegateType : special.bindType ) || type;
+
+ // Update special based on newly reset type
+ special = jQuery.event.special[ type ] || {};
+
+ // handleObj is passed to all event handlers
+ handleObj = jQuery.extend({
+ type: type,
+ origType: tns[1],
+ data: data,
+ handler: handler,
+ guid: handler.guid,
+ selector: selector,
+ needsContext: selector && jQuery.expr.match.needsContext.test( selector ),
+ namespace: namespaces.join(".")
+ }, handleObjIn );
+
+ // Init the event handler queue if we're the first
+ handlers = events[ type ];
+ if ( !handlers ) {
+ handlers = events[ type ] = [];
+ handlers.delegateCount = 0;
+
+ // Only use addEventListener/attachEvent if the special events handler returns false
+ if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
+ // Bind the global event handler to the element
+ if ( elem.addEventListener ) {
+ elem.addEventListener( type, eventHandle, false );
+
+ } else if ( elem.attachEvent ) {
+ elem.attachEvent( "on" + type, eventHandle );
+ }
+ }
+ }
+
+ if ( special.add ) {
+ special.add.call( elem, handleObj );
+
+ if ( !handleObj.handler.guid ) {
+ handleObj.handler.guid = handler.guid;
+ }
+ }
+
+ // Add to the element's handler list, delegates in front
+ if ( selector ) {
+ handlers.splice( handlers.delegateCount++, 0, handleObj );
+ } else {
+ handlers.push( handleObj );
+ }
+
+ // Keep track of which events have ever been used, for event optimization
+ jQuery.event.global[ type ] = true;
+ }
+
+ // Nullify elem to prevent memory leaks in IE
+ elem = null;
+ },
+
+ global: {},
+
+ // Detach an event or set of events from an element
+ remove: function( elem, types, handler, selector, mappedTypes ) {
+
+ var t, tns, type, origType, namespaces, origCount,
+ j, events, special, eventType, handleObj,
+ elemData = jQuery.hasData( elem ) && jQuery._data( elem );
+
+ if ( !elemData || !(events = elemData.events) ) {
+ return;
+ }
+
+ // Once for each type.namespace in types; type may be omitted
+ types = jQuery.trim( hoverHack( types || "" ) ).split(" ");
+ for ( t = 0; t < types.length; t++ ) {
+ tns = rtypenamespace.exec( types[t] ) || [];
+ type = origType = tns[1];
+ namespaces = tns[2];
+
+ // Unbind all events (on this namespace, if provided) for the element
+ if ( !type ) {
+ for ( type in events ) {
+ jQuery.event.remove( elem, type + types[ t ], handler, selector, true );
+ }
+ continue;
+ }
+
+ special = jQuery.event.special[ type ] || {};
+ type = ( selector? special.delegateType : special.bindType ) || type;
+ eventType = events[ type ] || [];
+ origCount = eventType.length;
+ namespaces = namespaces ? new RegExp("(^|\\.)" + namespaces.split(".").sort().join("\\.(?:.*\\.|)") + "(\\.|$)") : null;
+
+ // Remove matching events
+ for ( j = 0; j < eventType.length; j++ ) {
+ handleObj = eventType[ j ];
+
+ if ( ( mappedTypes || origType === handleObj.origType ) &&
+ ( !handler || handler.guid === handleObj.guid ) &&
+ ( !namespaces || namespaces.test( handleObj.namespace ) ) &&
+ ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) {
+ eventType.splice( j--, 1 );
+
+ if ( handleObj.selector ) {
+ eventType.delegateCount--;
+ }
+ if ( special.remove ) {
+ special.remove.call( elem, handleObj );
+ }
+ }
+ }
+
+ // Remove generic event handler if we removed something and no more handlers exist
+ // (avoids potential for endless recursion during removal of special event handlers)
+ if ( eventType.length === 0 && origCount !== eventType.length ) {
+ if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) {
+ jQuery.removeEvent( elem, type, elemData.handle );
+ }
+
+ delete events[ type ];
+ }
+ }
+
+ // Remove the expando if it's no longer used
+ if ( jQuery.isEmptyObject( events ) ) {
+ delete elemData.handle;
+
+ // removeData also checks for emptiness and clears the expando if empty
+ // so use it instead of delete
+ jQuery.removeData( elem, "events", true );
+ }
+ },
+
+ // Events that are safe to short-circuit if no handlers are attached.
+ // Native DOM events should not be added, they may have inline handlers.
+ customEvent: {
+ "getData": true,
+ "setData": true,
+ "changeData": true
+ },
+
+ trigger: function( event, data, elem, onlyHandlers ) {
+ // Don't do events on text and comment nodes
+ if ( elem && (elem.nodeType === 3 || elem.nodeType === 8) ) {
+ return;
+ }
+
+ // Event object or event type
+ var cache, exclusive, i, cur, old, ontype, special, handle, eventPath, bubbleType,
+ type = event.type || event,
+ namespaces = [];
+
+ // focus/blur morphs to focusin/out; ensure we're not firing them right now
+ if ( rfocusMorph.test( type + jQuery.event.triggered ) ) {
+ return;
+ }
+
+ if ( type.indexOf( "!" ) >= 0 ) {
+ // Exclusive events trigger only for the exact event (no namespaces)
+ type = type.slice(0, -1);
+ exclusive = true;
+ }
+
+ if ( type.indexOf( "." ) >= 0 ) {
+ // Namespaced trigger; create a regexp to match event type in handle()
+ namespaces = type.split(".");
+ type = namespaces.shift();
+ namespaces.sort();
+ }
+
+ if ( (!elem || jQuery.event.customEvent[ type ]) && !jQuery.event.global[ type ] ) {
+ // No jQuery handlers for this event type, and it can't have inline handlers
+ return;
+ }
+
+ // Caller can pass in an Event, Object, or just an event type string
+ event = typeof event === "object" ?
+ // jQuery.Event object
+ event[ jQuery.expando ] ? event :
+ // Object literal
+ new jQuery.Event( type, event ) :
+ // Just the event type (string)
+ new jQuery.Event( type );
+
+ event.type = type;
+ event.isTrigger = true;
+ event.exclusive = exclusive;
+ event.namespace = namespaces.join( "." );
+ event.namespace_re = event.namespace? new RegExp("(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)") : null;
+ ontype = type.indexOf( ":" ) < 0 ? "on" + type : "";
+
+ // Handle a global trigger
+ if ( !elem ) {
+
+ // TODO: Stop taunting the data cache; remove global events and always attach to document
+ cache = jQuery.cache;
+ for ( i in cache ) {
+ if ( cache[ i ].events && cache[ i ].events[ type ] ) {
+ jQuery.event.trigger( event, data, cache[ i ].handle.elem, true );
+ }
+ }
+ return;
+ }
+
+ // Clean up the event in case it is being reused
+ event.result = undefined;
+ if ( !event.target ) {
+ event.target = elem;
+ }
+
+ // Clone any incoming data and prepend the event, creating the handler arg list
+ data = data != null ? jQuery.makeArray( data ) : [];
+ data.unshift( event );
+
+ // Allow special events to draw outside the lines
+ special = jQuery.event.special[ type ] || {};
+ if ( special.trigger && special.trigger.apply( elem, data ) === false ) {
+ return;
+ }
+
+ // Determine event propagation path in advance, per W3C events spec (#9951)
+ // Bubble up to document, then to window; watch for a global ownerDocument var (#9724)
+ eventPath = [[ elem, special.bindType || type ]];
+ if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) {
+
+ bubbleType = special.delegateType || type;
+ cur = rfocusMorph.test( bubbleType + type ) ? elem : elem.parentNode;
+ for ( old = elem; cur; cur = cur.parentNode ) {
+ eventPath.push([ cur, bubbleType ]);
+ old = cur;
+ }
+
+ // Only add window if we got to document (e.g., not plain obj or detached DOM)
+ if ( old === (elem.ownerDocument || document) ) {
+ eventPath.push([ old.defaultView || old.parentWindow || window, bubbleType ]);
+ }
+ }
+
+ // Fire handlers on the event path
+ for ( i = 0; i < eventPath.length && !event.isPropagationStopped(); i++ ) {
+
+ cur = eventPath[i][0];
+ event.type = eventPath[i][1];
+
+ handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" );
+ if ( handle ) {
+ handle.apply( cur, data );
+ }
+ // Note that this is a bare JS function and not a jQuery handler
+ handle = ontype && cur[ ontype ];
+ if ( handle && jQuery.acceptData( cur ) && handle.apply && handle.apply( cur, data ) === false ) {
+ event.preventDefault();
+ }
+ }
+ event.type = type;
+
+ // If nobody prevented the default action, do it now
+ if ( !onlyHandlers && !event.isDefaultPrevented() ) {
+
+ if ( (!special._default || special._default.apply( elem.ownerDocument, data ) === false) &&
+ !(type === "click" && jQuery.nodeName( elem, "a" )) && jQuery.acceptData( elem ) ) {
+
+ // Call a native DOM method on the target with the same name name as the event.
+ // Can't use an .isFunction() check here because IE6/7 fails that test.
+ // Don't do default actions on window, that's where global variables be (#6170)
+ // IE<9 dies on focus/blur to hidden element (#1486)
+ if ( ontype && elem[ type ] && ((type !== "focus" && type !== "blur") || event.target.offsetWidth !== 0) && !jQuery.isWindow( elem ) ) {
+
+ // Don't re-trigger an onFOO event when we call its FOO() method
+ old = elem[ ontype ];
+
+ if ( old ) {
+ elem[ ontype ] = null;
+ }
+
+ // Prevent re-triggering of the same event, since we already bubbled it above
+ jQuery.event.triggered = type;
+ elem[ type ]();
+ jQuery.event.triggered = undefined;
+
+ if ( old ) {
+ elem[ ontype ] = old;
+ }
+ }
+ }
+ }
+
+ return event.result;
+ },
+
+ dispatch: function( event ) {
+
+ // Make a writable jQuery.Event from the native event object
+ event = jQuery.event.fix( event || window.event );
+
+ var i, j, cur, ret, selMatch, matched, matches, handleObj, sel, related,
+ handlers = ( (jQuery._data( this, "events" ) || {} )[ event.type ] || []),
+ delegateCount = handlers.delegateCount,
+ args = core_slice.call( arguments ),
+ run_all = !event.exclusive && !event.namespace,
+ special = jQuery.event.special[ event.type ] || {},
+ handlerQueue = [];
+
+ // Use the fix-ed jQuery.Event rather than the (read-only) native event
+ args[0] = event;
+ event.delegateTarget = this;
+
+ // Call the preDispatch hook for the mapped type, and let it bail if desired
+ if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) {
+ return;
+ }
+
+ // Determine handlers that should run if there are delegated events
+ // Avoid non-left-click bubbling in Firefox (#3861)
+ if ( delegateCount && !(event.button && event.type === "click") ) {
+
+ for ( cur = event.target; cur != this; cur = cur.parentNode || this ) {
+
+ // Don't process clicks (ONLY) on disabled elements (#6911, #8165, #11382, #11764)
+ if ( cur.disabled !== true || event.type !== "click" ) {
+ selMatch = {};
+ matches = [];
+ for ( i = 0; i < delegateCount; i++ ) {
+ handleObj = handlers[ i ];
+ sel = handleObj.selector;
+
+ if ( selMatch[ sel ] === undefined ) {
+ selMatch[ sel ] = handleObj.needsContext ?
+ jQuery( sel, this ).index( cur ) >= 0 :
+ jQuery.find( sel, this, null, [ cur ] ).length;
+ }
+ if ( selMatch[ sel ] ) {
+ matches.push( handleObj );
+ }
+ }
+ if ( matches.length ) {
+ handlerQueue.push({ elem: cur, matches: matches });
+ }
+ }
+ }
+ }
+
+ // Add the remaining (directly-bound) handlers
+ if ( handlers.length > delegateCount ) {
+ handlerQueue.push({ elem: this, matches: handlers.slice( delegateCount ) });
+ }
+
+ // Run delegates first; they may want to stop propagation beneath us
+ for ( i = 0; i < handlerQueue.length && !event.isPropagationStopped(); i++ ) {
+ matched = handlerQueue[ i ];
+ event.currentTarget = matched.elem;
+
+ for ( j = 0; j < matched.matches.length && !event.isImmediatePropagationStopped(); j++ ) {
+ handleObj = matched.matches[ j ];
+
+ // Triggered event must either 1) be non-exclusive and have no namespace, or
+ // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace).
+ if ( run_all || (!event.namespace && !handleObj.namespace) || event.namespace_re && event.namespace_re.test( handleObj.namespace ) ) {
+
+ event.data = handleObj.data;
+ event.handleObj = handleObj;
+
+ ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler )
+ .apply( matched.elem, args );
+
+ if ( ret !== undefined ) {
+ event.result = ret;
+ if ( ret === false ) {
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ }
+ }
+ }
+ }
+
+ // Call the postDispatch hook for the mapped type
+ if ( special.postDispatch ) {
+ special.postDispatch.call( this, event );
+ }
+
+ return event.result;
+ },
+
+ // Includes some event props shared by KeyEvent and MouseEvent
+ // *** attrChange attrName relatedNode srcElement are not normalized, non-W3C, deprecated, will be removed in 1.8 ***
+ props: "attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),
+
+ fixHooks: {},
+
+ keyHooks: {
+ props: "char charCode key keyCode".split(" "),
+ filter: function( event, original ) {
+
+ // Add which for key events
+ if ( event.which == null ) {
+ event.which = original.charCode != null ? original.charCode : original.keyCode;
+ }
+
+ return event;
+ }
+ },
+
+ mouseHooks: {
+ props: "button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),
+ filter: function( event, original ) {
+ var eventDoc, doc, body,
+ button = original.button,
+ fromElement = original.fromElement;
+
+ // Calculate pageX/Y if missing and clientX/Y available
+ if ( event.pageX == null && original.clientX != null ) {
+ eventDoc = event.target.ownerDocument || document;
+ doc = eventDoc.documentElement;
+ body = eventDoc.body;
+
+ event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 );
+ event.pageY = original.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 );
+ }
+
+ // Add&