<commit_before>;(function (win, document) {

//create the global namespace
var Spineless = win.Spineless = {};
Spineless.$ = win.$;

//list of attributes that require special processing
var blacklist = [
	"id",
	"tag",
	"children",
	"className",
	"text",
	"view"
];

//list of input nodeName variations
var INPUT_NODE = [
	"INPUT",
	"SELECT",
	"TEXTAREA",
	"BUTTON"
];

//feature detection constants
var DETECT = {
	ON_INPUT: (document.createElement("input").oninput === null)
};

//unique number #TODO: use this for something
var UID = 0;

/**
* merge all object keys from the argument list
* into one object and return it.
*/
function merge () {
	var target = {};
	var args = Array.prototype.slice.call(arguments);

	//loop over all the arguments
	for (var i = 0; i < args.length; ++i) {
		if (typeof args[i] !== "object") continue;

		for (var key in args[i]) {
			if (!(key in target))
				target[key] = args[i][key];
		}
	}

	return target;
}

//helper method to create extend functions on widgets
function createExtend (parent) {
	return function (opts) {
		var cls = function () {
			if (this instanceof cls) {
				this.init && this.init.apply(this, arguments);
			} else {
				cls.super.apply(cls, arguments);
			}
		};

		//we don't want to use the original constructor
		//or else it will call `init`
		function Dummy(){}
  		Dummy.prototype = parent.prototype;

		//extend the prototype with the widget
		cls.prototype = new Dummy;
		cls.prototype.constructor = cls;

		//give access to the parent class
		cls.super = function (ctx, method, args) {
			parent.prototype[method].apply(ctx, args);
		};

		//give an extend function to this class
		cls.extend = createExtend(cls);

		//extend the class with these options
		for (var key in opts)
			cls.prototype[key] = opts[key];

		return cls;
	};
}

/**
* Spineless Event emitter class.
*/
var Event = function () {
	this.tag = "Event";
	this.init && this.init.apply(this, arguments);
};

Event.prototype = {
	init: function () {
		this._handlers = {};
	},

	on: function (evt, cb) {
		if (!this._handlers[evt])
			this._handlers[evt] = [];

		this._handlers[evt].push(cb);
	},

	off: function (evt, cb) {
		//remove every handler
		if (!evt && !cb) {
			this._handlers = {};
			return;
		}

		if (!cb) {
			//remove every handler with that name
			this._handlers[evt] = [];
		} else {
			var handlers = this._handlers[evt] || [];

			//find the exact handler
			for (var i = handlers.length; i >= 0; --i) {
				if (handlers[i] === cb) {
					handlers.splice(i, 1);
				}
			}
		}
	},

	emit: function (evt) {
		//save all the arguments except the first one
		var args = Array.prototype.slice.call(arguments, 1);
		var node = this;

		do {
			//execute the handlers
			if (!node._handlers || !node._handlers[evt]) continue;

			for (var i = 0; i < node._handlers[evt].length; ++i) {
				//skip if the value is not executable
				if (typeof node._handlers[evt][i] !== "function")
					continue;

				node._handlers[evt][i].apply(node, args);
			}
		} while (node = node.parent);
	},

	once: function (evt, cb) {
		this.on(evt, function temp () {
			cb && cb.apply(this, arguments);
			this.off(evt, temp);
		});
	}
};

//setup event aliases
Event.prototype.bind = Event.prototype.subscribe = Event.prototype.on;
Event.prototype.unbind = Event.prototype.unsubscribe = Event.prototype.off;
Event.prototype.trigger = Event.prototype.publish = Event.prototype.emit;

Event.extend = createExtend(Event);

/**
* Spineless Views are the backbone of the framework. Use
* this class to build your DOM structure in JSON, assign
* event handlers and communicate with the server.
*/
var View = Event.extend({

	/**
	* Default methods
	*/
	init: function (opts) {
		View(this, "init", arguments);
		console.log(this.constructor.name, this.constructor.toString())
		opts = opts || {};

		//pass in the parent view through options
		this.parent = opts.parent;
		this.superview = opts.superview || (this.parent && this.parent.el);
		this.children = [];

		//internal structures
		this.model = {};
		this.form = [];

		//template exists in DOM, parse it
		if (typeof this.template === "string") {
			this.el = document.getElementById(this.template);
			this.parseTemplate(this.el);
		} else { //DOM parent fragment
			this.el = document.createDocumentFragment();
			this.renderTemplate(this.el);
		}

		//keep a reference to this class
		var self = this;

		//copy items from opts into the model
		for (var item in this.defaults) {
			if (item in opts) {
				this.model[item] = opts[item];
				delete opts[item];
			} else {
				this.model[item] = this.defaults[item];
			}
		}

		//bind forms to model
		if (this.defaults)
			this._bindForms();

		//attach any event handlers
		if (typeof this.events === "object") {
			for (var on in this.events) {
				var parsed = on.split(" ");
				var cb = this[this.events[on]];

				//ensure correct format and element exists
				if (parsed.length !== 2 || !this[parsed[1]])
					continue;

				this.attachEvent(parsed[1], parsed[0], cb);
			}
		}

		this.on("change", this.render);

		//execute render after initialisation
		setTimeout(function() {
			self.render && self.render.call(self);

			//check to see if there is a parent el
			if (!self.superview && self.parent)
				self.superview = self.parent.el;

			self.superview && self.superview.appendChild(self.el);
		}, 0);
	},

	attachEvent: function (obj, evt, cb) {
		var self = this;

		//add a 2nd level evt handler
		this[obj]["on" + evt] = function (e) {
			//simple IE fix
			e = e || window.event;

			self.emit("input:" + evt, e, self);
			return cb && cb.call(self, e);
		};
	},

	/**
	* Heirarchy methods
	*/
	addChild: function (child) {
		child.parent = this;

		this.children.push(child);
		this.emit("ChildAdded");
		child.emit("ParentAdded");
	},

	removeFromParent: function () {
		var children = this.parent.children;
		for (var i = children.length - 1; i >= 0; --i) {
			if (children[i] === this) {
				children.splice(i, 1);
				break;
			}
		}

		this.superview.removeChild(this.container);
		this.parent.emit("ChildRemoved", this);
		this.emit("ParentRemoved", parent);
	},

	/**
	* Render methods
	*/
	renderTemplate: function (parent) {
		var tpl = this.template;
		if (!tpl) return;

		var container = document.createElement("div");
		container.setAttribute("class", "container");

		for (var i = 0; i < tpl.length; ++i) {
			View.toDOM(this, tpl[i], container);
		}

		parent.appendChild(container);
		this.container = container;
	},

	parseTemplate: function (parent) {
		var collection = parent.childNodes;

		for (var i = 0; i < collection.length; ++i) {
			var id = collection[i].id || collection[i].name;

			//if the property hasn't been taken, apply to class
			if (id) {
				//if it exists as a node, turn it into an array
				if (this[id] && this[id].nodeName) {
					this[id] = [ this[id], collection[i] ];
				} else if (this[id] && 'length' in this[id]) {
					this[id].push(collection[i]);
				} else {
					this[id] = collection[i];
				}

				//add the node to the form array
				if (INPUT_NODE.indexOf(collection[i].nodeName) !== -1) {
					this.form.push(collection[i]);
				}
			}

			//recurse the tree
			if (collection[i].childNodes)
				this.parseTemplate(collection[i]);
		}
	},

	/**
	* Any form elements with the same property
	* as the model should be bound
	*/
	_bindForms: function () {

		//callback to handle all changes
		function handleChange () {
			for (var i = 0; i < this.form.length; ++i) {
				//mock the evt object
				handleSingleChange({target: this.form[i]});
			}
		}

		//handle a change on a single form item
		function handleSingleChange (evt) {
			var input = evt.target;
			var key = input.id || input.name;
			var value;

			if (input.type === "checkbox") {
				value = input.checked;
			} else if (input.type === "radio") {
				//only update the value if checked
				if (input.checked)
					value = input.value;
			} else {
				value = input.value;
			}

			//if value has actually been set
			if (value !== undefined) {
				if (this.model[key] !== value)
					this.emit("change", key, value);

				this.model[key] = value;
			}
		}

		if (DETECT.ON_INPUT) {
			//if the new oninput event is supported, use that
			for (var i = 0; i < this.form.length; ++i) {
				this.form[i].addEventListener("input", handleSingleChange.bind(this), false);
				this.form[i].addEventListener("change", handleSingleChange.bind(this), false);
			}
		} else {
			//otherwise setup an interval to poll the value
			this._interval = setInterval(handleChange, 300);
		}
	},

	_unbindForms: function () {
		//if we had to resort to interval
		if (this._interval)
			clearInterval(this._interval);
	},

	/**
	* Recursively generate a new object containing
	* the models of the views.
	*/
	getModel: function () {
		var model = merge({}, this.model);

		//serialize all children if they exist
		if (this.children.length) {
			model.children = [];

			for (var i = 0; i < this.children.length; ++i) {
				model.children.push(
					this.children[i].getModel()
				);
			}
		}

		return model;
	},

	serialize: function () {
		return JSON.stringify(this.getModel());
	},

	unserialize: function () {},

	/**
	* Network methods
	*/
	sync: function(method, url, data) {
		if (typeof this.validate === "function") {
			var err = this.validate();
			if (err) {
				return console.error(err);
			}
		}

		console.log(method, url, JSON.stringify(data));
	},

	post: function () {
		this.sync("POST", this.url, this.model);
	},

	delete: function () {
		this.sync("DELETE", this.url, this.model);
	},

	//shim functions
	render: function () {}
});

/**
* Static method to turn a JSON template into a DOM structure.
* @param ctx - Instance object to save ID references to
* @param obj - Template object to convert
* @param parent - Append the element to this parent
* @return HTMLElement
*/
View.toDOM = function (ctx, obj, parent) {
	if (arguments.length < 3) {
		parent = obj;
		obj = ctx;
		ctx = null;
	}

	//template is a view instead of DOM
	if (obj.view) {
		var view = new obj.view(obj);
		view.superview = parent;

		if (ctx) {
			ctx.addChild(view);
		}

		return view.el;
	}

	var el = document.createElement(obj.tag || "div");

	for (var key in obj)
		if (blacklist.indexOf(key) === -1)
			el.setAttribute(key, obj[key]);

	if (obj.className)
		el.setAttribute("class", obj.className);

	if ('text' in obj)
		el.innerText = obj.text;

	//render children
	if (obj.children) {
		for (var i = 0; i < obj.children.length; ++i) {
			View.toDOM(ctx, obj.children[i], el);
		}
	}

	//append to a parent if specified
	if (parent)
		parent.appendChild(el);

	if (ctx) {
		//save a ref on the context
		if (obj.id) ctx[obj.id] = el;

		//if an input node, save to forms array
		if (INPUT_NODE.indexOf(obj.tag) !== -1) {
			ctx.form.push(el);
		}
	}

	return el;
}

View.extend = createExtend(View);

/**
* Source url for retrieving data
*/
var Source = function(opts) {
	var collection = new View;

	Spineless.$.ajax({
		url: opts.url,
		dataType: 'json',
		success: function (resp) {
			for (var i = 0; i < resp.length; ++i) {
				var view = new opts.view(resp[i]);
				collection.addChild(view);
			}

			setTimeout(function () {
				opts.success(collection)
			}, 0);
		}
	});

	return collection;
};

Source.extend = createExtend(Source);

//assign the classes to the namespace
Spineless.Event = Event;
Spineless.View = View;
Spineless.merge = merge;
Spineless.Source = Source;

})(window, window.document);
<commit_msg>* Better event apis<commit_after>;(function (win, document) {

//create the global namespace
var Spineless = win.Spineless = {};
Spineless.$ = win.$;

//list of attributes that require special processing
var blacklist = [
	"id",
	"tag",
	"children",
	"className",
	"text",
	"view"
];

//list of input nodeName variations
var INPUT_NODE = [
	"INPUT",
	"SELECT",
	"TEXTAREA",
	"BUTTON"
];

//feature detection constants
var DETECT = {
	ON_INPUT: (document.createElement("input").oninput === null)
};

//unique number #TODO: use this for something
var UID = 0;

//save reference to prototype methods
var slice = Array.prototype.slice;
var spaceRegex = /\s+/;

/**
* merge all object keys from the argument list
* into one object and return it.
*/
function merge () {
	var target = {};
	var args = slice.call(arguments);

	//loop over all the arguments
	for (var i = 0; i < args.length; ++i) {
		if (typeof args[i] !== "object") continue;

		for (var key in args[i]) {
			if (!(key in target))
				target[key] = args[i][key];
		}
	}

	return target;
}

//helper method to create extend functions on widgets
function createExtend (parent) {
	return function (opts) {
		var cls = function () {
			if (this instanceof cls) {
				this.init && this.init.apply(this, arguments);
			} else {
				cls.super.apply(cls, arguments);
			}
		};

		//we don't want to use the original constructor
		//or else it will call `init`
		function Dummy(){}
  		Dummy.prototype = parent.prototype;

		//extend the prototype with the widget
		cls.prototype = new Dummy;
		cls.prototype.constructor = cls;

		//give access to the parent class
		cls.super = function (ctx, method, args) {
			parent.prototype[method].apply(ctx, args);
		};

		//give an extend function to this class
		cls.extend = createExtend(cls);

		//extend the class with these options
		for (var key in opts)
			cls.prototype[key] = opts[key];

		return cls;
	};
}

/**
* Spineless Event emitter class.
*/
var Event = function () {
	this.tag = "Event";
	this.init && this.init.apply(this, arguments);
};

Event.prototype = {
	init: function () {
		this._handlers = {};
	},

	on: function (evt, cb) {
		if (spaceRegex.test(evt)) {
			//if delimetered by a space, call on
			//for every event.
			var evts = evt.split(spaceRegex);
			for (var i = 0; i < evts.length; ++i)
				this.on(evts[i], cb);

			return this;
		}

		//define callbacks with an object
		if (typeof evt === "object") {
			//key is the event name, value is the
			//callback function
			for (var evtName in evt)
				this.on(evtName, evt[evtName]);
		}

		if (!this._handlers[evt])
			this._handlers[evt] = [];

		this._handlers[evt].push(cb);
		return this;
	},

	off: function (evt, cb) {
		//remove every handler
		if (!evt && !cb) {
			this._handlers = {};
			return this;
		}

		if (!cb) {
			//remove every handler with that name
			this._handlers[evt] = [];
		} else {
			var handlers = this._handlers[evt] || [];

			//find the exact handler
			for (var i = handlers.length; i >= 0; --i) {
				if (handlers[i] === cb) {
					handlers.splice(i, 1);
				}
			}
		}

		return this;
	},

	emit: function (evt) {
		//save all the arguments except the first one
		var args = slice.call(arguments, 1);
		var node = this;

		//if there is a colon, emit wildcards
		if (evt.indexOf(":") !== -1) {
			var evtSplat = evt.split(":");
			var wildcard = evtSplat[0] + ":*";

			//don't emit wildcard if already a wildcard
			if (evtSplat[1] !== "*") {
				var wildArgs = slice.call(arguments, 0);
				wildArgs[0] = wildcard;

				this.emit.apply(this, wildArgs);
			}
		}

		do {
			//execute the handlers
			if (!node._handlers || !node._handlers[evt]) continue;

			for (var i = 0; i < node._handlers[evt].length; ++i) {
				//skip if the value is not executable
				if (typeof node._handlers[evt][i] !== "function")
					continue;

				node._handlers[evt][i].apply(node, args);
			}
		} while (node = node.parent);

		return this;
	},

	once: function (evt, cb) {
		this.on(evt, function temp () {
			cb && cb.apply(this, arguments);
			this.off(evt, temp);
		});

		return this;
	}
};

//setup event aliases
Event.prototype.bind = Event.prototype.subscribe = Event.prototype.on;
Event.prototype.unbind = Event.prototype.unsubscribe = Event.prototype.off;
Event.prototype.trigger = Event.prototype.publish = Event.prototype.emit;

Event.extend = createExtend(Event);

/**
* Spineless Views are the backbone of the framework. Use
* this class to build your DOM structure in JSON, assign
* event handlers and communicate with the server.
*/
var View = Event.extend({

	/**
	* Default methods
	*/
	init: function (opts) {
		View(this, "init", arguments);
		opts = opts || {};

		//pass in the parent view through options
		this.parent = opts.parent;
		this.superview = opts.superview || (this.parent && this.parent.el);
		this.children = [];

		//internal structures
		this.model = {};
		this.form = [];

		//template exists in DOM, parse it
		if (typeof this.template === "string") {
			this.el = document.getElementById(this.template);
			this.parseTemplate(this.el);
		} else { //DOM parent fragment
			this.el = document.createDocumentFragment();
			this.renderTemplate(this.el);
		}

		//keep a reference to this class
		var self = this;

		//copy items from opts into the model
		for (var item in this.defaults) {
			if (item in opts) {
				this.model[item] = opts[item];
				delete opts[item];
			} else {
				this.model[item] = this.defaults[item];
			}
		}

		//bind forms to model
		if (this.defaults)
			this._bindForms();

		//attach any event handlers
		if (typeof this.events === "object") {
			for (var on in this.events) {
				var parsed = on.split(" ");
				var cb = this[this.events[on]];

				//ensure correct format and element exists
				if (parsed.length !== 2 || !this[parsed[1]])
					continue;

				this.attachEvent(parsed[1], parsed[0], cb);
			}
		}

		this.on("change", this.render);

		//execute render after initialisation
		setTimeout(function() {
			self.render && self.render.call(self);

			//check to see if there is a parent el
			if (!self.superview && self.parent)
				self.superview = self.parent.el;

			self.superview && self.superview.appendChild(self.el);
		}, 0);
	},

	attachEvent: function (obj, evt, cb) {
		var self = this;

		//add a 2nd level evt handler
		this[obj]["on" + evt] = function (e) {
			//simple IE fix
			e = e || window.event;

			self.emit("dom:" + evt, e, self);
			return cb && cb.call(self, e);
		};
	},

	/**
	* Heirarchy methods
	*/
	addChild: function (child) {
		child.parent = this;

		this.children.push(child);
		this.emit("child:add", child);
		child.emit("parent:add", this);
	},

	removeFromParent: function () {
		var children = this.parent.children;
		for (var i = children.length - 1; i >= 0; --i) {
			if (children[i] === this) {
				children.splice(i, 1);
				break;
			}
		}

		this.superview.removeChild(this.container);
		this.parent.emit("child:remove", this);
		this.emit("parent:remove", parent);
	},

	/**
	* Render methods
	*/
	renderTemplate: function (parent) {
		var tpl = this.template;
		if (!tpl) return;

		var container = document.createElement("div");
		container.setAttribute("class", "container");

		for (var i = 0; i < tpl.length; ++i) {
			View.toDOM(this, tpl[i], container);
		}

		parent.appendChild(container);
		this.container = container;
	},

	parseTemplate: function (parent) {
		var collection = parent.childNodes;

		for (var i = 0; i < collection.length; ++i) {
			var id = collection[i].id || collection[i].name;

			//if the property hasn't been taken, apply to class
			if (id) {
				//if it exists as a node, turn it into an array
				if (this[id] && this[id].nodeName) {
					this[id] = [ this[id], collection[i] ];
				} else if (this[id] && 'length' in this[id]) {
					this[id].push(collection[i]);
				} else {
					this[id] = collection[i];
				}

				//add the node to the form array
				if (INPUT_NODE.indexOf(collection[i].nodeName) !== -1) {
					this.form.push(collection[i]);
				}
			}

			//recurse the tree
			if (collection[i].childNodes)
				this.parseTemplate(collection[i]);
		}
	},

	/**
	* Any form elements with the same property
	* as the model should be bound
	*/
	_bindForms: function () {

		//callback to handle all changes
		function handleChange () {
			for (var i = 0; i < this.form.length; ++i) {
				//mock the evt object
				handleSingleChange({target: this.form[i]});
			}
		}

		//handle a change on a single form item
		function handleSingleChange (evt) {
			var input = evt.target;
			var key = input.id || input.name;
			var value;

			if (input.type === "checkbox") {
				value = input.checked;
			} else if (input.type === "radio") {
				//only update the value if checked
				if (input.checked)
					value = input.value;
			} else {
				value = input.value;
			}

			//if value has actually been set
			if (value !== undefined) {
				//emit the change events
				if (this.model[key] !== value) {
					this.emit("change", key, value);
					this.emit("change:"+key, value);
				}

				this.model[key] = value;
			}
		}

		if (DETECT.ON_INPUT) {
			//if the new oninput event is supported, use that
			for (var i = 0; i < this.form.length; ++i) {
				this.form[i].addEventListener("input", handleSingleChange.bind(this), false);
				this.form[i].addEventListener("change", handleSingleChange.bind(this), false);
			}
		} else {
			//otherwise setup an interval to poll the value
			this._interval = setInterval(handleChange, 300);
		}
	},

	_unbindForms: function () {
		//if we had to resort to interval
		if (this._interval)
			clearInterval(this._interval);
	},

	/**
	* Recursively generate a new object containing
	* the models of the views.
	*/
	getModel: function () {
		var model = merge({}, this.model);

		//serialize all children if they exist
		if (this.children.length) {
			model.children = [];

			for (var i = 0; i < this.children.length; ++i) {
				model.children.push(
					this.children[i].getModel()
				);
			}
		}

		return model;
	},

	serialize: function () {
		return JSON.stringify(this.getModel());
	},

	unserialize: function () {},

	/**
	* Network methods
	*/
	sync: function(method, url, data) {
		if (typeof this.validate === "function") {
			var err = this.validate();
			if (err) {
				return console.error(err);
			}
		}

		console.log(method, url, JSON.stringify(data));
	},

	post: function () {
		this.sync("POST", this.url, this.model);
	},

	delete: function () {
		this.sync("DELETE", this.url, this.model);
	},

	//shim functions
	render: function () {}
});

/**
* Static method to turn a JSON template into a DOM structure.
* @param ctx - Instance object to save ID references to
* @param obj - Template object to convert
* @param parent - Append the element to this parent
* @return HTMLElement
*/
View.toDOM = function (ctx, obj, parent) {
	if (arguments.length < 3) {
		parent = obj;
		obj = ctx;
		ctx = null;
	}

	//template is a view instead of DOM
	if (obj.view) {
		var view = new obj.view(obj);
		view.superview = parent;

		if (ctx) {
			ctx.addChild(view);
		}

		return view.el;
	}

	var el = document.createElement(obj.tag || "div");

	for (var key in obj)
		if (blacklist.indexOf(key) === -1)
			el.setAttribute(key, obj[key]);

	if (obj.className)
		el.setAttribute("class", obj.className);

	if ('text' in obj)
		el.innerText = obj.text;

	//render children
	if (obj.children) {
		for (var i = 0; i < obj.children.length; ++i) {
			View.toDOM(ctx, obj.children[i], el);
		}
	}

	//append to a parent if specified
	if (parent)
		parent.appendChild(el);

	if (ctx) {
		//save a ref on the context
		if (obj.id) ctx[obj.id] = el;

		//if an input node, save to forms array
		if (INPUT_NODE.indexOf(obj.tag) !== -1) {
			ctx.form.push(el);
		}
	}

	return el;
}

View.extend = createExtend(View);

/**
* Source url for retrieving data
*/
var Source = function(opts) {
	var collection = new View;

	Spineless.$.ajax({
		url: opts.url,
		dataType: 'json',
		success: function (resp) {
			for (var i = 0; i < resp.length; ++i) {
				var view = new opts.view(resp[i]);
				collection.addChild(view);
			}

			setTimeout(function () {
				opts.success(collection)
			}, 0);
		}
	});

	return collection;
};

Source.extend = createExtend(Source);

//assign the classes to the namespace
Spineless.Event = Event;
Spineless.View = View;
Spineless.merge = merge;
Spineless.Source = Source;

})(window, window.document);
<|endoftext|>


<commit_before>(function () {

  'use strict';

  // The rules used to hide elements during the ready lifecycle callback.
  var hiddenRules = document.createElement('style');

  // Observers
  // ---------

  // Normalise the mutaiton observer constructor.
  var MutationObserver = window.MutationObserver || window.WebkitMutationObserver || window.MozMutationObserver;

  if (!MutationObserver) {
    MutationObserver = function (callback) {
      this.callback = callback;
      this.elements = [];
    };

    MutationObserver.prototype = {
      observe: function (target, options) {
        var that = this;
        var attributeOldValueCache = {};
        var item = {
          target: target,
          options: options,
          insertHandler: function (e) {
            if (!canTriggerInsertOrRemove(e)) {
              return;
            }

            that.callback([
              mutationRecord(e, {
                addedNodes: [e.target]
              })
            ]);
          },
          removeHandler: function (e) {
            if (!canTriggerInsertOrRemove(e)) {
              return;
            }

            that.callback([
              mutationRecord(e, {
                removedNodes: [e.target]
              })
            ]);
          },
          attributeHandler: function (e) {
            if (!canTriggerAttributeModification(e)) {
              return;
            }

            that.callback([
              mutationRecord(e, {
                attributeName: e.attrName,
                oldValue: options.attributeOldValue ? (attributeOldValueCache[e.attrName] || e.prevValue || null) : null,
                type: 'attributes'
              })
            ]);

            // We keep track of old values so that when IE incorrectly reports the old value we can ensure it is
            // actually correct.
            if (options.attributeOldValue) {
              attributeOldValueCache[e.attrName] = e.newValue;
            }
          }
        };

        this.elements.push(item);

        if (options.childList) {
          target.addEventListener('DOMSubtreeModified', item.insertHandler);
          target.addEventListener('DOMNodeRemoved', item.removeHandler);
        }

        if (options.attributes) {
          target.addEventListener('DOMAttrModified', item.attributeHandler);
        }

        return this;

        function canTriggerInsertOrRemove (e) {
          return options.childList && (options.subtree || e.target.parentNode === target);
        }

        function canTriggerAttributeModification (e) {
          return e.target === target;
        }

        function mutationRecord (e, merge) {
          return inherit(merge, {
            addedNodes: null,
            attributeName: null,
            attributeNamespace: null,
            nextSibling: e.target.nextSibling,
            oldValue: null,
            previousSibling: e.target.previousSibling,
            removedNodes: null,
            target: e.target,
            type: 'childList'
          });
        }
      },

      disconnect: function () {
        for (var a in this.elements) {
          var item = this.elements[a];
          item.target.removeEventListener('DOMSubtreeModified', item.insertHandler);
          item.target.removeEventListener('DOMNodeRemoved', item.removeHandler);
          item.target.removeEventListener('DOMAttrModified', item.attributeHandler);
        }

        this.elements = [];

        return this;
      }
    };
  }

  // Public API
  // ----------

  var documentObserver;
  var skateComponents = {};

  /**
   * Creates a listener for the specified component.
   *
   * @param {String} id The ID of the component.
   * @param {Object | Function} component The component definition.
   *
   * @returns {Function} Function or constructor that creates a custom-element for the component.
   */
  function skate (id, component) {
    if (skateComponents[id]) {
      throw new Error('A component with the ID of "' + id + '" already exists.');
    }

    if (!documentObserver) {
      documentObserver = new MutationObserver(function (mutations) {
        mutations.forEach(function (mutation) {
          skate.init(mutation.addedNodes);
          triggerRemoveAll(mutation.removedNodes);
        });
      });

      documentObserver.observe(document, {
        childList: true,
        subtree: true
      });
    }

    if (!component) {
      component = {};
    }

    if (typeof component === 'function') {
      component = {
        insert: component
      };
    }

    inherit(component, skate.defaults);

    var Element = makeElementConstructor(id, component);

    if (component.ready) {
      hiddenRules.sheet.insertRule(
        id + ':not(.' + component.classname + '),' +
        '[' + id + ']:not(.' + component.classname + '),' +
        '.' + id + ':not(.' + component.classname + '){display:none}',
        hiddenRules.sheet.cssRules.length
      );
    }

    var existing = Element.existing();

    for (var a = 0; a < existing.length; a++) {
      triggerLifecycle(id, component, existing[a]);
    }

    skateComponents[id] = component;

    return Element;
  }

  // Restriction type constants.
  skate.types = {
    ANY: 'act',
    ATTR: 'a',
    CLASS: 'c',
    NOATTR: 'ct',
    NOCLASS: 'at',
    NOTAG: 'ac',
    TAG: 't'
  };

  // Default configuration.
  skate.defaults = {
    // Set to `{...}` of `attrName: `{ init: ..., update: ..., remove: ... }` to listen to specific attributes.
    attributes: false,

    // The classname to use when showing this component.
    classname: '__skate',

    // Whether or not to start listening right away.
    listen: true,

    // Properties and methods to add to each element.
    prototype: {},

    // The type of bindings to allow.
    type: skate.types.ANY
  };

  /**
   * Stops listening.
   *
   * @returns {skate}
   */
  skate.destroy = function () {
    documentObserver.disconnect();
    documentObserver = undefined;
    skateComponents = {};
    return skate;
  };

  /**
   * Synchronously initialises the specified element or elements.
   *
   * @param {Element | Traversable} elements The element or elements to init.
   *
   * @returns {skate}
   */
  skate.init = function (elements) {
    eachElement(elements, function (element) {
      for (var possibleId in possibleIds(element)) {
        if (possibleId in skateComponents) {
          triggerLifecycle(possibleId, skateComponents[possibleId], element);
        }
      }

      skate.init(element.children);
    });

    return skate;
  };

  /**
   * Creates a new mutation observer for the specified element.
   *
   * @param {Function} callback The callback to execute for the observer.
   *
   * @returns {MutationObserver}
   */
  skate.watch = function (callback) {
    return new MutationObserver(callback);
  };

  /**
   * Executes a callback when an element is initialised as a particular component.
   *
   * @param {HTMLElement} element The element to listen to.
   *
   * @returns {Object}
   */
  skate.when = function (element) {
    return {
      /**
       * Sets the id of the component to listen for on the element.
       *
       * @param {String} id The id of the component to listen for.
       *
       * @returns {Object}
       */
      is: function (id) {
        return {
          /**
           * Executes the specified callback when an element is initialised with a particular component.
           *
           * @param {Function} callback The callback to execute.
           *
           * @returns {Object}
           */
          then: function (callback) {
            if (data(element, id + '.ready-called')) {
              callback(element);
            } else {
              var callbacks = data(element, id + '.when-callbacks');

              if (!callbacks) {
                callbacks = [];
              }

              callbacks.push(callback);
              data(element, id + '.when-callbacks', callbacks);
            }

            return this;
          }
        };
      }
    };
  };

  // Lifecycle Triggers
  // ------------------

  // Triggers the entire lifecycle.
  function triggerLifecycle (id, component, target) {
    triggerReady(id, component, target, function (replaceWith) {
      if (!replaceWith) {
        return triggerInsert(id, component, target);
      }

      if (replaceWith === target || !target.parentNode) {
        return;
      }

      // A placeholder for replacing the current element.
      var comment = document.createComment('placeholder');

      // Replace the target with the placeholder.
      target.parentNode.insertBefore(comment, target);
      target.parentNode.removeChild(target);

      // Handle HTML.
      if (typeof replaceWith === 'string') {
        var div = document.createElement('div');
        div.innerHTML = replaceWith;
        replaceWith = div.children;
      }

      // Place each item before the comment in sequence.
      eachElement(replaceWith, function (element) {
        comment.parentNode.insertBefore(element, comment);
      });

      // Cleanup.
      comment.parentNode.removeChild(comment);
    });
  }

  // Triggers the ready callback and continues execution to the insert callback.
  function triggerReady (id, component, target, done) {
    var definedMultipleArgs = /^[^(]+\([^,)]+,/;
    var readyFn = component.ready;
    done = done || function () {};

    if (data(target, id + '.ready-called')) {
      return done();
    }

    data(target, id + '.ready-called', true);
    inherit(target, component.prototype);
    triggerWhenCallbacks(target, id);

    if (readyFn && definedMultipleArgs.test(readyFn)) {
      readyFn(target, done);
    } else if (readyFn) {
      done(readyFn(target));
    } else {
      done();
    }
  }

  // Triggers insert on the target.
  function triggerInsert (id, component, target) {
    var insertFn = component.insert;

    if (data(target, id + '.insert-called')) {
      return;
    }

    if (!target.parentNode) {
      return;
    }

    data(target, id + '.insert-called', true);
    triggerAttributes(id, component, target);
    addClass(target, component.classname);

    if (insertFn) {
      insertFn(target);
    }
  }

  // Triggers remove on the target.
  function triggerRemove (id, component, target) {
    if (component.remove && !data(target, id + '.remove-called')) {
      data(target, id + '.remove-called', true);
      component.remove(target);
    }
  }

  // Triggers the remove callbacks of the specified elements and their descendants.
  function triggerRemoveAll (elements) {
    eachElement(elements, function (element) {
      triggerRemoveAll(element.children);
      for (var possibleId in possibleIds(element)) {
        if (possibleId in skateComponents) {
          triggerRemove(possibleId, skateComponents[possibleId], element);
        }
      }
    });
  }

  // Initialises and binds attribute handlers.
  function triggerAttributes (id, component, target) {
    if (!component.attributes || data(target, id + '.attributes-called')) {
      return;
    }

    data(target, id + '.attributes-called', true);

    var observer = new MutationObserver(function (mutations) {
      mutations.forEach(function (mutation) {
        var name = mutation.attributeName;
        var attr = target.attributes[name];
        var lifecycle = component.attributes[name];

        if (!lifecycle) {
          return;
        }

        if (attr && mutation.oldValue === null && (lifecycle.insert || lifecycle.update || lifecycle)) {
          insert(lifecycle, target, attr.nodeValue);
        } else if (attr && mutation.oldValue !== null && (lifecycle.update || lifecycle)) {
          update(lifecycle, target, attr.nodeValue, mutation.oldValue);
        } else if (!attr && lifecycle.remove) {
          remove(lifecycle, target, mutation.oldValue);
        }
      });
    });

    observer.observe(target, {
      attributes: true,
      attributeOldValue: true
    });

    // Now trigger init on each attribute.
    for (var a = 0; a < target.attributes.length; a++) {
      var attribute = target.attributes[a];
      var lifecycle = component.attributes[attribute.nodeName];

      if (lifecycle) {
        insert(lifecycle, target, attribute.nodeValue);
      }
    }

    function insert (lifecycle, element, newValue) {
      (lifecycle.insert || lifecycle.update || lifecycle)(element, newValue);
    }

    function update (lifecycle, element, newValue, oldValue) {
      (lifecycle.update || lifecycle)(element, newValue, oldValue);
    }

    function remove (lifecycle, element, oldValue) {
      lifecycle.remove(element, oldValue);
    }
  }

  function triggerWhenCallbacks (target, id) {
    var callbacks = data(target, id + '.when-callbacks');

    if (!callbacks) {
      return;
    }

    callbacks.forEach(function (callback) {
      callback(target);
    });

    // Cleaning up extra callbacks will help
    // prevent possible memory leaks.
    removeData(target, id + '.when-callbacks');
  }

  // Utilities
  // ---------

  function data (element, name, value) {
    if (value === undefined) {
      return element.__SKATE_DATA && element.__SKATE_DATA[name];
    }

    if (!element.__SKATE_DATA) {
      element.__SKATE_DATA = {};
    }

    element.__SKATE_DATA[name] = value;

    return element;
  }

  function removeData (element, name) {
    if (element.__SKATE_DATA && element.__SKATE_DATA[name]) {
      delete element.__SKATE_DATA[name];
    }
  }

  // Adds the specified class to the element.
  function addClass (element, classname) {
    if (element.classList) {
      element.classList.add(classname);
    } else {
      element.className += element.className ? ' ' + classname : classname;
    }
  }

  // Calls the specified callback for each element.
  function eachElement (elements, callback) {
    if (!elements) {
      return;
    }

    if (elements.nodeType) {
      if (elements.nodeType === 1) {
        elements = [elements];
      } else {
        return;
      }
    }

    if (!elements.length) {
      return;
    }

    for (var a = 0; a < elements.length; a++) {
      if (elements[a] && elements[a].nodeType === 1) {
        callback(elements[a], a);
      }
    }
  }

  // Returns the possible ids from an element.
  function possibleIds (element) {
    var ids = data(element, 'possible-ids');

    if (ids) {
      return ids;
    }

    var tag = element.tagName.toLowerCase();

    ids = {};
    ids[tag] = tag;

    for (var a = 0; a < element.attributes.length; a++) {
      var name = element.attributes[a].nodeName;
      ids[name] = name;
    }

    element.className.split(' ').forEach(function (id) {
      if (id) {
        ids[id] = id;
      }
    });

    data(element, 'possible-ids', ids);

    return ids;
  }

  // Merges the second argument into the first.
  function inherit (child, parent) {
    for (var prop in parent) {
      if (child[prop] === undefined) {
        child[prop] = parent[prop];
      }
    }

    return child;
  }

  // Creates a constructor for the specified component.
  function makeElementConstructor (id, component) {
    var isTag = component.type.indexOf(skate.types.TAG) > -1;
    var isAttr = component.type.indexOf(skate.types.ATTR) > -1;
    var isClass = component.type.indexOf(skate.types.CLASS) > -1;
    var selector = (function () {
        var selectors = [];

        if (isTag) {
          selectors.push(id);
        }

        if (isAttr) {
          selectors.push('[' + id + ']');
        }

        if (isClass) {
          selectors.push('.' + id);
        }

        return selectors.join(', ');
      }());

    function CustomElement () {
      if (!isTag) {
        throw new Error('Cannot construct "' + id + '" as a custom element.');
      }

      var element = document.createElement(id);

      component.prototype = this.constructor.prototype;
      triggerReady(id, component, element);

      return element;
    }

    CustomElement.existing = function (within) {
      return (within || document).querySelectorAll(selector);
    };

    CustomElement.selector = function () {
      return selector;
    };

    CustomElement.prototype = Object.create(component.prototype, {
      constructor: {
        enumerable: false,
        value: CustomElement
      }
    });

    return CustomElement;
  }

  // Global Setup
  // ------------

  // Rules that hide elements as they're inserted so that elements are hidden
  // prior to calling the ready callback to prevent FOUC if the component
  // modifies the element in which it is bound.
  document.getElementsByTagName('head')[0].appendChild(hiddenRules);

  // Exporting
  // ---------

  if (typeof define === 'function' && define.amd) {
    define('skate', [], function () {
      return skate;
    });
  } else {
    window.skate = skate;
  }

})();
<commit_msg>Comment about mutation observer polyfill.<commit_after>(function () {

  'use strict';

  // The rules used to hide elements during the ready lifecycle callback.
  var hiddenRules = document.createElement('style');

  // Observers
  // ---------

  // Normalise the mutaiton observer constructor.
  var MutationObserver = window.MutationObserver || window.WebkitMutationObserver || window.MozMutationObserver;

  // Polyfill only the parts of Mutation Observer that we need.
  if (!MutationObserver) {
    MutationObserver = function (callback) {
      this.callback = callback;
      this.elements = [];
    };

    MutationObserver.prototype = {
      observe: function (target, options) {
        var that = this;
        var attributeOldValueCache = {};
        var item = {
          target: target,
          options: options,
          insertHandler: function (e) {
            if (!canTriggerInsertOrRemove(e)) {
              return;
            }

            that.callback([
              mutationRecord(e, {
                addedNodes: [e.target]
              })
            ]);
          },
          removeHandler: function (e) {
            if (!canTriggerInsertOrRemove(e)) {
              return;
            }

            that.callback([
              mutationRecord(e, {
                removedNodes: [e.target]
              })
            ]);
          },
          attributeHandler: function (e) {
            if (!canTriggerAttributeModification(e)) {
              return;
            }

            that.callback([
              mutationRecord(e, {
                attributeName: e.attrName,
                oldValue: options.attributeOldValue ? (attributeOldValueCache[e.attrName] || e.prevValue || null) : null,
                type: 'attributes'
              })
            ]);

            // We keep track of old values so that when IE incorrectly reports the old value we can ensure it is
            // actually correct.
            if (options.attributeOldValue) {
              attributeOldValueCache[e.attrName] = e.newValue;
            }
          }
        };

        this.elements.push(item);

        if (options.childList) {
          target.addEventListener('DOMSubtreeModified', item.insertHandler);
          target.addEventListener('DOMNodeRemoved', item.removeHandler);
        }

        if (options.attributes) {
          target.addEventListener('DOMAttrModified', item.attributeHandler);
        }

        return this;

        function canTriggerInsertOrRemove (e) {
          return options.childList && (options.subtree || e.target.parentNode === target);
        }

        function canTriggerAttributeModification (e) {
          return e.target === target;
        }

        function mutationRecord (e, merge) {
          return inherit(merge, {
            addedNodes: null,
            attributeName: null,
            attributeNamespace: null,
            nextSibling: e.target.nextSibling,
            oldValue: null,
            previousSibling: e.target.previousSibling,
            removedNodes: null,
            target: e.target,
            type: 'childList'
          });
        }
      },

      disconnect: function () {
        for (var a in this.elements) {
          var item = this.elements[a];
          item.target.removeEventListener('DOMSubtreeModified', item.insertHandler);
          item.target.removeEventListener('DOMNodeRemoved', item.removeHandler);
          item.target.removeEventListener('DOMAttrModified', item.attributeHandler);
        }

        this.elements = [];

        return this;
      }
    };
  }

  // Public API
  // ----------

  var documentObserver;
  var skateComponents = {};

  /**
   * Creates a listener for the specified component.
   *
   * @param {String} id The ID of the component.
   * @param {Object | Function} component The component definition.
   *
   * @returns {Function} Function or constructor that creates a custom-element for the component.
   */
  function skate (id, component) {
    if (skateComponents[id]) {
      throw new Error('A component with the ID of "' + id + '" already exists.');
    }

    if (!documentObserver) {
      documentObserver = new MutationObserver(function (mutations) {
        mutations.forEach(function (mutation) {
          skate.init(mutation.addedNodes);
          triggerRemoveAll(mutation.removedNodes);
        });
      });

      documentObserver.observe(document, {
        childList: true,
        subtree: true
      });
    }

    if (!component) {
      component = {};
    }

    if (typeof component === 'function') {
      component = {
        insert: component
      };
    }

    inherit(component, skate.defaults);

    var Element = makeElementConstructor(id, component);

    if (component.ready) {
      hiddenRules.sheet.insertRule(
        id + ':not(.' + component.classname + '),' +
        '[' + id + ']:not(.' + component.classname + '),' +
        '.' + id + ':not(.' + component.classname + '){display:none}',
        hiddenRules.sheet.cssRules.length
      );
    }

    var existing = Element.existing();

    for (var a = 0; a < existing.length; a++) {
      triggerLifecycle(id, component, existing[a]);
    }

    skateComponents[id] = component;

    return Element;
  }

  // Restriction type constants.
  skate.types = {
    ANY: 'act',
    ATTR: 'a',
    CLASS: 'c',
    NOATTR: 'ct',
    NOCLASS: 'at',
    NOTAG: 'ac',
    TAG: 't'
  };

  // Default configuration.
  skate.defaults = {
    // Set to `{...}` of `attrName: `{ init: ..., update: ..., remove: ... }` to listen to specific attributes.
    attributes: false,

    // The classname to use when showing this component.
    classname: '__skate',

    // Whether or not to start listening right away.
    listen: true,

    // Properties and methods to add to each element.
    prototype: {},

    // The type of bindings to allow.
    type: skate.types.ANY
  };

  /**
   * Stops listening for new elements. Generally this will only be used in testing.
   *
   * @returns {skate}
   */
  skate.destroy = function () {
    documentObserver.disconnect();
    documentObserver = undefined;
    skateComponents = {};
    return skate;
  };

  /**
   * Synchronously initialises the specified element or elements.
   *
   * @param {Element | Traversable} elements The element or elements to init.
   *
   * @returns {skate}
   */
  skate.init = function (elements) {
    eachElement(elements, function (element) {
      for (var possibleId in possibleIds(element)) {
        if (possibleId in skateComponents) {
          triggerLifecycle(possibleId, skateComponents[possibleId], element);
        }
      }

      skate.init(element.children);
    });

    return skate;
  };

  /**
   * Creates a new mutation observer for the specified element.
   *
   * @param {Function} callback The callback to execute for the observer.
   *
   * @returns {MutationObserver}
   */
  skate.watch = function (callback) {
    return new MutationObserver(callback);
  };

  /**
   * Executes a callback when an element is initialised as a particular component.
   *
   * @param {HTMLElement} element The element to listen to.
   *
   * @returns {Object}
   */
  skate.when = function (element) {
    return {
      /**
       * Sets the id of the component to listen for on the element.
       *
       * @param {String} id The id of the component to listen for.
       *
       * @returns {Object}
       */
      is: function (id) {
        return {
          /**
           * Executes the specified callback when an element is initialised with a particular component.
           *
           * @param {Function} callback The callback to execute.
           *
           * @returns {Object}
           */
          then: function (callback) {
            if (data(element, id + '.ready-called')) {
              callback(element);
            } else {
              var callbacks = data(element, id + '.when-callbacks');

              if (!callbacks) {
                callbacks = [];
              }

              callbacks.push(callback);
              data(element, id + '.when-callbacks', callbacks);
            }

            return this;
          }
        };
      }
    };
  };

  // Lifecycle Triggers
  // ------------------

  // Triggers the entire lifecycle.
  function triggerLifecycle (id, component, target) {
    triggerReady(id, component, target, function (replaceWith) {
      if (!replaceWith) {
        return triggerInsert(id, component, target);
      }

      if (replaceWith === target || !target.parentNode) {
        return;
      }

      // A placeholder for replacing the current element.
      var comment = document.createComment('placeholder');

      // Replace the target with the placeholder.
      target.parentNode.insertBefore(comment, target);
      target.parentNode.removeChild(target);

      // Handle HTML.
      if (typeof replaceWith === 'string') {
        var div = document.createElement('div');
        div.innerHTML = replaceWith;
        replaceWith = div.children;
      }

      // Place each item before the comment in sequence.
      eachElement(replaceWith, function (element) {
        comment.parentNode.insertBefore(element, comment);
      });

      // Cleanup.
      comment.parentNode.removeChild(comment);
    });
  }

  // Triggers the ready callback and continues execution to the insert callback.
  function triggerReady (id, component, target, done) {
    var definedMultipleArgs = /^[^(]+\([^,)]+,/;
    var readyFn = component.ready;
    done = done || function () {};

    if (data(target, id + '.ready-called')) {
      return done();
    }

    data(target, id + '.ready-called', true);
    inherit(target, component.prototype);
    triggerWhenCallbacks(target, id);

    if (readyFn && definedMultipleArgs.test(readyFn)) {
      readyFn(target, done);
    } else if (readyFn) {
      done(readyFn(target));
    } else {
      done();
    }
  }

  // Triggers insert on the target.
  function triggerInsert (id, component, target) {
    var insertFn = component.insert;

    if (data(target, id + '.insert-called')) {
      return;
    }

    if (!target.parentNode) {
      return;
    }

    data(target, id + '.insert-called', true);
    triggerAttributes(id, component, target);
    addClass(target, component.classname);

    if (insertFn) {
      insertFn(target);
    }
  }

  // Triggers remove on the target.
  function triggerRemove (id, component, target) {
    if (component.remove && !data(target, id + '.remove-called')) {
      data(target, id + '.remove-called', true);
      component.remove(target);
    }
  }

  // Triggers the remove callbacks of the specified elements and their descendants.
  function triggerRemoveAll (elements) {
    eachElement(elements, function (element) {
      triggerRemoveAll(element.children);
      for (var possibleId in possibleIds(element)) {
        if (possibleId in skateComponents) {
          triggerRemove(possibleId, skateComponents[possibleId], element);
        }
      }
    });
  }

  // Initialises and binds attribute handlers.
  function triggerAttributes (id, component, target) {
    if (!component.attributes || data(target, id + '.attributes-called')) {
      return;
    }

    data(target, id + '.attributes-called', true);

    var observer = new MutationObserver(function (mutations) {
      mutations.forEach(function (mutation) {
        var name = mutation.attributeName;
        var attr = target.attributes[name];
        var lifecycle = component.attributes[name];

        if (!lifecycle) {
          return;
        }

        if (attr && mutation.oldValue === null && (lifecycle.insert || lifecycle.update || lifecycle)) {
          insert(lifecycle, target, attr.nodeValue);
        } else if (attr && mutation.oldValue !== null && (lifecycle.update || lifecycle)) {
          update(lifecycle, target, attr.nodeValue, mutation.oldValue);
        } else if (!attr && lifecycle.remove) {
          remove(lifecycle, target, mutation.oldValue);
        }
      });
    });

    observer.observe(target, {
      attributes: true,
      attributeOldValue: true
    });

    // Now trigger init on each attribute.
    for (var a = 0; a < target.attributes.length; a++) {
      var attribute = target.attributes[a];
      var lifecycle = component.attributes[attribute.nodeName];

      if (lifecycle) {
        insert(lifecycle, target, attribute.nodeValue);
      }
    }

    function insert (lifecycle, element, newValue) {
      (lifecycle.insert || lifecycle.update || lifecycle)(element, newValue);
    }

    function update (lifecycle, element, newValue, oldValue) {
      (lifecycle.update || lifecycle)(element, newValue, oldValue);
    }

    function remove (lifecycle, element, oldValue) {
      lifecycle.remove(element, oldValue);
    }
  }

  function triggerWhenCallbacks (target, id) {
    var callbacks = data(target, id + '.when-callbacks');

    if (!callbacks) {
      return;
    }

    callbacks.forEach(function (callback) {
      callback(target);
    });

    // Cleaning up extra callbacks will help
    // prevent possible memory leaks.
    removeData(target, id + '.when-callbacks');
  }

  // Utilities
  // ---------

  function data (element, name, value) {
    if (value === undefined) {
      return element.__SKATE_DATA && element.__SKATE_DATA[name];
    }

    if (!element.__SKATE_DATA) {
      element.__SKATE_DATA = {};
    }

    element.__SKATE_DATA[name] = value;

    return element;
  }

  function removeData (element, name) {
    if (element.__SKATE_DATA && element.__SKATE_DATA[name]) {
      delete element.__SKATE_DATA[name];
    }
  }

  // Adds the specified class to the element.
  function addClass (element, classname) {
    if (element.classList) {
      element.classList.add(classname);
    } else {
      element.className += element.className ? ' ' + classname : classname;
    }
  }

  // Calls the specified callback for each element.
  function eachElement (elements, callback) {
    if (!elements) {
      return;
    }

    if (elements.nodeType) {
      if (elements.nodeType === 1) {
        elements = [elements];
      } else {
        return;
      }
    }

    if (!elements.length) {
      return;
    }

    for (var a = 0; a < elements.length; a++) {
      if (elements[a] && elements[a].nodeType === 1) {
        callback(elements[a], a);
      }
    }
  }

  // Returns the possible ids from an element.
  function possibleIds (element) {
    var ids = data(element, 'possible-ids');

    if (ids) {
      return ids;
    }

    var tag = element.tagName.toLowerCase();

    ids = {};
    ids[tag] = tag;

    for (var a = 0; a < element.attributes.length; a++) {
      var name = element.attributes[a].nodeName;
      ids[name] = name;
    }

    element.className.split(' ').forEach(function (id) {
      if (id) {
        ids[id] = id;
      }
    });

    data(element, 'possible-ids', ids);

    return ids;
  }

  // Merges the second argument into the first.
  function inherit (child, parent) {
    for (var prop in parent) {
      if (child[prop] === undefined) {
        child[prop] = parent[prop];
      }
    }

    return child;
  }

  // Creates a constructor for the specified component.
  function makeElementConstructor (id, component) {
    var isTag = component.type.indexOf(skate.types.TAG) > -1;
    var isAttr = component.type.indexOf(skate.types.ATTR) > -1;
    var isClass = component.type.indexOf(skate.types.CLASS) > -1;
    var selector = (function () {
        var selectors = [];

        if (isTag) {
          selectors.push(id);
        }

        if (isAttr) {
          selectors.push('[' + id + ']');
        }

        if (isClass) {
          selectors.push('.' + id);
        }

        return selectors.join(', ');
      }());

    function CustomElement () {
      if (!isTag) {
        throw new Error('Cannot construct "' + id + '" as a custom element.');
      }

      var element = document.createElement(id);

      component.prototype = this.constructor.prototype;
      triggerReady(id, component, element);

      return element;
    }

    CustomElement.existing = function (within) {
      return (within || document).querySelectorAll(selector);
    };

    CustomElement.selector = function () {
      return selector;
    };

    CustomElement.prototype = Object.create(component.prototype, {
      constructor: {
        enumerable: false,
        value: CustomElement
      }
    });

    return CustomElement;
  }

  // Global Setup
  // ------------

  // Rules that hide elements as they're inserted so that elements are hidden
  // prior to calling the ready callback to prevent FOUC if the component
  // modifies the element in which it is bound.
  document.getElementsByTagName('head')[0].appendChild(hiddenRules);

  // Exporting
  // ---------

  if (typeof define === 'function' && define.amd) {
    define('skate', [], function () {
      return skate;
    });
  } else {
    window.skate = skate;
  }

})();
<|endoftext|>


<commit_before>var Data = {

  starts : [
    "Experiential truth ",
    "The physical world ",
    "Non-judgment ",
    "Parsimony ",
    "Infinity ",
    "Calamity ",
    "Prosody ",
    "Unity ",
    "Good health ",
    "A clean brain ",
    "Brusque temperment ",
    "A flexible thought pattern ",
    "Insightful visage ",
    "Mindfulness in action ",
    "Compassion ",
    "Altruism ",
    "Totality ",
    "Insoucinace ",
    "Dynamic mutative creation ",
    "The graspably non-considerable ",
    "Social science ",
    "Stone age philosophy ",
    "Interpretive dance ",
    "Meditative physics ",
    "Rambunctious cause ",
    "Multitudinality ",
    "Nascent luminence ",
    "True randomness ",
    "Deconstrutionist wisdom ",
    "Science intersecting religion ",
    "The role of a leader ",
    "The soul of a calm person ",
    "The idea of God ",
    "Mechanistic reaction ",
    "Semiotic involvement ",
    "Quantum physics ",
    "Cerebrolinguistics ",
    "Dubstep as a metaphor for liberty "
  ],

  middles : [
    "nurtures ",
    "unifies ",
    "separates ",
    "distributes widely ",
    "appreciates ",
    "deprecates ",
    "conglomerates ",
    "gives meaning to ",
    "displays ",
    "permeates ",
    "clarifies ",
    "asceticizes ",
    "recapitulates ",
    "satisfies ",
    "sanctifies via ",
    "trivializes ",
    "justifies with ",
    "rarifies ",
    "codifies ",
    "socializes by way of ",
    "reclaims ",
    "disintegrates ",
    "integrates ",
    "reduces ",
    "explores ",
    "dissociates from within ",
    "exposes ",
    "builds, using ",
    "teaches through ",
    "projects onto ",
    "imparts reality to ",
    "constructs with "
  ],

  qualifiers : [
    "abundance of ",
    "ostentatious ",
    "the barrier of ",
    "self-righteous ",
    "species specific ",
    "heterodynamic ",
    "epistemological ",
    "synchronistic ",
    "timely ",
    "unpredictable ",
    "unassailable ",
    "dishonest ",
    "factual ",
    "highly descriptive ",
    "location based ",
    "describably pure ",
    "bounded ",
    "unbounded ",
    "trans-boundary ",
    "expedient ",
    "ontogenous ",
    "geopolitical ",
    "popular ",
    "advocatory ",
    "pluripotentialistic ",
    "rhythmatic ",
    "dimensional ",
    "sophist ",
    "reliquary ",
    "escapist ",
    "anarchist ",
    "conformist ",
    "erudite ",
    "miniscule ",
    "Manichean ",
    "Hadrean ",
    "media driven ",
    "global ",
    "local ",
    "post-",
    "hyper-",
    "trans-",
    "extra-",
    "super-",
    "supra-",
    "world regarding ",
    "provably optimal ",
    "potential "
  ],

  finishes : [
    "marvel.",
    "choices.",
    "harmony.",
    "phylogeny.",
    "disharmony.",
    "traversal.",
    "ostracism.",
    "semiotics.",
    "deconstructionism.",
    "post-logic.",
    "escapism.",
    "criarchy.",
    "platitudinality.",
    "creosophy.",
    "journalism.",
    "preternaturality.",
    "glossolalia.",
    "idolatry.",
    "conscience confluence.",
    "hapto-ergonomics.",
    "mental rigor.",
    "spiritual fortitude.",
    "wisdom.",
    "secret genius.",
    "inner brilliance.",
    "unseen internal gift.",
    "combobulation.",
    "transdoctrinal inference.",
    "creativity.",
    "significance.",
    "materialization.",
    "generosity.",
    "infinitude.",
    "plurality.",
    "asceticism.",
    "truism.",
    "monopoly.",
    "monosophism.",
    "polyarchy.",
    "togetherness.",
    "voluminous reaction.",
    "joyous revelation.",
    "stimulation.",
    "numbing.",
    "intuition.",
    "actions."
  ]

};

function randFrom(X) {
  return X[Math.floor(Math.random() * X.length)];
}

function makeDC() {
  return randFrom(Data.starts) + randFrom(Data.middles) + randFrom(Data.qualifiers) + randFrom(Data.finishes);
}

function go() {
  document.getElementById('txt').innerHTML = '"' + makeDC() + '"';
}
<commit_msg>Update choprifier.js<commit_after>var Data = {

  starts : [
    "Experiential truth ",
    "The physical world ",
    "Non-judgment ",
    "Parsimony ",
    "Infinity ",
    "Calamity ",
    "Prosody ",
    "Unity ",
    "Good health ",
    "A clean brain ",
    "Entreaty ",
    "Brusque temperment ",
    "A flexible thought pattern ",
    "Insightful visage ",
    "Mindfulness in action ",
    "Compassion ",
    "Altruism ",
    "Totality ",
    "Insoucinace ",
    "Dynamic mutative creation ",
    "The graspably non-considerable ",
    "Social science ",
    "Stone age philosophy ",
    "Interpretive dance ",
    "Meditative physics ",
    "Rambunctious cause ",
    "Multitudinality ",
    "Nascent luminence ",
    "True randomness ",
    "Deconstrutionist wisdom ",
    "Science intersecting religion ",
    "The role of a leader ",
    "The soul of a calm person ",
    "The idea of God ",
    "Mechanistic reaction ",
    "Semiotic involvement ",
    "Quantum physics ",
    "Cerebrolinguistics ",
    "Dubstep as a metaphor for liberty "
  ],

  middles : [
    "nurtures ",
    "unifies ",
    "separates ",
    "distributes widely ",
    "appreciates ",
    "deprecates ",
    "conglomerates ",
    "gives meaning to ",
    "displays ",
    "permeates ",
    "clarifies ",
    "asceticizes ",
    "recapitulates ",
    "satisfies ",
    "sanctifies via ",
    "trivializes ",
    "justifies with ",
    "rarifies ",
    "codifies ",
    "socializes by way of ",
    "reclaims ",
    "disintegrates ",
    "integrates ",
    "reduces ",
    "explores ",
    "dissociates from within ",
    "exposes ",
    "builds, using ",
    "teaches through ",
    "projects onto ",
    "imparts reality to ",
    "constructs with "
  ],

  qualifiers : [
    "abundance of ",
    "ostentatious ",
    "the barrier of ",
    "self-righteous ",
    "species specific ",
    "heterodynamic ",
    "epistemological ",
    "synchronistic ",
    "timely ",
    "unpredictable ",
    "unassailable ",
    "dishonest ",
    "factual ",
    "highly descriptive ",
    "location based ",
    "describably pure ",
    "bounded ",
    "unbounded ",
    "trans-boundary ",
    "expedient ",
    "ontogenous ",
    "geopolitical ",
    "popular ",
    "advocatory ",
    "pluripotentialistic ",
    "rhythmatic ",
    "dimensional ",
    "sophist ",
    "reliquary ",
    "escapist ",
    "anarchist ",
    "conformist ",
    "erudite ",
    "miniscule ",
    "Manichean ",
    "Hadrean ",
    "media driven ",
    "global ",
    "local ",
    "post-",
    "hyper-",
    "trans-",
    "extra-",
    "super-",
    "supra-",
    "world regarding ",
    "provably optimal ",
    "potential "
  ],

  finishes : [
    "marvel.",
    "choices.",
    "harmony.",
    "phylogeny.",
    "disharmony.",
    "traversal.",
    "ostracism.",
    "semiotics.",
    "deconstructionism.",
    "post-logic.",
    "escapism.",
    "criarchy.",
    "platitudinality.",
    "creosophy.",
    "journalism.",
    "preternaturality.",
    "glossolalia.",
    "idolatry.",
    "conscience confluence.",
    "hapto-ergonomics.",
    "mental rigor.",
    "spiritual fortitude.",
    "wisdom.",
    "secret genius.",
    "inner brilliance.",
    "unseen internal gift.",
    "combobulation.",
    "transdoctrinal inference.",
    "creativity.",
    "significance.",
    "materialization.",
    "generosity.",
    "infinitude.",
    "plurality.",
    "asceticism.",
    "truism.",
    "monopoly.",
    "monosophism.",
    "polyarchy.",
    "togetherness.",
    "voluminous reaction.",
    "joyous revelation.",
    "stimulation.",
    "numbing.",
    "intuition.",
    "actions."
  ]

};

function randFrom(X) {
  return X[Math.floor(Math.random() * X.length)];
}

function makeDC() {
  return randFrom(Data.starts) + randFrom(Data.middles) + randFrom(Data.qualifiers) + randFrom(Data.finishes);
}

function go() {
  document.getElementById('txt').innerHTML = '"' + makeDC() + '"';
}
<|endoftext|>


<commit_before>import { Chess } from "chess.js"

import { logger } from "~/app/index"
import { REVISION_TYPES } from "~/share/constants"

import Model, { transaction } from "./base"

import Position from "./position"

export default class Revision extends Model {
  get tableName() {
    return "revisions"
  }

  get hasTimestamps() {
    return true
  }

  static async create(game, { type, ...rest }) {
    try {
      await this[type].call(null, { game, ...rest })
    } catch({ message }) {
      logger.error(message)
    }
  }

  static async move({ game, from, to, promotion }) {
    const currentPosition = await game.currentPosition()
    const currentFen = currentPosition.get("m_fen")

    const chess = new Chess(currentFen)

    if (chess.game_over()) {
      return false
    }

    chess.move({ from, to, promotion })

    if (chess.fen() === currentFen) {
      return false
    }

    const position = new Position({
      m_fen: chess.fen(),
      move_number: currentPosition.get("move_number") + 1
    })

    await transaction(async transacting => {
      await position.save(null, { transacting })

      const revision = new Revision({
        game_id: game.get("id"),
        source_game_id: game.get("id"),
        position_id: position.get("id"),
        type: REVISION_TYPES.MOVE
      })

      await revision.save(null, { transacting })
    })

    if (chess.game_over()) {
      await game.setResult()
    }

    return true
  }
}
<commit_msg>fix missing argument on set result call<commit_after>import { Chess } from "chess.js"

import { logger } from "~/app/index"
import { REVISION_TYPES } from "~/share/constants"

import Model, { transaction } from "./base"

import Position from "./position"

export default class Revision extends Model {
  get tableName() {
    return "revisions"
  }

  get hasTimestamps() {
    return true
  }

  static async create(game, { type, ...rest }) {
    try {
      await this[type].call(null, { game, ...rest })
    } catch({ message }) {
      logger.error(message)
    }
  }

  static async move({ game, from, to, promotion }) {
    const currentPosition = await game.currentPosition()
    const currentFen = currentPosition.get("m_fen")

    const chess = new Chess(currentFen)

    if (chess.game_over()) {
      return false
    }

    chess.move({ from, to, promotion })

    if (chess.fen() === currentFen) {
      return false
    }

    const position = new Position({
      m_fen: chess.fen(),
      move_number: currentPosition.get("move_number") + 1
    })

    await transaction(async transacting => {
      await position.save(null, { transacting })

      const revision = new Revision({
        game_id: game.get("id"),
        source_game_id: game.get("id"),
        position_id: position.get("id"),
        type: REVISION_TYPES.MOVE
      })

      await revision.save(null, { transacting })
    })

    if (chess.game_over()) {
      await game.setResult(chess)
    }

    return true
  }
}
<|endoftext|>


<commit_before>describe('Memorize', function() {
  describe('when setting it on beforeAll ', function() {
    beforeAll(function() {
      this.memorize('object', function() {
        return { id: 1 };
      });

      this.memorize('object2', { id: 2 });

      this.memorize({
        object3: function() {
          return { id: 3 };
        },
        object4: { id: 4 }
      });
    });

    describe('when retrieving the value memorized with a function', function() {
      it('returns the memorized object', function() {
        expect(this.memorized('object')).toEqual({ id: 1 });
      });

      describe('when changing the value of the object within the test', function () {
        it('remember the change within the test', function() {
          expect(this.memorized('object')).toEqual({ id: 1 });
          this.memorized('object').id = 10;
          expect(this.memorized('object')).toEqual({ id: 10 });
        });

        it('does not affect another test', function() {
          expect(this.memorized('object')).toEqual({ id: 1 });
        });
      });
    });

    describe('when retrieving the value memorized with a value', function() {
      it('returns the memorized object', function() {
        expect(this.memorized('object2')).toEqual({ id: 2 });
      });
    });

    describe('when retrieving the value memorized with a function inside a hash', function() {
      it('returns the memorized object', function() {
        expect(this.memorized('object3')).toEqual({ id: 3 });
      });
    });

    describe('when retrieving the value memorized with a value inside a hash', function() {
      it('returns the memorized object', function() {
        expect(this.memorized('object4')).toEqual({ id: 4 });
      });
    });

    describe('when overriding with a beforeEach', function() {
      beforeEach(function() {
        this.memorize('object', function() {
          return { id: 5 };
        });

        this.memorize('object2', { id: 6 });

        this.memorize({
          object3: function() {
            return { id: 7 };
          },
          object4: { id: 8 }
        });
      });

      describe('when retrieving the value memorized with a function', function() {
        it('returns the memorized object', function() {
          expect(this.memorized('object')).toEqual({ id: 5 });
        });
      });

      describe('when retrieving the value memorized with a value', function() {
        it('returns the memorized object', function() {
          expect(this.memorized('object2')).toEqual({ id: 6 });
        });
      });

      describe('when retrieving the value memorized with a function inside a hash', function() {
        it('returns the memorized object', function() {
          expect(this.memorized('object3')).toEqual({ id: 7 });
        });
      });

      describe('when retrieving the value memorized with a value inside a hash', function() {
        it('returns the memorized object', function() {
          expect(this.memorized('object4')).toEqual({ id: 8 });
        });
      });

      describe('when overriding with beforeAll', function() {
        beforeAll(function() {
          this.memorize('object', function() {
            return { id: 9 };
          });

          this.memorize('object2', { id: 10 });

          this.memorize({
            object3: function() {
              return { id: 11 };
            },
            object4: { id: 12 }
          });
        });

        describe('when retrieving the value memorized with a function', function() {
          it('returns the memorized object', function() {
            expect(this.memorized('object')).toEqual({ id: 5 });
          });
        });

        describe('when retrieving the value memorized with a value', function() {
          it('returns the memorized object', function() {
            expect(this.memorized('object2')).toEqual({ id: 6 });
          });
        });

        describe('when retrieving the value memorized with a function inside a hash', function() {
          it('returns the memorized object', function() {
            expect(this.memorized('object3')).toEqual({ id: 7 });
          });
        });

        describe('when retrieving the value memorized with a value inside a hash', function() {
          it('returns the memorized object', function() {
            expect(this.memorized('object4')).toEqual({ id: 8 });
          });
        });
      });
    });

    describe('when overriding with beforeAll', function() {
      beforeAll(function() {
        this.memorize('object', function() {
          return { id: 9 };
        });

        this.memorize('object2', { id: 10 });

        this.memorize({
          object3: function() {
            return { id: 11 };
          },
          object4: { id: 12 }
        });
      });

      describe('when retrieving the value memorized with a function', function() {
        it('returns the memorized object', function() {
          expect(this.memorized('object')).toEqual({ id: 9 });
        });
      });

      describe('when retrieving the value memorized with a value', function() {
        it('returns the memorized object', function() {
          expect(this.memorized('object2')).toEqual({ id: 10 });
        });
      });

      describe('when retrieving the value memorized with a function inside a hash', function() {
        it('returns the memorized object', function() {
          expect(this.memorized('object3')).toEqual({ id: 11 });
        });
      });

      describe('when retrieving the value memorized with a value inside a hash', function() {
        it('returns the memorized object', function() {
          expect(this.memorized('object4')).toEqual({ id: 12 });
        });
      });
    });
  });

  describe('when calling memorized twice inside the same example', function() {
    beforeEach(function() {
      var context = this;
      context.called = 0;

      context.memorize('object', function() {
        context.called = context.called + 1;
        return { id: 1 };
      });
    });

    it('does not call the function twice', function () {
      this.memorized('object');
      this.memorized('object');

      expect(this.called).toEqual(1);
    });

    describe('when another memorizaton calls for the first one', function() {
      beforeEach(function() {
        this.memorize('object2', function() {
          this.object();
          return { id: 2 };
        });
      });

      xit('does not call the function twice', function () {
        this.memorized('object2');
        this.memorized('object');

        expect(this.called).toEqual(1);
      });
    });
  });

  describe('when calling memorized value within a memorized function', function() {
    describe('defining everything on a beforeAll', function() {
      beforeAll(function() {
        this.memorize({
          object: function() {
            return { id: 100 };
          },
          object2: function() {
            return { obj1: this.object() };
          }
        });
      });

      it('returns the inner object in the inner call', function() {
        expect(this.memorized('object2')).toEqual({ obj1: { id: 100 } });
      });

      describe('when overriding inner object', function() {
        describe('in a beforeEach', function() {
          beforeEach(function() {
            this.memorize('object', function() {
              return { id: 101 };
            });
          });

          it('accepts the override', function() {
            expect(this.memorized('object2')).toEqual({ obj1: { id:101 } });
          });
        });

        describe('in a beforeAll', function() {
          beforeAll(function() {
            this.memorize('object', function() {
              return { id: 102 };
            });
          });

          it('accepts the override', function() {
            expect(this.memorized('object2')).toEqual({ obj1: { id:102 } });
          });
        });
      });
    });

    describe('defining everything on a beforeEach', function() {
      beforeEach(function() {
        this.memorize({
          object: function() {
            return { id: 100 };
          },
          object2: function() {
            return { obj1: this.object() };
          }
        });
      });

      it('returns the inner object in the inner call', function() {
        expect(this.memorized('object2')).toEqual({ obj1: { id: 100 } });
      });

      describe('when overriding inner object', function() {
        describe('in a beforeEach', function() {
          beforeEach(function() {
            this.memorize('object', function() {
              return { id: 101 };
            });
          });

          it('accepts the override', function() {
            expect(this.memorized('object2')).toEqual({ obj1: { id:101 } });
          });
        });

        describe('in a beforeAll', function() {
          beforeAll(function() {
            this.memorize('object', function() {
              return { id: 102 };
            });
          });

          it('does not accept the override', function() {
            expect(this.memorized('object2')).toEqual({ obj1: { id:100 } });
          });
        });
      });
    });

    describe('defining the main object in a beforeEach', function() {
      beforeEach(function() {
        this.memorize('object2', function() {
          return { obj1: this.object() };
        });
      });

      describe('when setting inner object', function() {
        describe('in a beforeEach', function() {
          beforeEach(function() {
            this.memorize('object', function() {
              return { id: 101 };
            });
          });

          it('accepts the value', function() {
            expect(this.memorized('object2')).toEqual({ obj1: { id:101 } });
          });
        });

        describe('in a beforeAll', function() {
          beforeAll(function() {
            this.memorize('object', function() {
              return { id: 102 };
            });
          });

          it('accepts the value', function() {
            expect(this.memorized('object2')).toEqual({ obj1: { id:102 } });
          });
        });
      });
    });

    describe('defining the main object in a beforeAll', function() {
      beforeAll(function() {
        this.memorize('object2', function() {
          return { obj1: this.object() };
        });
      });

      describe('when setting inner object', function() {
        describe('in a beforeEach', function() {
          beforeEach(function() {
            this.memorize('object', function() {
              return { id: 101 };
            });
          });

          it('accepts the value', function() {
            expect(this.memorized('object2')).toEqual({ obj1: { id:101 } });
          });
        });

        describe('in a beforeAll', function() {
          beforeAll(function() {
            this.memorize('object', function() {
              return { id: 102 };
            });
          });

          it('accepts the value', function() {
            expect(this.memorized('object2')).toEqual({ obj1: { id:102 } });
          });
        });
      });
    });
  });
});
<commit_msg>Enable failing spec<commit_after>describe('Memorize', function() {
  describe('when setting it on beforeAll ', function() {
    beforeAll(function() {
      this.memorize('object', function() {
        return { id: 1 };
      });

      this.memorize('object2', { id: 2 });

      this.memorize({
        object3: function() {
          return { id: 3 };
        },
        object4: { id: 4 }
      });
    });

    describe('when retrieving the value memorized with a function', function() {
      it('returns the memorized object', function() {
        expect(this.memorized('object')).toEqual({ id: 1 });
      });

      describe('when changing the value of the object within the test', function () {
        it('remember the change within the test', function() {
          expect(this.memorized('object')).toEqual({ id: 1 });
          this.memorized('object').id = 10;
          expect(this.memorized('object')).toEqual({ id: 10 });
        });

        it('does not affect another test', function() {
          expect(this.memorized('object')).toEqual({ id: 1 });
        });
      });
    });

    describe('when retrieving the value memorized with a value', function() {
      it('returns the memorized object', function() {
        expect(this.memorized('object2')).toEqual({ id: 2 });
      });
    });

    describe('when retrieving the value memorized with a function inside a hash', function() {
      it('returns the memorized object', function() {
        expect(this.memorized('object3')).toEqual({ id: 3 });
      });
    });

    describe('when retrieving the value memorized with a value inside a hash', function() {
      it('returns the memorized object', function() {
        expect(this.memorized('object4')).toEqual({ id: 4 });
      });
    });

    describe('when overriding with a beforeEach', function() {
      beforeEach(function() {
        this.memorize('object', function() {
          return { id: 5 };
        });

        this.memorize('object2', { id: 6 });

        this.memorize({
          object3: function() {
            return { id: 7 };
          },
          object4: { id: 8 }
        });
      });

      describe('when retrieving the value memorized with a function', function() {
        it('returns the memorized object', function() {
          expect(this.memorized('object')).toEqual({ id: 5 });
        });
      });

      describe('when retrieving the value memorized with a value', function() {
        it('returns the memorized object', function() {
          expect(this.memorized('object2')).toEqual({ id: 6 });
        });
      });

      describe('when retrieving the value memorized with a function inside a hash', function() {
        it('returns the memorized object', function() {
          expect(this.memorized('object3')).toEqual({ id: 7 });
        });
      });

      describe('when retrieving the value memorized with a value inside a hash', function() {
        it('returns the memorized object', function() {
          expect(this.memorized('object4')).toEqual({ id: 8 });
        });
      });

      describe('when overriding with beforeAll', function() {
        beforeAll(function() {
          this.memorize('object', function() {
            return { id: 9 };
          });

          this.memorize('object2', { id: 10 });

          this.memorize({
            object3: function() {
              return { id: 11 };
            },
            object4: { id: 12 }
          });
        });

        describe('when retrieving the value memorized with a function', function() {
          it('returns the memorized object', function() {
            expect(this.memorized('object')).toEqual({ id: 5 });
          });
        });

        describe('when retrieving the value memorized with a value', function() {
          it('returns the memorized object', function() {
            expect(this.memorized('object2')).toEqual({ id: 6 });
          });
        });

        describe('when retrieving the value memorized with a function inside a hash', function() {
          it('returns the memorized object', function() {
            expect(this.memorized('object3')).toEqual({ id: 7 });
          });
        });

        describe('when retrieving the value memorized with a value inside a hash', function() {
          it('returns the memorized object', function() {
            expect(this.memorized('object4')).toEqual({ id: 8 });
          });
        });
      });
    });

    describe('when overriding with beforeAll', function() {
      beforeAll(function() {
        this.memorize('object', function() {
          return { id: 9 };
        });

        this.memorize('object2', { id: 10 });

        this.memorize({
          object3: function() {
            return { id: 11 };
          },
          object4: { id: 12 }
        });
      });

      describe('when retrieving the value memorized with a function', function() {
        it('returns the memorized object', function() {
          expect(this.memorized('object')).toEqual({ id: 9 });
        });
      });

      describe('when retrieving the value memorized with a value', function() {
        it('returns the memorized object', function() {
          expect(this.memorized('object2')).toEqual({ id: 10 });
        });
      });

      describe('when retrieving the value memorized with a function inside a hash', function() {
        it('returns the memorized object', function() {
          expect(this.memorized('object3')).toEqual({ id: 11 });
        });
      });

      describe('when retrieving the value memorized with a value inside a hash', function() {
        it('returns the memorized object', function() {
          expect(this.memorized('object4')).toEqual({ id: 12 });
        });
      });
    });
  });

  describe('when calling memorized twice inside the same example', function() {
    beforeEach(function() {
      var context = this;
      context.called = 0;

      context.memorize('object', function() {
        context.called = context.called + 1;
        return { id: 1 };
      });
    });

    it('does not call the function twice', function () {
      this.memorized('object');
      this.memorized('object');

      expect(this.called).toEqual(1);
    });

    describe('when another memorizaton calls for the first one', function() {
      beforeEach(function() {
        this.memorize('object2', function() {
          this.object();
          return { id: 2 };
        });
      });

      it('does not call the function twice', function () {
        this.memorized('object2');
        this.memorized('object');

        expect(this.called).toEqual(1);
      });
    });
  });

  describe('when calling memorized value within a memorized function', function() {
    describe('defining everything on a beforeAll', function() {
      beforeAll(function() {
        this.memorize({
          object: function() {
            return { id: 100 };
          },
          object2: function() {
            return { obj1: this.object() };
          }
        });
      });

      it('returns the inner object in the inner call', function() {
        expect(this.memorized('object2')).toEqual({ obj1: { id: 100 } });
      });

      describe('when overriding inner object', function() {
        describe('in a beforeEach', function() {
          beforeEach(function() {
            this.memorize('object', function() {
              return { id: 101 };
            });
          });

          it('accepts the override', function() {
            expect(this.memorized('object2')).toEqual({ obj1: { id:101 } });
          });
        });

        describe('in a beforeAll', function() {
          beforeAll(function() {
            this.memorize('object', function() {
              return { id: 102 };
            });
          });

          it('accepts the override', function() {
            expect(this.memorized('object2')).toEqual({ obj1: { id:102 } });
          });
        });
      });
    });

    describe('defining everything on a beforeEach', function() {
      beforeEach(function() {
        this.memorize({
          object: function() {
            return { id: 100 };
          },
          object2: function() {
            return { obj1: this.object() };
          }
        });
      });

      it('returns the inner object in the inner call', function() {
        expect(this.memorized('object2')).toEqual({ obj1: { id: 100 } });
      });

      describe('when overriding inner object', function() {
        describe('in a beforeEach', function() {
          beforeEach(function() {
            this.memorize('object', function() {
              return { id: 101 };
            });
          });

          it('accepts the override', function() {
            expect(this.memorized('object2')).toEqual({ obj1: { id:101 } });
          });
        });

        describe('in a beforeAll', function() {
          beforeAll(function() {
            this.memorize('object', function() {
              return { id: 102 };
            });
          });

          it('does not accept the override', function() {
            expect(this.memorized('object2')).toEqual({ obj1: { id:100 } });
          });
        });
      });
    });

    describe('defining the main object in a beforeEach', function() {
      beforeEach(function() {
        this.memorize('object2', function() {
          return { obj1: this.object() };
        });
      });

      describe('when setting inner object', function() {
        describe('in a beforeEach', function() {
          beforeEach(function() {
            this.memorize('object', function() {
              return { id: 101 };
            });
          });

          it('accepts the value', function() {
            expect(this.memorized('object2')).toEqual({ obj1: { id:101 } });
          });
        });

        describe('in a beforeAll', function() {
          beforeAll(function() {
            this.memorize('object', function() {
              return { id: 102 };
            });
          });

          it('accepts the value', function() {
            expect(this.memorized('object2')).toEqual({ obj1: { id:102 } });
          });
        });
      });
    });

    describe('defining the main object in a beforeAll', function() {
      beforeAll(function() {
        this.memorize('object2', function() {
          return { obj1: this.object() };
        });
      });

      describe('when setting inner object', function() {
        describe('in a beforeEach', function() {
          beforeEach(function() {
            this.memorize('object', function() {
              return { id: 101 };
            });
          });

          it('accepts the value', function() {
            expect(this.memorized('object2')).toEqual({ obj1: { id:101 } });
          });
        });

        describe('in a beforeAll', function() {
          beforeAll(function() {
            this.memorize('object', function() {
              return { id: 102 };
            });
          });

          it('accepts the value', function() {
            expect(this.memorized('object2')).toEqual({ obj1: { id:102 } });
          });
        });
      });
    });
  });
});
<|endoftext|>