Commits

Mikhail Korobov  committed 7f6ea65

initial import

  • Participants

Comments (0)

Files changed (26)

+syntax: glob
+
+#projects
+.settings/*
+.project
+.pydevproject
+.cache/*
+nbproject/*
+.buildpath
+build.properties
+MANIFEST.MF
+
+#temp files
+*.pyc
+*.pyo
+*.orig
+*~
+stuff/
+
+#os files
+.DS_Store
+Thumbs.db
+
+#setup files
+build/
+dist/
+MANIFEST
+django_mootools_behavior.egg-info
+Copyright (c) 2011 Mikhail Korobov
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+include *.txt
+include *.rst
+recursive-include mootools_behavior/static *.js
+========================
+django-mootools-behavior
+========================
+
+Utilities for https://github.com/anutron/behavior integration with django.
+
+This app provides template filters that can alter data attributes of the form
+fields required for behavior. It also have a 'behavior' and 'behavior-more'
+libraries bundled for use with django.contrib.staticfiles.
+
+The license is MIT.
+
+Installation
+============
+
+::
+
+    pip install django-mootools-behavior
+
+Configuration
+=============
+
+1. Add 'mootools_behavior' to INSTALLED_APPS;
+2. (optional) run ``python manage.py collectstatic`` in order to get 'behavior'
+   javascript files;
+3. include the necessary js into html and use provided template filters
+   in order to set attributes for form elements.
+
+Filters
+=======
+
+behave
+------
+
+Sets 'data-behavior-...' attribute to a form field::
+
+    {% load mootools_behavior %}
+
+    <!-- data-behavior:"OverText" will be added to input field -->
+    {{ form.title|behave:"OverText" }}
+
+
+set_data
+--------
+
+Sets HTML5 data attribute ( http://ejohn.org/blog/html-5-data-attributes/ ).
+
+Example::
+
+    {% load mootools_behavior %}
+
+    <!-- data-behavior:"OverText" will be added to input field -->
+    {{ form.title|set_data:"behavior:OverText" }}
+
+Contributing
+============
+
+If you've found a bug, implemented a feature or have a suggestion,
+do not hesitate to contact me, fire an issue or send a pull request.
+
+Source code:
+
+* https://bitbucket.org/kmike/django-mootools-behavior/
+* https://github.com/kmike/django-mootools-behavior/
+
+Report bugs:
+
+* https://bitbucket.org/kmike/django-mootools-behavior/issues
+* https://github.com/kmike/django-mootools-behavior/issues

File mootools_behavior/__init__.py

Empty file added.

File mootools_behavior/assets.py

+# -*- coding: utf-8 -*-
+# predefined bundles for webassets (https://github.com/miracle2k/webassets)
+
+from django_assets import Bundle
+
+behavior_core = Bundle(
+    'mootools_behavior/Behavior/Element.Data.js',
+    'mootools_behavior/Behavior/BehaviorAPI.js',
+    'mootools_behavior/Behavior/Behavior.js',
+)

File mootools_behavior/models.py

+# Hello, django! Please, load my template tags.

File mootools_behavior/static/mootools_behavior/Behavior.More/Delegators/Delegator.AddRemoveClass.js

+/*
+---
+description: Provides methods to add/remove/toggle a class on a given target.
+provides: [Delegator.ToggleClass, Delegator.AddClass, Delegator.RemoveClass]
+requires: [Behavior/Delegator, Core/Element]
+script: Delegator.AddRemoveClass.js
+name: Delegator.AddRemoveClass
+
+...
+*/
+(function(){
+
+	var triggers = {};
+
+	['add', 'remove', 'toggle'].each(function(action){
+
+		triggers[action + 'Class'] = {
+			require: ['class'],
+			handler: function(event, link, api){
+				var target = link;
+				if (api.get('target')) {
+					target = link.getElement(api.get('target'));
+					if (!target) api.fail('could not locate target element to ' + action + ' its class', link);
+				}
+				target[action + 'Class'](api.get('class'));
+			}
+		};
+
+	});
+
+	Delegator.register('click', triggers);
+
+})();

File mootools_behavior/static/mootools_behavior/Behavior.More/Delegators/Delegator.Ajax.js

+/*
+---
+description: Provides functionality for links that load content into a target element via ajax.
+provides: [Delegator.Ajax]
+requires: [Behavior/Delegator, Core/Request.HTML, More/Spinner]
+script: Delegator.Ajax.js
+name: Delegator.Ajax
+...
+*/
+
+(function(){
+
+	Delegator.register('click', 'Ajax', {
+		require: ['target'],
+		defaults: {
+			action: 'injectBottom'
+		},
+		handler: function(event, link, api){
+			var target,
+				action = api.get('action'),
+				selector = api.get('target');
+			if (selector) {
+				if (selector == "self") {
+					target = element;
+				} else {
+					target = link.getElement(selector);
+				}
+			}
+
+			if (!target) api.fail('ajax trigger error: element matching selector %s was not found', selector);
+
+			var requestTarget = new Element('div');
+
+			var spinnerTarget = api.get('spinner-target');
+			if (spinnerTarget) spinnerTarget = link.getElement(spinnerTarget);
+
+			event.preventDefault();
+			new Request.HTML(
+				Object.cleanValues({
+					method: 'get',
+					evalScripts: api.get('evalScripts'),
+					url: api.get('href') || link.get('href'),
+					spinnerTarget: spinnerTarget || target,
+					useSpinner: api.getAs(Boolean, 'useSpinner'),
+					update: requestTarget,
+					onSuccess: function(){
+						//reverse the elements and inject them
+						//reversal is required since it injects each after the target
+						//pushing down the previously added element
+						var elements = requestTarget.getChildren();
+						if (api.get('filter')){
+							elements = new Element('div').adopt(elements).getElements(api.get('filter'));
+						}
+						switch(action){
+							case 'replace':
+								var container = target.getParent();
+								elements.reverse().injectAfter(target);
+								this.fireEvent('destroyDom', target);
+								target.destroy();
+								this.fireEvent('ammendDom', [container, elements]);
+								break;
+							case 'update':
+								this.fireEvent('destroyDom', target.getChildren());
+								target.empty();
+								elements.inject(target);
+								this.fireEvent('ammendDom', [target, elements]);
+								break;
+							default:
+								//injectTop, injectBottom, injectBefore, injectAfter
+								if (action == "injectTop" || action == "injectAfter") elements.reverse();
+								elements[action](target);
+								this.fireEvent('ammendDom', [target, elements]);
+						}
+					}.bind(this)
+				})
+			).send();
+		}
+	});
+
+})();
+
+

File mootools_behavior/static/mootools_behavior/Behavior.More/Delegators/Delegator.CheckAllOrNone.js

+/*
+---
+description: Checks all or none of a group of checkboxes.
+provides: [Delegator.CheckAllOrNone]
+requires: [Behavior/Delegator]
+script: Delegator.CheckAllOrNone.js
+name: Delegator.CheckAllOrNone
+
+...
+*/
+
+Delegator.register('click', {
+
+	'checkAll': {
+		require: ['targets'],
+		handler: function(event, link, api){
+			var targets = link.getElements(api.get('targets'));
+			if (targets.length) targets.set('checked', true);
+			else api.warn('There were no inputs found to check.');
+		}
+	},
+
+	'checkNone': {
+		require: ['targets'],
+		handler: function(event, link, api){
+			var targets = link.getElements(api.get('targets'));
+			if (targets.length) targets.set('checked', false);
+			else api.warn('There were no inputs found to uncheck.');
+		}
+	}
+
+});

File mootools_behavior/static/mootools_behavior/Behavior.More/Delegators/Delegator.SubmitLink.js

+/*
+---
+description: When the user clicks a link with this delegator, submit the target form.
+provides: [Delegator.SubmitLink]
+requires: [Behavior/Delegator]
+script: Delegator.SubmitLink.js
+name: Delegator.SubmitLink
+
+...
+*/
+
+(function(){
+
+	var injectValues = function(form, data){
+		var injected = new Elements();
+		Object.each(data, function(value, key){
+			if (typeOf(value) == 'array'){
+				value.each(function(val){
+					injected.push(
+						new Element('input', {
+							type: 'hidden',
+							name: key,
+							value: val
+						}).inject(form)
+					);
+				});
+			} else {
+				new Element('input', {
+					type: 'hidden',
+					name: key,
+					value: value
+				}).inject(form);
+			}
+		});
+		return injected;
+	};
+
+	Delegator.register('click', {
+
+		'submitLink': function(event, el, api){
+			var formSelector = api.get('form') || '!form';
+			var form = el.getElement(formSelector);
+			if (!form) api.fail('Cannot find target form: "' +formSelector+ '" for submitLink delegator.');
+			var rq = form.retrieve('form.request');
+			var extraData = api.getAs(Object, 'extra-data');
+			var injected;
+			if (extraData) injected = injectValues(form, extraData);
+			if (rq) rq.send();
+			else form.submit();
+			if (injected) injected.destroy();
+		}
+
+	});
+
+})();

File mootools_behavior/static/mootools_behavior/Behavior.More/Drag/Behavior.Resizable.js

+/*
+---
+description: Creates instances of Drag for resizable elements.
+provides: [Behavior.Resizable]
+requires: [Behavior/Behavior, More/Drag]
+script: Behavior.Resizable.js
+name: Behavior.Resizable
+...
+*/
+Behavior.addGlobalFilter('Resizable', {
+	//deprecated options
+	deprecated: {
+		handle: 'resize-handle',
+		child: 'resize-child'
+	},
+	deprecatedAsJSON: {
+		modifiers: 'resize-modifiers'
+	},
+	setup: function(element, api){
+		var options = {};
+		if (api.get('handle')) options.handle = element.getElement(api.get('handle'));
+		if (api.get('modifiers')) options.modifiers = api.getAs(Object, 'modifiers');
+		var target = element;
+		if (api.get('child')) target = element.getElement(api.get('child'));
+		var drag = target.makeResizable(options);
+		api.onCleanup(drag.detach.bind(drag));
+		return drag;
+	}
+
+});

File mootools_behavior/static/mootools_behavior/Behavior.More/Drag/Behavior.Sortable.js

+/*
+---
+description: Creates instances of Sortable for sortable lists, optionally updating an input or element with the sort state.
+provides: [Behavior.Sortable]
+requires: [Behavior/Behavior, More/Sortables, More/Scroller]
+script: Behavior.HtmlTable.js
+name: Behavior.HtmlTable
+...
+*/
+(function(){
+
+Behavior.addGlobalFilter('Sortable',  {
+
+	defaults: {
+		clone: true,
+		opacity: 0.6
+	},
+	deprecated: {
+		lists: 'sort-lists',
+		state: 'sort-state',
+		property: 'sort-property',
+		'property-child': 'property-child'
+	},
+	setup: function(element, api){
+		//get the list selector
+		var lists = api.get('lists');
+		//if present, get the elements
+		if (lists) lists = element.getElements(lists);
+		//else the target element is the list
+		else lists = element;
+		//get the state target; this is the element (typically an input) to put the new state value on change
+		var target = api.get('state');
+		if (target) target = element.getParent().getElement(target);
+
+		//get the property to read from each sorted element
+		var property = api.get('property');
+		//if the value is on a child element, a selector is specified to find it (see docs)
+		var property_child = api.get('property-child');
+	
+		var scrollParent;
+		var sort = new Sortables(lists, {
+			clone: api.getAs(Boolean, 'clone'),
+			opacity: api.getAs(Number, 'opacity'),
+			onStart: function(element, clone){
+				clone.addClass('clone');
+				var scroller,
+				    scrollElement = isScrollable(element) ? element : getScrollParent(element);
+				if (scrollElement && scrollElement != scrollParent) {
+					scroller = new Scroller(scrollElement);
+					scrollElement.store('behavior:scroller', scroller);
+					scrollParent = scrollElement;
+				} else {
+					if (scrollParent) scroller = scrollParent.retrieve('behavior:scroller');
+				}
+				if (scroller) scroller.attach();
+			},
+			onComplete: function(){
+				if (target) {
+					target.set(target.get('tag') == 'input' ? 'value' : 'html', sort.serialize(function(item){
+						if (property_child) item = item.getElement(property_child);
+						var isInput = ['input', 'textarea', 'select'].contains(item.get('tag'));
+						return item.get(property || 'name') || isInput ? item.get('value') : item.get('id');
+					}).join(','));
+				}
+				if (scrollParent) scrollParent.retrieve('behavior:scroller').detach();
+			}
+		});
+		api.onCleanup(sort.detach.bind(sort));
+		return sort;
+	}
+
+});
+
+var isBody = function(element){
+	return (/^(?:body|html)$/i).test(element.tagName);
+};
+
+var isScrollable = function(element){
+	return ['scroll', 'auto'].contains(element.getStyle('overflow'));
+};
+
+var getScrollParent = function(element){
+	var scrollParent,
+	    parent = element.getParent();
+	while (!scrollParent){
+		if (isBody(parent) || isScrollable(parent)){
+			scrollParent = parent;
+		} else {
+			parent = parent.getParent();
+		}
+	}
+	return scrollParent;
+};
+
+})();

File mootools_behavior/static/mootools_behavior/Behavior.More/Forms/Behavior.FormRequest.js

+/*
+---
+description: Makes form elements with a FormRequest data filter automatically update via Ajax.
+provides: [Behavior.FormRequest]
+requires: [Behavior/Behavior, More/Form.Request, Behavior/Element.Data]
+script: Behavior.FormRequest.js
+name: Behavior.FormRequest
+...
+*/
+
+Behavior.addGlobalFilter('FormRequest', {
+	defaults: {
+		resetForm: true
+	},
+	setup: function(element, api){
+		var updateElement,
+		    update = api.get('update');
+		if (update =="self") {
+			updateElement = element;
+		} else {
+			updateElement = element.getElement(update);
+		}
+
+		if (!updateElement) api.fail('Could not find target element for form update');
+
+		//pass null for the update element argument; JFrame does our updating for us
+		var req = new Form.Request(element, updateElement, {
+			requestOptions: {
+				filter: api.get('filter'),
+				spinnerTarget: updateElement
+			},
+			resetForm: api.get('resetForm') || /* noReset is deprecated: */ !element.hasClass('noReset')
+		});
+		api.onCleanup(req.detach.bind(req));
+		return req;
+	}
+
+});

File mootools_behavior/static/mootools_behavior/Behavior.More/Forms/Behavior.FormValidator.js

+/*
+---
+description: Adds an instance of Form.Validator.Inline to any form with the class .form-validator.
+provides: [Behavior.FormValidator]
+requires: [Behavior/Behavior, More/Form.Validator.Inline, More/Object.Extras]
+script: Behavior.FormValidator.js
+name: Behavior.FormValidator
+...
+*/
+
+Behavior.addGlobalFilter('FormValidator', {
+	defaults: {
+		useTitles: true,
+		scrollToErrorsOnSubmit: true,
+		scrollToErrorsOnBlur: false,
+		scrollToErrorsOnChange: false,
+		ignoreHidden: true,
+		ignoreDisabled: true,
+		useTitles: false,
+		evaluateOnSubmit: true,
+		evaluateFieldsOnBlur: true,
+		evaluateFieldsOnChange: true,
+		serial: true,
+		stopOnFailure: true
+	},
+	setup: function(element, api) {
+		//instantiate the form validator
+		var validator = element.retrieve('validator');
+		if (!validator) {
+			validator = new Form.Validator.Inline(element, 
+				Object.cleanValues(
+					api.getAs({
+						useTitles: Boolean,
+						scrollToErrorsOnSubmit: Boolean,
+						scrollToErrorsOnBlur: Boolean,
+						scrollToErrorsOnChange: Boolean,
+						ignoreHidden: Boolean,
+						ignoreDisabled: Boolean,
+						useTitles: Boolean,
+						evaluateOnSubmit: Boolean,
+						evaluateFieldsOnBlur: Boolean,
+						evaluateFieldsOnChange: Boolean,
+						serial: Boolean,
+						stopOnFailure: Boolean
+					})
+				)
+			);
+		}
+		//if the api provides a getScroller method, which should return an instance of
+		//Fx.Scroll, use it instead
+		if (api.getScroller) {
+			validator.setOptions({
+				onShow: function(input, advice, className) {
+					api.getScroller().toElement(input);
+				},
+				scrollToErrorsOnSubmit: false
+			});
+		}
+		return validator;
+	}
+
+});

File mootools_behavior/static/mootools_behavior/Behavior.More/Forms/Behavior.OverText.js

+/*
+---
+description: Sets up an input to have an OverText instance for inline labeling. This is a global filter.
+provides: [Behavior.OverText]
+requires: [Behavior/Behavior, More/OverText]
+script: Behavior.OverText.js
+name: Behavior.OverText
+...
+*/
+Behavior.addGlobalFilter('OverText', function(element, api){
+
+	//create the overtext instance
+	var ot = new OverText(element);
+	element.get('class').split(' ').each(function(cls) {
+		if (cls) ot.text.addClass('overText-'+cls);
+	});
+	element.getBehaviors().each(function(filter){
+		if (filter != "OverText") ot.text.addClass('overText-'+filter);
+	});
+
+	//this method updates the text position with a slight delay
+	var updater = function(){
+		ot.reposition.delay(10, ot);
+	};
+
+	//update the position whenever the behavior element is shown
+	api.addEvent('layout:display', updater);
+
+	api.onCleanup(function(){
+		api.removeEvent('layout:display', updater);
+		ot.destroy();
+	});
+	return ot;
+
+});

File mootools_behavior/static/mootools_behavior/Behavior.More/Fx/Behavior.FxAccordion.js

+/*
+---
+description: Creates an Fx.Accordion from any element with Accordion in its data-behavior property.  Uses the .toggle elements within the element as the toggles and the .target elements as the targets. 
+provides: [Behavior.Accordion, Behavior.FxAccordion]
+requires: [Behavior/Behavior, More/Fx.Accordion, Behavior/Element.Data, More/Object.Extras]
+script: Behavior.Accordion.js
+name: Behavior.Accordion
+...
+*/
+
+Behavior.addGlobalFilter('Accordion', {
+	deprecated: {
+		headers:'toggler-elements',
+		sections:'section-elements'
+	},
+	defaults: {
+		// defaults from Fx.Accordion:
+		display: 0,
+		height: true,
+		width: false,
+		opacity: true,
+		alwaysHide: false,
+		trigger: 'click',
+		initialDisplayFx: true,
+		resetHeight: true,
+		headers: '.header',
+		sections: '.section'
+	},
+	setup: function(element, api){
+		var accordion = new Fx.Accordion(element.getElements(api.get('headers')), element.getElements(api.get('sections')),
+			Object.cleanValues(
+				Object.merge(api.getAs({
+					fixedHeight: Number,
+					fixedWidth: Number,
+					display: Number,
+					show: Number,
+					height: Boolean,
+					width: Boolean,
+					opacity: Boolean,
+					alwaysHide: Boolean,
+					trigger: String,
+					initialDisplayFx: Boolean,
+					resetHeight: Boolean
+				}), {
+					opacity: api.getAs(Boolean, 'fade'),
+					height: api.get('orientation') == 'vertical',
+					width: api.get('orientation') == 'horizontal'
+				})
+			)
+		);
+		api.onCleanup(accordion.detach.bind(accordion));
+		return accordion;
+	}
+});

File mootools_behavior/static/mootools_behavior/Behavior.More/Interface/Behavior.HtmlTable.js

+/*
+---
+description: Creates instances of HtmlTable for tables with the HtmlTable filter
+provides: [Behavior.HtmlTable]
+requires: [Behavior/Behavior, More/HtmlTable.Sort, More/HtmlTable.Zebra, More/HtmlTable.Select, More/HtmlTable.Tree, More/HtmlTable.Resize, More/Object.Extras]
+script: Behavior.HtmlTable.js
+name: Behavior.HtmlTable
+...
+*/
+
+/*
+	Refactor HtmlTable.Sort functionality:
+	don't detect the parsers on startup
+	wait for click
+	unless the option says to sort on startup
+*/
+
+HtmlTable = Class.refactor(HtmlTable, {
+	detectParsers: function(){
+		//if we are parsing on startup, then set ready to true
+		if (this.options.sortOnStartup) this._readyToParse = true;
+		//otherwise, don't parse until ready
+		if (this._readyToParse) {
+			this._parsed = true;
+			return this.previous();
+		}
+	},
+	headClick: function(event, el){
+		//on click, if we haven't parsed, set ready to true and run the parser
+		if (!this._parsed) {
+			this._readyToParse = true;
+			this.setParsers();
+		}
+		this.previous(event, el);
+	},
+	sort: function(index, reverse, pre){
+		//don't sort if we haven't parsed; this prevents sorting on startup
+		if (this._parsed) return this.previous(index, reverse, pre);
+	}
+});
+
+
+Behavior.addGlobalFilter('HtmlTable', {
+
+	deprecatedAsJSON: {
+		resize: 'table-resize'
+	},
+
+	defaults: {
+		classNoSort: 'noSort'
+	},
+
+	setup: function(element, api){
+		//make all data tables sortable
+		var firstSort;
+		element.getElements('thead th').each(function(th, i){
+			if (firstSort == null && !th.hasClass('noSort')) firstSort = i;
+			if (th.hasClass('defaultSort')) firstSort = i;
+		});
+		api.setDefault('firstSort', firstSort);
+		var multiselectable = api.getAs(Boolean, 'multiselect', element.hasClass('multiselect'));
+		var table = new HtmlTable(element,
+			Object.cleanValues({
+				parsers: api.getAs(Array, 'parsers'),
+				sortOnStartup: api.getAs(Boolean, 'sortOnStartup'),
+				sortIndex: api.getAs(Number, 'firstSort'),
+				sortable: api.getAs(Boolean, 'sortable', /* deprecated default: */ element.hasClass('sortable') && !element.hasClass('treeview')),
+				classNoSort: api.get('noSort'),
+				selectable: api.getAs(Boolean, 'selectable', /* deprecated default: */ element.hasClass('selectable') || multiselectable),
+				allowMultiSelect: multiselectable,
+				useKeyboard: api.getAs(Boolean, 'useKeybaord', /* deprecated default: */ !element.hasClass('noKeyboard')),
+				enableTree: api.getAs(Boolean, 'enableTree', /* deprecated default: */ element.hasClass('treeView')),
+				resizable: api.getAs(Boolean, 'resizable', /* deprecated default: */ element.hasClass('resizable')),
+				resize: api.getAs(Boolean, 'resize'),
+				build: api.getAs(Boolean, 'build', /* deprecated default: */ element.hasClass('buildTree'))
+			})
+		);
+		api.onCleanup(function(){
+			if (table.keyboard) table.keyboard.relinquish();
+		});
+		// Hack to make tables not jump around in Chrome.
+		if (Browser.Engine.webkit) {
+			var width = element.style.width;
+			element.setStyle('width', '99%');
+			(function() {
+				element.style.width = width;
+			}).delay(1);
+		}
+		return table;
+	}
+
+});
+
+HtmlTable.defineParsers({
+	//A parser to allow numeric sorting by any value.
+	dataSortNumeric: {
+		match: /data-sort-numeric/,
+		convert: function() {
+			return this.getElement('[data-sort-numeric]').getData('sort-numeric').toInt();
+		},
+		number: true
+	},
+	//A parser to allow lexicographical sorting by any string.
+	dataSortString: {
+		match: /data-sort-string/,
+		convert: function() {
+			return this.getElement('[data-sort-string]').getData('sort-string');
+		},
+		number: false 
+	}
+});

File mootools_behavior/static/mootools_behavior/Behavior/Behavior.js

+/*
+---
+name: Behavior
+description: Auto-instantiates widgets/classes based on parsed, declarative HTML.
+requires: [Core/Class.Extras, Core/Element.Event, Core/Selectors, More/Table, /Element.Data, /BehaviorAPI]
+provides: [Behavior]
+...
+*/
+
+(function(){
+
+	var getLog = function(method){
+		return function(){
+			if (window.console && console[method]){
+				if(console[method].apply) console[method].apply(console, arguments);
+				else console[method](Array.from(arguments).join(' '));
+			}
+		};
+	};
+
+	var PassMethods = new Class({
+		//pass a method pointer through to a filter
+		//by default the methods for add/remove events are passed to the filter
+		//pointed to this instance of behavior. you could use this to pass along
+		//other methods to your filters. For example, a method to close a popup
+		//for filters presented inside popups.
+		passMethod: function(method, fn){
+			if (this.API.prototype[method]) throw new Error('Cannot overwrite API method ' + method + ' as it already exists');
+			this.API.implement(method, fn);
+			return this;
+		},
+
+		passMethods: function(methods){
+			for (method in methods) this.passMethod(method, methods[method]);
+			return this;
+		}
+
+	});
+
+	var spaceOrCommaRegex = /\s*,\s*|\s+/g;
+
+	BehaviorAPI.implement({
+		deprecate: function(deprecated, asJSON){
+			var set,
+			    values = {};
+			Object.each(deprecated, function(prop, key){
+				var value = this.element[ asJSON ? 'getJSONData' : 'getData'](prop);
+				if (value !== undefined){
+					set = true;
+					values[key] = value;
+				}
+			}, this);
+			this.setDefault(values);
+			return this;
+		}
+	});
+
+	this.Behavior = new Class({
+
+		Implements: [Options, Events, PassMethods],
+
+		options: {
+			//by default, errors thrown by filters are caught; the onError event is fired.
+			//set this to *true* to NOT catch these errors to allow them to be handled by the browser.
+			// breakOnErrors: false,
+			// container: document.body,
+
+			//default error behavior when a filter cannot be applied
+			onError: getLog('error'),
+			onWarn: getLog('warn'),
+			enableDeprecation: true,
+			selector: '[data-behavior]'
+		},
+
+		initialize: function(options){
+			this.setOptions(options);
+			this.API = new Class({ Extends: BehaviorAPI });
+			this.passMethods({
+				addEvent: this.addEvent.bind(this), 
+				removeEvent: this.removeEvent.bind(this),
+				addEvents: this.addEvents.bind(this), 
+				removeEvents: this.removeEvents.bind(this),
+				fireEvent: this.fireEvent.bind(this),
+				applyFilters: this.apply.bind(this),
+				applyFilter: this.applyFilter.bind(this),
+				getContentElement: this.getContentElement.bind(this),
+				getContainerSize: function(){
+					return this.getContentElement().measure(function(){
+						return this.getSize();
+					}); 
+				}.bind(this),
+				error: function(){ this.fireEvent('error', arguments); }.bind(this),
+				fail: function(){
+					var msg = Array.join(arguments, ' ');
+					throw new Error(msg);
+				},
+				warn: function(){
+					this.fireEvent('warn', arguments);
+				}.bind(this)
+			});
+		},
+
+		getContentElement: function(){
+			return this.options.container || document.body;
+		},
+
+		//Applies all the behavior filters for an element.
+		//container - (element) an element to apply the filters registered with this Behavior instance to.
+		//force - (boolean; optional) passed through to applyFilter (see it for docs)
+		apply: function(container, force){
+		  this._getElements(container).each(function(element){
+				var plugins = [];
+				element.getBehaviors().each(function(name){
+					var filter = this.getFilter(name);
+					if (!filter){
+						this.fireEvent('error', ['There is no filter registered with this name: ', name, element]);
+					} else {
+						var config = filter.config;
+						if (config.delay !== undefined){
+							this.applyFilter.delay(filter.config.delay, this, [element, filter, force]);
+						} else if(config.delayUntil){
+							this._delayFilterUntil(element, filter, force);
+						} else if(config.initializer){
+							this._customInit(element, filter, force);
+						} else {
+							plugins.extend(this.applyFilter(element, filter, force, true));
+						}
+					}
+				}, this);
+				plugins.each(function(plugin){ plugin(); });
+			}, this);
+			return this;
+		},
+
+		_getElements: function(container){
+			if (typeOf(this.options.selector) == 'function') return this.options.selector(container);
+			else return document.id(container).getElements(this.options.selector);
+		},
+
+		//delays a filter until the event specified in filter.config.delayUntil is fired on the element
+		_delayFilterUntil: function(element, filter, force){
+			var event = filter.config.delayUntil;
+			var init = function(e){
+				element.removeEvent(event, init);
+				var setup = filter.setup;
+				filter.setup = function(element, api, _pluginResult){
+					api.event = e;
+					setup.apply(filter, [element, api, _pluginResult]);
+				};
+				this.applyFilter(element, filter, force);
+				filter.setup = setup;
+			}.bind(this);
+			element.addEvent(event, init);
+		},
+
+		//runs custom initiliazer defined in filter.config.initializer
+		_customInit: function(element, filter, force){
+			var api = new this.API(element, filter.name);
+			api.runSetup = this.applyFilter.pass([element, filter, force], this);
+			filter.config.initializer(element, api);
+		},
+
+		//Applies a specific behavior to a specific element.
+		//element - the element to which to apply the behavior
+		//filter - (object) a specific behavior filter, typically one registered with this instance or registered globally.
+		//force - (boolean; optional) apply the behavior to each element it matches, even if it was previously applied. Defaults to *false*.
+		//_returnPlugins - (boolean; optional; internal) if true, plugins are not rendered but instead returned as an array of functions
+		//_pluginTargetResult - (obj; optional internal) if this filter is a plugin for another, this is whatever that target filter returned
+		//                      (an instance of a class for example)
+		applyFilter: function(element, filter, force, _returnPlugins, _pluginTargetResult){
+			var pluginsToReturn = [];
+			if (this.options.breakOnErrors){
+				pluginsToReturn = this._applyFilter.apply(this, arguments);
+			} else {
+				try {
+					pluginsToReturn = this._applyFilter.apply(this, arguments);
+				} catch (e){
+					this.fireEvent('error', ['Could not apply the behavior ' + filter.name, e]);
+				}
+			}
+			return _returnPlugins ? pluginsToReturn : this;
+		},
+
+		//see argument list above for applyFilter
+		_applyFilter: function(element, filter, force, _returnPlugins, _pluginTargetResult){
+			var pluginsToReturn = [];
+			element = document.id(element);
+			//get the filters already applied to this element
+			var applied = getApplied(element);
+			//if this filter is not yet applied to the element, or we are forcing the filter
+			if (!applied[filter.name] || force){
+				//if it was previously applied, garbage collect it
+				if (applied[filter.name]) applied[filter.name].cleanup(element);
+				var api = new this.API(element, filter.name);
+
+				//deprecated
+				api.markForCleanup = filter.markForCleanup.bind(filter);
+				api.onCleanup = function(fn){
+					filter.markForCleanup(element, fn);
+				};
+
+				if (filter.config.deprecated && this.options.enableDeprecation) api.deprecate(filter.config.deprecated);
+				if (filter.config.deprecateAsJSON && this.options.enableDeprecation) api.deprecate(filter.config.deprecatedAsJSON, true);
+
+				//deal with requirements and defaults
+				if (filter.config.requireAs){
+					api.requireAs(filter.config.requireAs);
+				} else if (filter.config.require){
+					api.require.apply(api, Array.from(filter.config.require));
+				}
+
+				if (filter.config.defaults) api.setDefault(filter.config.defaults);
+
+				//apply the filter
+				var result = filter.setup(element, api, _pluginTargetResult);
+				if (filter.config.returns && !instanceOf(result, filter.config.returns)){
+					throw new Error("Filter " + filter.name + " did not return a valid instance.");
+				}
+				element.store('Behavior Filter result:' + filter.name, result);
+				//and mark it as having been previously applied
+				applied[filter.name] = filter;
+				//apply all the plugins for this filter
+				var plugins = this.getPlugins(filter.name);
+				if (plugins){
+					for (var name in plugins){
+						if (_returnPlugins){
+							pluginsToReturn.push(this.applyFilter.pass([element, plugins[name], force, null, result], this));
+						} else {
+							this.applyFilter(element, plugins[name], force, null, result);
+						}
+					}
+				}
+			}
+			return pluginsToReturn;
+		},
+
+		//given a name, returns a registered behavior
+		getFilter: function(name){
+			return this._registered[name] || Behavior._registered[name];
+		},
+
+		getPlugins: function(name){
+			return this._plugins[name] || Behavior._plugins[name];
+		},
+
+		//Garbage collects all applied filters for an element and its children.
+		//element - (*element*) container to cleanup
+		//ignoreChildren - (*boolean*; optional) if *true* only the element will be cleaned, otherwise the element and all the
+		//	  children with filters applied will be cleaned. Defaults to *false*.
+		cleanup: function(element, ignoreChildren){
+			element = document.id(element);
+			var applied = getApplied(element);
+			for (var filter in applied){
+				applied[filter].cleanup(element);
+				element.eliminate('Behavior Filter result:' + filter);
+				delete applied[filter];
+			}
+			if (!ignoreChildren) this._getElements(element).each(this.cleanup, this);
+			return this;
+		}
+
+	});
+
+	//Export these for use elsewhere (notabily: Delegator).
+	Behavior.getLog = getLog;
+	Behavior.PassMethods = PassMethods;
+
+
+	//Returns the applied behaviors for an element.
+	var getApplied = function(el){
+		return el.retrieve('_appliedBehaviors', {});
+	};
+
+	//Registers a behavior filter.
+	//name - the name of the filter
+	//fn - a function that applies the filter to the given element
+	//overwrite - (boolean) if true, will overwrite existing filter if one exists; defaults to false.
+	var addFilter = function(name, fn, overwrite){
+		if (!this._registered[name] || overwrite) this._registered[name] = new Behavior.Filter(name, fn);
+		else throw new Error('Could not add the Behavior filter "' + name  +'" as a previous trigger by that same name exists.');
+	};
+	
+	var addFilters = function(obj, overwrite){
+		for (var name in obj){
+			addFilter.apply(this, [name, obj[name], overwrite]);
+		}
+	};
+	
+	//Registers a behavior plugin
+	//filterName - (*string*) the filter (or plugin) this is a plugin for
+	//name - (*string*) the name of this plugin
+	//setup - a function that applies the filter to the given element
+	var addPlugin = function(filterName, name, setup, overwrite){
+		if (!this._plugins[filterName]) this._plugins[filterName] = {};
+		if (!this._plugins[filterName][name] || overwrite) this._plugins[filterName][name] = new Behavior.Filter(name, setup);
+		else throw new Error('Could not add the Behavior filter plugin "' + name  +'" as a previous trigger by that same name exists.');
+	};
+	
+	var addPlugins = function(obj, overwrite){
+		for (var name in obj){
+			addPlugin.apply(this, [obj[name].fitlerName, obj[name].name, obj[name].setup], overwrite);
+		}
+	};
+	
+	//Add methods to the Behavior namespace for global registration.
+	Object.append(Behavior, {
+		_registered: {},
+		_plugins: {},
+		addGlobalFilter: addFilter,
+		addGlobalFilters: addFilters,
+		addGlobalPlugin: addPlugin,
+		addGlobalPlugins: addPlugins
+	});
+	//Add methods to the Behavior class for instance registration.
+	Behavior.implement({
+		_registered: {},
+		_plugins: {},
+		addFilter: addFilter,
+		addFilters: addFilters,
+		addPlugin: addPlugin,
+		addPlugins: addPlugins
+	});
+
+	//This class is an actual filter that, given an element, alters it with specific behaviors.
+	Behavior.Filter = new Class({
+
+		config: {
+			/**
+				returns: Foo,
+				require: ['req1', 'req2'],
+				//or
+				requireAs: {
+					req1: Boolean,
+					req2: Number,
+					req3: String
+				},
+				defaults: {
+					opt1: false,
+					opt2: 2
+				},
+				//simple example:
+				setup: function(element, API){
+					var kids = element.getElements(API.get('selector'));
+					//some validation still has to occur here
+					if (!kids.length) API.fail('there were no child elements found that match ', API.get('selector'));
+					if (kids.length < 2) API.warn("there weren't more than 2 kids that match", API.get('selector'));
+					var fooInstance = new Foo(kids, API.get('opt1', 'opt2'));
+					API.onCleanup(function(){
+						fooInstance.destroy();
+					});
+					return fooInstance;
+				},
+				delayUntil: 'mouseover',
+				//OR
+				delay: 100,
+				//OR
+				initializer: function(element, API){
+					element.addEvent('mouseover', API.runSetup); //same as specifying event
+					//or
+					API.runSetup.delay(100); //same as specifying delay
+					//or something completely esoteric
+					var timer = (function(){
+						if (element.hasClass('foo')){
+							clearInterval(timer);
+							API.runSetup();
+						}
+					}).periodical(100);
+					//or
+					API.addEvent('someBehaviorEvent', API.runSetup);
+				});
+				*/
+		},
+
+		//Pass in an object with the following properties:
+		//name - the name of this filter
+		//setup - a function that applies the filter to the given element
+		initialize: function(name, setup){
+			this.name = name;
+			if (typeOf(setup) == "function"){
+				this.setup = setup;
+			} else {
+				Object.append(this.config, setup);
+				this.setup = this.config.setup;
+			}
+			this._cleanupFunctions = new Table();
+		},
+
+		//Stores a garbage collection pointer for a specific element.
+		//Example: if your filter enhances all the inputs in the container
+		//you might have a function that removes that enhancement for garbage collection.
+		//You would mark each input matched with its own cleanup function.
+		//NOTE: this MUST be the element passed to the filter - the element with this filters
+		//      name in its data-behavior property. I.E.:
+		//<form data-behavior="FormValidator">
+		//  <input type="text" name="email"/>
+		//</form>
+		//If this filter is FormValidator, you can mark the form for cleanup, but not, for example
+		//the input. Only elements that match this filter can be marked.
+		markForCleanup: function(element, fn){
+			var functions = this._cleanupFunctions.get(element);
+			if (!functions) functions = [];
+			functions.include(fn);
+			this._cleanupFunctions.set(element, functions);
+			return this;
+		},
+
+		//Garbage collect a specific element.
+		//NOTE: this should be an element that has a data-behavior property that matches this filter.
+		cleanup: function(element){
+			var marks = this._cleanupFunctions.get(element);
+			if (marks){
+				marks.each(function(fn){ fn(); });
+				this._cleanupFunctions.erase(element);
+			}
+			return this;
+		}
+
+	});
+
+	Behavior.elementDataProperty = 'behavior';
+
+	Element.implement({
+
+		addBehavior: function(name){
+			return this.setData(Behavior.elementDataProperty, this.getBehaviors().include(name).join(' '));
+		},
+
+		removeBehavior: function(name){
+			return this.setData(Behavior.elementDataProperty, this.getBehaviors().erase(name).join(' '));
+		},
+
+		getBehaviors: function(){
+			var filters = this.getData(Behavior.elementDataProperty);
+			if (!filters) return [];
+			return filters.trim().split(spaceOrCommaRegex);
+		},
+
+		hasBehavior: function(name){
+			return this.getBehaviors().contains(name);
+		},
+
+		getBehaviorResult: function(name){
+			return this.retrieve('Behavior Filter result:' + name);
+		}
+
+	});
+
+
+})();

File mootools_behavior/static/mootools_behavior/Behavior/BehaviorAPI.js

+/*
+---
+name: BehaviorAPI
+description: HTML getters for Behavior's API model.
+requires: [Core/Class, /Element.Data]
+provides: [BehaviorAPI]
+...
+*/
+
+
+(function(){
+	//see Docs/BehaviorAPI.md for documentation of public methods.
+	
+	window.BehaviorAPI = new Class({
+		element: null,
+		prefix: '',
+		defaults: {},
+
+		initialize: function(element, prefix){
+			this.element = element;
+			this.prefix = prefix.toLowerCase();
+		},
+
+		/******************
+		 * PUBLIC METHODS
+		 ******************/
+
+		get: function(/* name[, name, name, etc] */){
+			if (arguments.length > 1) return this._getObj(Array.from(arguments));
+			return this._getValue(arguments[0]);
+		},
+
+		getAs: function(/*returnType, name, defaultValue OR {name: returnType, name: returnType, etc}*/){
+			if (typeOf(arguments[0]) == 'object') return this._getValuesAs.apply(this, arguments);
+			return this._getValueAs.apply(this, arguments);
+		},
+
+		require: function(/* name[, name, name, etc] */){
+			for (var i = 0; i < arguments.length; i++){
+				if (this._getValue(arguments[i]) == undefined) throw new Error('Could not retrieve ' + this.prefix + '-' + arguments[i] + ' option from element.');
+			}
+			return this;
+		},
+
+		requireAs: function(returnType, name /* OR {name: returnType, name: returnType, etc}*/){
+			var val;
+			if (typeOf(arguments[0]) == 'object'){
+				for (var objName in arguments[0]){
+					val = this._getValueAs(arguments[0][objName], objName);
+					if (val === undefined || val === null) throw new Error("Could not retrieve " + this.prefix + '-' + objName + " option from element.");
+				}
+			} else {
+				val = this._getValueAs(returnType, name);
+				if (val === undefined || val === null) throw new Error("Could not retrieve " + this.prefix + '-' + name + " option from element.");
+			}
+			return this;
+		},
+
+		setDefault: function(name, value /* OR {name: value, name: value, etc }*/){
+			if (typeOf(arguments[0]) == 'object'){
+				for (var objName in arguments[0]){
+					this.setDefault(objName, arguments[0][objName]);
+				}
+				return;
+			}
+			name = name.camelCase();
+			this.defaults[name] = value;
+			if (this._getValue(name) == null){
+				var options = this._getOptions();
+				options[name] = value;
+			}
+			return this;
+		},
+
+		refreshAPI: function(){
+			delete this.options;
+			this.setDefault(this.defaults);
+			return;
+		},
+
+		/******************
+		 * PRIVATE METHODS
+		 ******************/
+
+		//given an array of names, returns an object of key/value pairs for each name
+		_getObj: function(names){
+			var obj = {};
+			names.each(function(name){
+				var value = this._getValue(name);
+				if (value !== undefined) obj[name] = value;
+			}, this);
+			return obj;
+		},
+		//gets the data-behaviorname-options object and parses it as JSON
+		_getOptions: function(){
+			if (!this.options){
+				var options = this.element.getData(this.prefix + '-options', '{}');
+				if (options && options[0] != '{') options = '{' + options + '}';
+				var isSecure = JSON.isSecure(options);
+				if (!isSecure) throw new Error('warning, options value for element is not parsable, check your JSON format for quotes, etc.');
+				this.options = isSecure ? JSON.decode(options) : {};
+				for (option in this.options) {
+					this.options[option.camelCase()] = this.options[option];
+				}
+			}
+			return this.options;
+		},
+		//given a name (string) returns the value for it
+		_getValue: function(name){
+			name = name.camelCase();
+			var options = this._getOptions();
+			if (!options.hasOwnProperty(name)){
+				var inline = this.element.getData(this.prefix + '-' + name.hyphenate());
+				if (inline) options[name] = inline;
+			}
+			return options[name];
+		},
+		//given a Type and a name (string) returns the value for it coerced to that type if possible
+		//else returns the defaultValue or null
+		_getValueAs: function(returnType, name, defaultValue){
+			var value = this._getValue(name);
+			if (value == null || value == undefined) return defaultValue;
+			var coerced = this._coerceFromString(returnType, value);
+			if (coerced == null) throw new Error("Could not retrieve value '" + name + "' as the specified type. Its value is: " + value);
+			return coerced;
+		},
+		//given an object of name/Type pairs, returns those as an object of name/value (as specified Type) pairs
+		_getValuesAs: function(obj){
+			var returnObj = {};
+			for (var name in obj){
+				returnObj[name] = this._getValueAs(obj[name], name);
+			}
+			return returnObj;
+		},
+		//attempts to run a value through the JSON parser. If the result is not of that type returns null.
+		_coerceFromString: function(toType, value){
+			if (typeOf(value) == 'string' && toType != String){
+				if (JSON.isSecure(value)) value = JSON.decode(value);
+			}
+			if (instanceOf(value, toType)) return value;
+			return null;
+		}
+	});
+
+})();

File mootools_behavior/static/mootools_behavior/Behavior/Delegator.js

+/*
+---
+name: Delegator
+description: Allows for the registration of delegated events on a container.
+requires: [More/Element.Delegation, Core/Options, Core/Events, /Event.Mock, /Behavior]
+provides: [Delegator]
+...
+*/
+(function(){
+
+	var spaceOrCommaRegex = /\s*,\s*|\s+/g;
+
+	window.Delegator = new Class({
+
+		Implements: [Options, Events, Behavior.PassMethods],
+
+		options: {
+			// breakOnErrors: false,
+			getBehavior: function(){},
+			onError: Behavior.getLog('error'),
+			onWarn: Behavior.getLog('warn')
+		},
+
+		initialize: function(options){
+			this.setOptions(options);
+			this._bound = {
+				eventHandler: this._eventHandler.bind(this)
+			};
+			Delegator._instances.push(this);
+			Object.each(Delegator._triggers, function(trigger){
+				this._eventTypes.combine(trigger.types);
+			}, this);
+			this.API = new Class({ Extends: BehaviorAPI });
+			this.passMethods({
+				addEvent: this.addEvent.bind(this), 
+				removeEvent: this.removeEvent.bind(this),
+				addEvents: this.addEvents.bind(this), 
+				removeEvents: this.removeEvents.bind(this),
+				fireEvent: this.fireEvent.bind(this),
+				attach: this.attach.bind(this),
+				trigger: this.trigger.bind(this),
+				error: function(){ this.fireEvent('error', arguments); }.bind(this),
+				fail: function(){
+					var msg = Array.join(arguments, ' ');
+					throw new Error(msg);
+				},
+				warn: function(){
+					this.fireEvent('warn', arguments);
+				}.bind(this)
+			});
+
+			this.bindToBehavior(this.options.getBehavior());
+		},
+
+		bindToBehavior: function(behavior){
+			if (!behavior) return;
+			this.unbindFromBehavior();
+			this._behavior = behavior;
+			if (!this._behaviorEvents){
+				var self = this;
+				this._behaviorEvents = {
+					destroyDom: function(elements){
+						Array.from(elements).each(function(element){
+							self._behavior.cleanup(element);
+						});
+					},
+					ammendDom: function(container){
+						self._behavior.apply(container);
+					}
+				};
+			}
+			this.addEvents(this._behaviorEvents);
+		},
+
+		getBehavior: function(){
+			return this._behavior;
+		},
+
+		unbindFromBehavior: function(){
+			if (this._behaviorEvents && this._behavior){
+				this._behavior.removeEvents(this._behaviorEvents);
+				delete this._behavior;
+			}
+		},
+
+		attach: function(target, _method){
+			_method = _method || 'addEvent';
+			target = document.id(target);
+			if ((_method == 'addEvent' && this._attachedTo.contains(target)) ||
+			    (_method == 'removeEvent') && !this._attachedTo.contains(target)) return this;
+			this._eventTypes.each(function(event){
+				target[_method](event + ':relay([data-trigger])', this._bound.eventHandler);
+			}, this);
+			this._attachedTo.push(target);
+			return this;
+		},
+
+		detach: function(target){
+			if (target){
+				this.attach(target, 'removeEvent');
+				return this;
+			} else {
+				this._attachedTo.each(this.detach, this);
+			}
+		},
+
+		trigger: function(name, element, event){
+			if (!event || typeOf(event) == "string") event = new Event.Mock(element, event);
+			var trigger = this._getTrigger(name);
+			if (trigger && trigger.types.contains(event.type)) {
+				if (this.options.breakOnErrors){
+					this._trigger(trigger, element, event);
+				} else {
+					try {
+						this._trigger(trigger, element, event);
+					} catch(e) {
+						this.fireEvent('error', ['Could not apply the trigger', name, e]);
+					}
+				}
+			} else {
+				this.fireEvent('error', 'Could not find a trigger with the name ' + name + ' for event: ' + event.type);
+			}
+			return this;
+		},
+
+		/******************
+		 * PRIVATE METHODS
+		 ******************/
+
+		_getTrigger: function(name){
+			return this._triggers[name] || Delegator._triggers[name];
+		},
+
+		_trigger: function(trigger, element, event){
+			var api = new this.API(element, trigger.name);
+			if (trigger.requireAs){
+				api.requireAs(trigger.requireAs);
+			} else if (trigger.require){
+				api.require.apply(api, Array.from(trigger.require));
+			} if (trigger.defaults){
+				api.setDefault(trigger.defaults);
+			}
+			trigger.handler.apply(this, [event, element, api]);
+			this.fireEvent('trigger', [trigger, element, event]);
+		},
+
+		_eventHandler: function(event, target){
+			var triggers = target.getTriggers();
+			if (triggers.contains('Stop')) event.stop();
+			if (triggers.contains('PreventDefault')) event.preventDefault();
+			triggers.each(function(trigger){
+				if (trigger != "Stop" && trigger != "PreventDefault") this.trigger(trigger, target, event);
+			}, this);
+		},
+
+		_onRegister: function(eventTypes){
+			eventTypes.each(function(eventType){
+				if (!this._eventTypes.contains(eventType)){
+					this._attachedTo.each(function(element){
+						element.addEvent(eventType + ':relay([data-trigger])', this._bound.eventHandler);
+					}, this);
+				}
+				this._eventTypes.include(eventType);
+			}, this);
+		},
+
+		_attachedTo: [],
+		_eventTypes: [],
+		_triggers: {}
+
+	});
+
+	Delegator._triggers = {};
+	Delegator._instances = [];
+	Delegator._onRegister = function(eventType){
+		this._instances.each(function(instance){
+			instance._onRegister(eventType);
+		});
+	};
+
+	Delegator.register = function(eventTypes, name, handler, overwrite /** or eventType, obj, overwrite */){
+		eventTypes = Array.from(eventTypes);
+		if (typeOf(name) == "object"){
+			var obj = name;
+			for (name in obj){
+				this.register.apply(this, [eventTypes, name, obj[name], handler]);
+			}
+			return this;
+		}
+		if (!this._triggers[name] || overwrite){
+			if (typeOf(handler) == "function"){
+				handler = {
+					handler: handler
+				};
+			}
+			handler.types = eventTypes;
+			handler.name = name;
+			this._triggers[name] = handler;
+			this._onRegister(eventTypes);
+		} else {
+			throw new Error('Could add the trigger "' + name  +'" as a previous trigger by that same name exists.');
+		}
+		return this;
+	};
+
+	Delegator.implement('register', Delegator.register);
+
+	Element.implement({
+
+		addTrigger: function(name){
+			return this.setData('trigger', this.getTriggers().include(name).join(' '));
+		},
+
+		removeTrigger: function(name){
+			return this.setData('trigger', this.getTriggers().erase(name).join(' '));
+		},
+
+		getTriggers: function(){
+			var triggers = this.getData('trigger');
+			if (!triggers) return [];
+			return triggers.trim().split(spaceOrCommaRegex);
+		},
+
+		hasTrigger: function(name){
+			return this.getTriggers().contains(name);
+		}
+
+	});
+
+})();

File mootools_behavior/static/mootools_behavior/Behavior/Element.Data.js

+/*
+---
+name: Element.Data
+description: Stores data in HTML5 data properties
+provides: [Element.Data]
+requires: [Core/Element, Core/JSON]
+script: Element.Data.js
+
+...
+*/
+(function(){
+
+	JSON.isSecure = function(string){
+		//this verifies that the string is parsable JSON and not malicious (borrowed from JSON.js in MooTools, which in turn borrowed it from Crockford)
+		//this version is a little more permissive, as it allows single quoted attributes because forcing the use of double quotes
+		//is a pain when this stuff is used as HTML properties
+		return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(string.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, '').replace(/'[^'\\\n\r]*'/g, ''));
+	};
+
+	Element.implement({
+		/*
+			sets an HTML5 data property.
+			arguments:
+				name - (string) the data name to store; will be automatically prefixed with 'data-'.
+				value - (string, number) the value to store.
+		*/
+		setData: function(name, value){
+			return this.set('data-' + name.hyphenate(), value);
+		},
+
+		getData: function(name, defaultValue){
+			var value = this.get('data-' + name.hyphenate());
+			if (value != undefined){
+				return value;
+			} else if (defaultValue != undefined){
+				this.setData(name, defaultValue);
+				return defaultValue;
+			}
+		},
+
+		/* 
+			arguments:
+				name - (string) the data name to store; will be automatically prefixed with 'data-'
+				value - (string, array, or object) if an object or array the object will be JSON encoded; otherwise stored as provided.
+		*/
+		setJSONData: function(name, value){
+			return this.setData(name, JSON.encode(value));
+		},
+
+		/*
+			retrieves a property from HTML5 data property you specify
+		
+			arguments:
+				name - (retrieve) the data name to store; will be automatically prefixed with 'data-'
+				strict - (boolean) if true, will set the JSON.decode's secure flag to true; otherwise the value is still tested but allows single quoted attributes.
+				defaultValue - (string, array, or object) the value to set if no value is found (see storeData above)
+		*/
+		getJSONData: function(name, strict, defaultValue){
+			var value = this.get('data-' + name);
+			if (value != undefined){
+				if (value && JSON.isSecure(value)) {
+					return JSON.decode(value, strict);
+				} else {
+					return value;
+				}
+			} else if (defaultValue != undefined){
+				this.setJSONData(name, defaultValue);
+				return defaultValue;
+			}
+		}
+
+	});
+
+})();

File mootools_behavior/static/mootools_behavior/Behavior/Event.Mock.js

+/*
+---
+name: Event.Mock
+
+description: Supplies a Mock Event object for use on fireEvent
+
+license: MIT-style
+
+authors:
+- Arieh Glazer
+
+requires: Core/Event
+
+provides: [Event.Mock]
+
+...
+*/
+
+(function($,window,undef){
+
+/**
+ * creates a Mock event to be used with fire event
+ * @param Element target an element to set as the target of the event - not required
+ *  @param string type the type of the event to be fired. Will not be used by IE - not required.
+ *
+ */
+Event.Mock = function(target,type){
+	var e = window.event;
+
+	type = type || 'click';