// +---------------------------------------------------------------------------+
// | This file is part of the Mootools Addon project.                          |
// | Copyright (C) Lemieux BÃ©dard Communication                                |
// |                                                                           |
// | For the full copyright and license information, please view the LICENSE   |
// | file that was distributed with this source code.                          |
// +---------------------------------------------------------------------------+

var Moo = {};// +---------------------------------------------------------------------------+
// | This file is part of the Mootools Addon project.                          |
// | Copyright (C) Lemieux BÃ©dard Communication                                |
// |                                                                           |
// | For the full copyright and license information, please view the LICENSE   |
// | file that was distributed with this source code.                          |
// +---------------------------------------------------------------------------+

String.implement
({
	/**
	 * Return the only the numbers of a string.
	 * @return string The numbers.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */	
	toNumber: function() {
		return this.replace(/[^0-9]/g, '');
	},
	
	/**
	 * Return the basename of a path.
	 * @return string The basename of a path.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */	
	basename: function() {
		return this.match(/[\/|\\]([^\\\/]+)$/)[1];
	},

	/**
	 * Repeat a string.
	 * @param string The string.
	 * @return string The repeated string.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */		
	repeat: function(i, m) { 
		for (var o = []; m > 0; o[--m] = i); return(o.join('')); 
	},

	/**
	 * Return a formated string.
	 * @param string The formats
	 * @return string The formated string.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */
 	sprintf: function() {
		var i = 0, a, f = arguments[i], o = [], m, p, c, x;
		f = this;
		while (f) {
			if (m = /^[^\x25]+/.exec(f)) o.push(m[0]);
			else if (m = /^\x25{2}/.exec(f)) o.push('%');
			else if (m = /^\x25(?:(\d+)\$)?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(f)) {
				if (((a = arguments[m[1] || i++]) == null) || (a == undefined)) throw("Too few arguments.");
				if (/[^s]/.test(m[7]) && (typeof(a) != 'number')) throw("Expecting number but found " + typeof(a));
				switch (m[7]) {
				    case 'b': a = a.toString(2); break;
				    case 'c': a = String.fromCharCode(a); break;
				    case 'd': a = parseInt(a); break;
				    case 'e': a = m[6] ? a.toExponential(m[6]) : a.toExponential(); break;
				    case 'f': a = m[6] ? parseFloat(a).toFixed(m[6]) : parseFloat(a); break;
				    case 'o': a = a.toString(8); break;
				    case 's': a = ((a = String(a)) && m[6] ? a.substring(0, m[6]) : a); break;
				    case 'u': a = Math.abs(a); break;
				    case 'x': a = a.toString(16); break;
				    case 'X': a = a.toString(16).toUpperCase(); break;
				}
				a = (/[def]/.test(m[7]) && m[2] && a > 0 ? '+' + a : a);
				c = m[3] ? m[3] == '0' ? '0' : m[3].charAt(1) : ' ';
				x = m[5] - String(a).length;
				p = m[5] ? String.repeat(c, x) : '';
				o.push(m[4] ? a + p : p + a);
			}
			else throw ("Huh ?!");
			f = f.substring(m[0].length);
		}
		return o.join('');
	},
	
	/**
	 * Print a formated string.
	 * @param string The formats
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */	
	printf: function() {
		document.write(this.sprintf.run(arguments))
	}
});// +---------------------------------------------------------------------------+
// | This file is part of the Mootools Addon project.                          |
// | Copyright (C) Lemieux BÃ©dard Communication                                |
// |                                                                           |
// | For the full copyright and license information, please view the LICENSE   |
// | file that was distributed with this source code.                          |
// +---------------------------------------------------------------------------+

Array.implement
({
	/**
	 * Add an event to each elements inside an array.
	 * @param string The event name.
	 * @param object The function.
	 * @return void
	 * @author Jean-Philippe DÃ©ry (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */	
	addEvent: function(event, fn) {
		this.each(function(el) {
			if (el.addEvent) el.addEvent(event, fn);
		})
	},
	
	/**
	 * Add a class to each elements inside the array.
	 * @param string The class.
	 * @return void
	 * @author Jean-Philippe DÃ©ry (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */
	addClass: function(klass) {
		this.each(function(el) {
			if (el.addClass) el.addClass(klass);
		});
	},
	
	/**
	 * Remove a class to each elements inside the array.
	 * @param string The class.
	 * @return void
	 * @author Jean-Philippe DÃ©ry (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */
	removeClass: function(klass) {
		this.each(function(el) {
			if (el.removeClass) el.removeClass(klass);
		});
	},	

	/**
	 * Set a style to all the elements inside the array.
	 * @param string The style name.
	 * @param string The style value.
	 * @return void
	 * @author Jean-Philippe DÃ©ry (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */
	setStyle: function(key, val) {
		this.each(function(el) {
			if (el.setStyle) el.setStyle(key, val);
		});
	},
	
	/**
	 * Set a series of styles to all the elements inside the array.
	 * @param object The style.
	 * @return void
	 * @author Jean-Philippe DÃ©ry (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */	
	setStyles: function(styles) {
		this.each(function(key, val) {
			if (el.setStyles) el.setStyles(styles);
		});
	},
	
	/**
	 * Show all the elements inside the array.
	 * @param string The display string.
	 * @return void
	 * @author Jean-Philippe DÃ©ry (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */	
	show: function(as) {
		this.each(function(key, val) {
			if (el.show) el.show(as);
		});
	},
	
	/**
	 * Hide all the elements inside the array.
	 * @param string The display string.
	 * @return void
	 * @author Jean-Philippe DÃ©ry (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */		
	hide: function() {
		this.each(function(key, val) {
			if (el.hide) el.hide();
		});
	}	
});// +---------------------------------------------------------------------------+
// | This file is part of the Mootools Addon project.                          |
// | Copyright (C) Lemieux BÃ©dard Communication                                |
// |                                                                           |
// | For the full copyright and license information, please view the LICENSE   |
// | file that was distributed with this source code.                          |
// +---------------------------------------------------------------------------+

Class.ToElement = new Class
({
	/**
	 * Return the element
	 * @return object the element.
	 * @author Jean-Philippe DÃ©ry (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */
	toElement: function() {
		return this.element;
	},
	
	/**
	 * Return the element
	 * @return object the element.
	 * @author Jean-Philippe DÃ©ry (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */	
	getElement: function() {
		return this.element;
	}
});
 
var ToElement = Class.ToElement;// +---------------------------------------------------------------------------+
// | This file is part of the Mootools Addon project.                          |
// | Copyright (C) Lemieux Bédard Communication                                |
// |                                                                           |
// | For the full copyright and license information, please view the LICENSE   |
// | file that was distributed with this source code.                          |
// +---------------------------------------------------------------------------+

/**
 * Extends the native element in order to add the container properties. The
 * container is basically the reference of the class which is holding an
 * element.
 * @package core
 * @subpackage element
 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
 * @copyright Lemieux Bedard Communication (francois.patry@lemieuxbedard.com)
 * @since 1.0.0
 * @version 1.0.0
 */
Element.implement
({
	/**
	 * @var array The array of object used as actors.
	 */
	actors: null,

	/**
	 * @var int The actor count.
	 */
	actorsCount: 0,
	
	/**
	 * Set the id of an element. This method is simply a shortcut to the common
	 * set property method allready in place.
	 * @param string The id.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */
	setId: function(id) {
		this.set('id', id);
	},

	/**
	 * Return the id of an element. This method is simply a shortcut to the common
	 * get property method allready in place.
	 * @return string The id.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */
	getId: function() {
		return this.get('id');
	},
	
	/**
	 * Return the text of an element.
	 * @return string The text.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */
	getText: function() {
		return this.get('text');
	},
	
	/**
	 * Set the text of an element.
	 * @param string The text.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */
	setText: function(text) {
		this.set('text', text);
	},
	
	/**
	 * Set the inside html of an element.
	 * @param array The html.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */
	setHtml: function(html) {
		return this.set('html', html);
	},
	
	/**
	 * Return the inside html of an element.
	 * @return array The html.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */
	getHtml: function() {
		return this.get('html');
	},
	
	/**
	 * This method will dispose of an attribute but the attribute will be stored
	 * in the internal class properties using the store method.
	 * @param string The property name.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */
	disposeProperty: function(property) {
		this.store(property, null);
		this.store(property, this.getProperty(property));
		this.removeProperty(property);
	},
	
	/**
	 * Return the property that was disposed before it was removed.
	 * @param string The property name.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */
	getDisposedProperty: function(property) {
		return this.retrieve(property);
	},
	
	/**
	 * This method will dispose the href attribute but the attribute will be stored
	 * in the internal class properties using the store method.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */
	disposeHrefProperty: function() {
		this.disposeProperty('href');
		this.setStyle('cursor', 'pointer');
	},
	
	/**
	 * Return the href property that was disposed before it was removed.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */
	getDisposedHrefProperty: function() {
		return this.getDisposedProperty('href');
	},
	
	/**
	 * Initialize the actor array.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */
	initializeActors: function() {
		if (this.actors == null) this.actors = new Array();
	},
	
	/**
	 * Add an actor to the current element. An actor is basically a class
	 * which will act as a complex event.
	 * @param object The actor object.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */
	addActor: function(actor) {
		this.initializeActors();
		this.actors.push(actor);
		this.actorsCount++;
		var args = $A(arguments);
		args.splice(0, 1);
		args.unshift(this);
		actor.applyOn.run(args, actor);
	},
	
	/**
	 * Return an actor based on the instance name.
	 * @param object The actor class.
	 * @return object The actor.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */
	getActor: function(actorName) {
		this.initializeActors(); 
		for (var i = 0; i < this.actors.length; i++) {
			if (this.actors[i] instanceof actorName) {
				return this.actors[i];
			}
		}
		return null;
	},
	
	/**
	 * Return all the actors.
	 * @return array The actors.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */		
	getActors: function() {
		return this.actors;
	},
	
	/**
	 * Return the ammount of actors used on this element.
	 * @return int The actor count.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */	
	getActorCount: function() {
		return this.actorsCount;
	},
	
	/**
	 * Indicate whether or not a given actor is acting in this object.
	 * @param object The actor class.
	 * @return bool True if the actor is present.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */
	hasActor: function(actorName) {
		this.initializeActors();
		for (var i = 0; i < this.actors.length; i++) {
			if (this.actors[i] instanceof actorName) {
				return true;
			}
		}
		return false;
	},
	
	/**
	 * Copy an actor to another element.
	 * @param object The actor class.
	 * @param object The actor recipient.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */	
	copyActor: function(actor, receiver) {
		this.initializeActors();
		var actor = this.getActor(actor);
		if (actor) {
			var args = [];
			args.push(actor);
			args = args.concat(actor.arguments);
			receiver.addActor.run(args, receiver);
		}
	},	

	/**
	 * Copy all actors to another element.
	 * @param object The actor recipient.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */		
	copyActors: function(receiver) {
		this.initializeActors();
		this.actors.each(function(actor) {
			var args = [];
			args.push(actor);
			args = args.concat(actor.arguments);
			receiver.addActor.run(args, receiver);
		}, this);
	},
	
	/**
	 * Show the element.
	 * @param string The type of display
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */		
	show: function(as) {
		var display = this.retrieve('display');
		if (as == null || 
			as == undefined) {
			as = display 
			   ? display 
			   : 'block';			
		}
		this.setStyle('display', as);
	},
	
	/**
	 * Hode the element.
	 * @param string The type of display
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */			
	hide: function() {
		var display =  this.getStyle('display');
		if (display && display != 'none') {
			this.store('display', display);
		}
		this.setStyle('display', 'none');
	},
	
	/**
	 * Refresh the element.
	 * @param string The type of display
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */	
	refresh: function(options) {
		new Moo.Request.Element.Update(options).refresh(this);
	}
});// +---------------------------------------------------------------------------+
// | This file is part of the Mootools Addon project.                          |
// | Copyright (C) Lemieux BÃ©dard Communication                                |
// |                                                                           |
// | For the full copyright and license information, please view the LICENSE   |
// | file that was distributed with this source code.                          |
// +---------------------------------------------------------------------------+// +---------------------------------------------------------------------------+
// | This file is part of the Mootools Addon project.                          |
// | Copyright (C) Lemieux BÃ©dard Communication                                |
// |                                                                           |
// | For the full copyright and license information, please view the LICENSE   |
// | file that was distributed with this source code.                          |
// +---------------------------------------------------------------------------+

/**
 * @const int The actor uses a performer.
 */
var MOO_ACTOR_TYPE_PERFORMER = 1;

/**
 * @const int The actor uses an element.
 */
var MOO_ACTOR_TYPE_ELEMENT = 2;

/**
 * An actor is a class that will basically activate itself upon an element using
 * the applyOn method of the element. An actor basically use one of the two 
 * following type. The performer type use an object as the actor and the element
 * type apply the stuff directly to the element. In other words, the performer
 * type of actor use the actor class as a wrapper. This is the prefered way to go.
 * @package Actor
 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
 * @copyright Lemieux Bedard Communication (francois.patry@lemieuxbedard.com)
 * @since 2.0.0
 * @version 2.0.0
 */
Moo.Actor = new Class
({
	/**
	 * @implements Events, Options.
	 */
	Implements: [Events, Options, Class.ToElement],
	
	/**
	 * @var object The element on which the actor will act on.
	 */
	element: null,
	
	/**
	 * @var object The performer.
	 */
	performer: null,
	
	/**
	 * @var object The arguments
	 */
	arguments: null,
		
	/**
	 * Initialize the actor by setting the options.
	 * @param object The options.
	 * @return object A reference to this object.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */
	initialize: function() {
		return this;
	},
	
	/**
	 * This method is called when an object is given as an actor to an element.
	 * @param object The element to apply everything's on.
	 * @param object The arguments.
	 * @return object A reference to this object.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */
	applyOn: function(element) {
		this.element = element;
		this.arguments = $A(arguments);
		this.arguments.splice(0, 1);
		this.performer = this.perform.run(this.arguments, this);
		return this;
	},

	/**
	 * This method will create and return the object which will act as the
	 * performer of the actor. All arguments will be passed to this method
	 * except for the element.
	 * @param object The arguments.
	 * @return object The performer object
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */
	perform: function() {
		throw new Exception('You must override me');
	},
		
	/**
	 * Return the performer object.
	 * @return The performer.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */	
	getPerformer: function() {
		return this.performer;
	},

	/**
	 * Return the performer object's element.
	 * @return object The performer element.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */	
	getPerformerElement: function() {
		return $(this.performer);
	},
	
	/**
	 * Return all the arguments.
	 * @return array All the arguments.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */		
	getArguments: function() {
		return this.arguments;
	},
	
	/**
	 * Return the element binded to this object.
	 * @return object The element  binded to this object
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */	
	toElement: function() {
		return this.element;
	}
});

/**
 * Return the performer of an actor on an element.
 * @param mixed The element or the selector.
 * @param object The actor name.
 * @return void The performer.
 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
 * @since 2.0.0
 */
function $P(element, actor)
{
	if (actor == undefined ||Â actor == null) alert('Error: You must provide an actor name');
	var e = $(element);
	if (e == null) e = document.getElement(element);
	if (e) {
		var a = e.getActor(actor);
		if (a) {
			var p = a.getPerformer();
			if (p) return p;
		}
	}
	return null;
}// +---------------------------------------------------------------------------+
// | This file is part of the Mootools Addon project.                          |
// | Copyright (C) Lemieux BÃ©dard Communication                                |
// |                                                                           |
// | For the full copyright and license information, please view the LICENSE   |
// | file that was distributed with this source code.                          |
// +---------------------------------------------------------------------------+// +---------------------------------------------------------------------------+
// | This file is part of the Mootools Addon project.                          |
// | Copyright (C) Lemieux BÃ©dard Communication                                |
// |                                                                           |
// | For the full copyright and license information, please view the LICENSE   |
// | file that was distributed with this source code.                          |
// +---------------------------------------------------------------------------+

/**
 * Moo.Animable
 * This is an interface that simplify the animation handling process.
 * @package Animation
 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
 * @copyright Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
 * @since 2.0.0
 * @version 2.0.0
 */
Moo.Animable = new Class
({
	/**
	 * This method will execute an animation with a given element and handle the 
	 * animation response. If the animation wants the default behavior to be
	 * executed after the process, it will use the behavior parameter as this
	 * default behavior.
	 * @param object The element to animate.
	 * @param object The animation.
	 * @param function The default behavior.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */	
	animate: function(element, animation, behavior) {
		// execute the animation if such thing is specified othewise the
		// default behavior will be executed
		if (animation) {
			if (animation.run(element) == MOO_ANIMATION_EXEC_DEFAULT_BEHAVIOR) {
				animation.end(function() { 
					behavior.run(); 
				});
			}
			return;
		}
		// at this point we know there is no animations so we execute
		// the default behavior 
		behavior.run();
	}
});// +---------------------------------------------------------------------------+
// | This file is part of the Mootools Addon project.                          |
// | Copyright (C) Lemieux BÃ©dard Communication                                |
// |                                                                           |
// | For the full copyright and license information, please view the LICENSE   |
// | file that was distributed with this source code.                          |
// +---------------------------------------------------------------------------+

/**
 * @const string Skip the default behavior.
 */
var MOO_ANIMATION_SKIP_DEFAULT_BEHAVIOR = 'skip';

/**
 * @const string Execute the default behavior.
 */	
var MOO_ANIMATION_EXEC_DEFAULT_BEHAVIOR = 'exec';

/**
 * Moo.Animation 
 * This class is the base class of all animations. This class, once extended will
 * be used with several other class where simple methods such as show and hide 
 * can be animated differently instead of using the default behavior.
 * @package Animation
 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
 * @copyright Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
 * @since 2.0.0
 * @version 2.0.0
 */
Moo.Animation = new Class
({
	/**
	 * @implements Events Options The basic element methods.
	 */
	Implements: [Events, Options],
			
	/**
	 * @var object The options.
	 */
	options: {
		onStart: $empty,
		onFinish: $empty,
		behavior: MOO_ANIMATION_EXEC_DEFAULT_BEHAVIOR
	},
	
	/**
	 * @var object The element that gets animated.
	 */
	element: null,
	
	/**
	 * @var function The function that gets called when the animation reaches the end.
	 */
	callback: null,
	
	/**
	 * Initialize the animation.
	 * @param object The options.
	 * @return object A reference to this object.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */	
	initialize: function(options) {
		this.setOptions(options);
		return this;
	},
	
	/**
	 * Runs the animation.
	 * @param object The options.
	 * @return object A reference to this object.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */		
	run: function(element) {
		this.element = element;
		this.fireEvent('start', this.element);
		return this.animate(this.element);
	},
	
	/**
	 * Set the function that will be called when the animation finishes.
	 * @param function The callback function.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */		
	end: function(callback) {
		this.callback = callback;
	},
	
	/**
	 * Override this method to add your own implementation of the animation. Don't
	 * forget to call the finish method when the animation's tween completes.
	 * @param object The element to animate. 
	 * @return string A constant about the behavior of the sub executions.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */		
	animate: function(element) {
		return this.options.behavior;
	},
	
	/**
	 * This method is called from your overridden animate method to indicate
	 * the animation finished.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */		
	finish: function() {
		if (this.callback) {
			this.callback.run(null, this);
			this.fireEvent('finish', this.element);
		} 
	}
});// +---------------------------------------------------------------------------+
// | This file is part of the Mootools Addon project.                          |
// | Copyright (C) Lemieux BÃ©dard Communication                                |
// |                                                                           |
// | For the full copyright and license information, please view the LICENSE   |
// | file that was distributed with this source code.                          |
// +---------------------------------------------------------------------------+

/**
 * Moo.Animation.FadeIn
 * A simple fade in animation.
 * @package Animation
 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
 * @copyright Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
 * @since 2.0.0
 * @version 2.0.0
 */
Moo.Animation.FadeIn = new Class
({
	/**
	 * @extends Moo.Animation.
	 */
	Extends: Moo.Animation,
	
	/**
	 * @var object The options.
	 */
	options: {
		duration: 'short',
		from: 0,
		to: 1
	},
	
	/**
	 * Execute the animation.
	 * @param object The element to animate. 
	 * @return string A constant about the behavior of the sub executions.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */		
	animate: function(element) {

		if (element.isVisible() == false) {
			element.fade('hide');
			element.show();
		}

		var fx =  new Fx.Tween(element, {
			duration: this.options.duration,
			onComplete: function() {
				this.finish();
			}.bind(this)
		})
		
		if (this.options.from)	fx.start('opacity', this.options.from, this.options.to);
		else					fx.start('opacity', this.options.to);

		return this.options.behavior;
	}
});
// +---------------------------------------------------------------------------+
// | This file is part of the Mootools Addon project.                          |
// | Copyright (C) Lemieux BÃ©dard Communication                                |
// |                                                                           |
// | For the full copyright and license information, please view the LICENSE   |
// | file that was distributed with this source code.                          |
// +---------------------------------------------------------------------------+

/**
 * Moo.Animation.FadeIn
 * A simple fade in animation.
 * @package Animation
 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
 * @copyright Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
 * @since 2.0.0
 * @version 2.0.0
 */
Moo.Animation.FadeOut = new Class
({
	/**
	 * @extends Moo.Animation.
	 */
	Extends: Moo.Animation,
	
	/**
	 * @var object The options.
	 */
	options: {
		duration: 'short',
		from: 1,
		to: 0
	},
	
	/**
	 * Execute the animation.
	 * @param object The element to animate. 
	 * @return string A constant about the behavior of the sub executions.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */		
	animate: function(element) {
		
		if (element.isVisible() == true) {
			element.fade('show');
		} else {
			element.show();
		}

		var fx =  new Fx.Tween(element, {
			duration: this.options.duration,
			onComplete: function() {
				this.finish();
			}.bind(this)
		})
		
		if (this.options.from)	fx.start('opacity', this.options.from, this.options.to);
		else					fx.start('opacity', this.options.to);

		return this.options.behavior;
	}
});
// +---------------------------------------------------------------------------+
// | This file is part of the Mootools Addon project.                          |
// | Copyright (C) Lemieux BÃ©dard Communication                                |
// |                                                                           |
// | For the full copyright and license information, please view the LICENSE   |
// | file that was distributed with this source code.                          |
// +---------------------------------------------------------------------------+// +---------------------------------------------------------------------------+
// | This file is part of the Mootools Addon project.                          |
// | Copyright (C) Lemieux BÃ©dard Communication                                |
// |                                                                           |
// | For the full copyright and license information, please view the LICENSE   |
// | file that was distributed with this source code.                          |
// +---------------------------------------------------------------------------+

/**
 * A command is basically an ajax request with extra options.
 * @package Command
 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
 * @copyright Lemieux Bedard Communication (francois.patry@lemieuxbedard.com)
 * @since 1.0.0
 * @version 1.0.0
 */
Moo.Command = new Class
({	
	/**
	 * @var implements MooElement, Event, Options
	 */
	Implements : [Events, Options],

	/**
	 * @var string The url to request the command.
	 */
	url: null,

	/**
	 * @var object The ajax request.
	 */
	request: null,

	/**
	 * @var hash The ajax request headers.
	 */
	headers: null,
	
	/**
	 * @var hash The ajax request parameters.
	 */
	parameters: null,
	
	/**
	 * @var object Whether or not the request is loading.
	 */
	loading: false,
	
	/**
	 * @var object The options.
	 */
	options: {
		onRequest: $empty,
		onComplete: $empty,
		evalScripts: false,
		method: 'post'
	},
	
	/**
	 * Initialize the actor by setting the options.
	 * @param object The options.
	 * @return object A reference to this object.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */
	initialize : function(url, options) {
		this.setUrl(url);
		this.setOptions(options);
		this.headers = new Hash();
		this.parameters = new Hash();
		return this;
	},
	
	/**
	 * Set the url to execute the command.
	 * @param string The url.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */
	setUrl: function(url) {
		this.url = url;
	},

	/**
	 * Set an header to send to the request.
	 * @param string The header name.
	 * @param string The header value.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */
	setHeader: function(name, value) {
		this.headers.set(name, value);
	},
	
	/**
	 * Set the content request header which will be handled by the server in order to
	 * return the content or the content with the decorator.
	 * @param string content or decorator.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */
	setContentRequestHeader: function(value) {
		this.setHeader('X-Content-Request', value);
	},
	
	/**
	 * Set an parameter to send to the request.
	 * @param string The parameter name.
	 * @param string The parameter value.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */
	setParameter: function(name, value) {
		this.parameters.set(name, value);
	},
	
	/**
	 * Clear all the parameters.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */
	clearParameters: function() {
		this.parameters = null;
		this.parameters = new Hash();
	},
	
	/**
	 * Prepare the ajax request.
	 * @param string The mode
	 * @param string The parameter value.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */
	prepare: function(mode) {
		this.fireEvent('request');
		switch (mode) {
			case 'json': this.request = new Request.JSON({url: this.url, method: this.options.method, evalScripts: this.options.evalScripts, data: this.parameters.toQueryString(), headers: this.headers.getClean(), onComplete: this.jsonRequestCompleted.bind(this)}); break;
			case 'html': this.request = new Request.HTML({url: this.url, method: this.options.method, evalScripts: this.options.evalScripts, data: this.parameters.toQueryString(), headers: this.headers.getClean(), onComplete: this.htmlRequestCompleted.bind(this)}); break;
			default:     this.request = new Request.HTML({url: this.url, method: this.options.method, evalScripts: this.options.evalScripts, data: this.parameters.toQueryString(), headers: this.headers.getClean(), onComplete: this.htmlRequestCompleted.bind(this)}); break;  
		}
	},
	
	/**
	 * Execute the command and request for an html response.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */
	executeHtml: function() {
		this.loading = true;
		this.setContentRequestHeader('content');
		this.prepare('html');
		this.request.send();
	},
	
	/**
	 * Execute the command and request for a json response.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */
	executeJson: function() {
		this.loading = true;	
		this.prepare('json');
		this.request.send();
	},

	/**
	 * This is called when the html request is completed.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */
	htmlRequestCompleted: function(responseTree, responseElements, responseHtml, responseJavaScript) {
		this.loading = false;
		this.fireEvent('complete', [responseTree, responseElements, responseHtml, responseJavaScript]);
	},
	
	/**
	 * This is called when the html request is completed.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */	
	jsonRequestCompleted: function(responseJson, responseText) {
		this.loading = false;
		this.fireEvent('complete', [responseJson, responseText]);
	},
	
	/**
	 * This is called when the html request is completed.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */	
	isError: function() {
		return false;
	},
	
	/**
	 * Indicate whether or not the command is loading.
	 * @return bool Whether or not this is loading.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0	 
	 */
	isLoading: function() {
		return this.loading;
	}
});// +---------------------------------------------------------------------------+
// | This file is part of the Mootools Addon project.                          |
// | Copyright (C) Lemieux BÃ©dard Communication                                |
// |                                                                           |
// | For the full copyright and license information, please view the LICENSE   |
// | file that was distributed with this source code.                          |
// +---------------------------------------------------------------------------+

Moo.Filter = {};// +---------------------------------------------------------------------------+
// | This file is part of the Mootools Addon project.                          |
// | Copyright (C) Lemieux BÃ©dard Communication                                |
// |                                                                           |
// | For the full copyright and license information, please view the LICENSE   |
// | file that was distributed with this source code.                          |
// +---------------------------------------------------------------------------+

/**
 * Moo.Filter.Highlight.
 * Filter through some text and highlight the matches.
 * content of a web page.
 * @package Filter
 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
 * @copyright Lemieux Bedard Communication (francois.patry@lemieuxbedard.com)
 * @since 2.0.0
 * @version 1.0.0
 */
Moo.Filter.Highlight = new Class
({
	/**
	 * @implements Events, Options.
	 */
	Implements: [Events, Options],
	
	/**
	 * @var object The options.
	 */
	options: {
		onBegin: $empty,
		onFinish: $empty,
		onMatch: $empty,
		onMismatch: $empty,
		onReset: $empty,
		emphasize: true,
		css: {
			parentMatch: 'filter-parent-match',
			elementMatch: 'filter-element-match'
		}
	},
	
	/**
	 * @var array The array to search for paths.
	 */
	paths: null,

	/**
	 * Initialize the text filter.
	 * @param object The root element of each searchable items.
	 * @param object The text input element used to filter.
	 * @param object The options.
	 * @author Jean-Philippe DÃ©ry (jean-philippe.dery@lemieuxbedard.com)
 	 * @since 2.0.0
	 */
	initialize: function(root, paths, options) {
		this.setOptions(options);
		this.root = root;
		this.paths = paths;
	},
	
	/**
	 * Add a path to seach.
	 * @param string The path to search.
	 * @return void
	 * @author Jean-Philippe DÃ©ry (jean-philippe.dery@lemieuxbedard.com)
 	 * @since 2.0.0
	 */
	addPath: function(path) {
		this.path.push(path);
	},
	
	/**
	 * Execute the filter and emphasize the element that were founds.
	 * @param string The path to search.
	 * @return void
	 * @author Jean-Philippe DÃ©ry (jean-philippe.dery@lemieuxbedard.com)
 	 * @since 2.0.0
	 */	
	filter: function(str) {
		this.clear();		
		if (str.length > 0) {
			str = str.escapeRegExp();
			var regex = new RegExp(str, 'i');
			var roots = $$(this.root);
			this.fireEvent('begin', regex);
			roots.each(function(root) {
				var found = false;
				// starts by looking at each root element and find whether or not
				// the node contains the searched text
				this.paths.each(function(path) {
					var nodes = root.getElements(path);
					if (nodes) {
						nodes.each(function(node) {
							var text = node.get('text');
							if (text.match(regex)) {
								found = true;
								// empahsize each parts of the text that matched the regular
								// expression if this option is selected.
								if (this.options.emphasize) this.emphasize(node, regex);
							}
						}, this);	
					}
				}, this);
				// finally we look whether or not the parent node contains the text 
				// we are looking for. In this case we apply a special css class and 
				// trigger an event
				if (found) {
					root.addClass(this.options.css.parentMatch);
					this.fireEvent('match', [root]);
				} else {
					if (root.hasClass(this.options.css.parentMatch)) {
						root.removeClass(this.options.css.parentMatch);
					}
					this.fireEvent('mismatch', [root]);
				}
			}, this);
			this.fireEvent('finish');
		} else {
			var roots = $$(this.root);
			roots.each(function(root) {
				this.fireEvent('reset', [root]);
			}, this);
		}
	},

	/**
	 * Emphasize the text of a node which match a given regular expression.
	 * @param object The first node.
	 * @param object The regular expression.
	 * @return void.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */	
	emphasize: function(node, regex) {
		if (node.nodeType === 3) {
			var match = node.data.match(regex);
			if (match) {
				var textnode = node.splitText(match.index);
				textnode.splitText(match[0].length);
				var cloneTextnode = textnode.cloneNode(true);
				var span = new Element('span');
				span.addClass(this.options.css.elementMatch);
				span.appendChild(cloneTextnode);
				textnode.parentNode.replaceChild(span, textnode);
				return 1;
			}
		} else {
			if (node.nodeType === 1 && node.childNodes) {
				if ((node.tagName === 'SPAN' && node.className === this.options.css.elementMatch) == false) {
					for (var i = 0; i < node.childNodes.length; i++) {
						i += this.emphasize(node.childNodes[i],regex);
					}
				}
			}
		}
		return 0;
	},
	
	/**
	 * Clear all the matches that were found inside the paths.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */
	clear: function() {
		var klass = this.root + '.' + this.options.css.parentMatch;
		var roots = $$(klass);
		roots.each(function(root) {
			this.normalize(root);
		}, this);
	},
	
	/**
	 * Remove all the highlights on the searchable pats inside a root
	 * element. This will also rebuild the text node tree.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */	
	normalize: function(root) {
		this.paths.each(function(path) {
			var nodes = root.getElements(path);
			if (nodes) {
				nodes.each(function(node) {
					var klass = '.' + this.options.css.elementMatch;
					var highlights = node.getElements(klass);
					if (highlights) {
						highlights.each(function(highlight) {
							highlight.appendText(highlight.get('html'), 'after').destroy(); 
						}, this);					
					}
					// rebuild the textnode tree otherwise we won't be table to search
					// for string like admin and then administrator
					node.set('html', node.get('html'));
				}, this);
			}
		}, this);
	}
});// +---------------------------------------------------------------------------+
// | This file is part of the Mootools Addon project.                          |
// | Copyright (C) Lemieux BÃ©dard Communication                                |
// |                                                                           |
// | For the full copyright and license information, please view the LICENSE   |
// | file that was distributed with this source code.                          |
// +---------------------------------------------------------------------------+// +---------------------------------------------------------------------------+
// | This file is part of the Mootools Addon project.                          |
// | Copyright (C) Lemieux BÃ©dard Communication                                |
// |                                                                           |
// | For the full copyright and license information, please view the LICENSE   |
// | file that was distributed with this source code.                          |
// +---------------------------------------------------------------------------+

/**
 * Moo.Finder
 * This class is used to find an element using a selector from
 * a given context. The context can be a dom tree or a text tree given by an
 * ajax response.
 * @package Finder
 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
 * @copyright Lemieux Bedard Communication (francois.patry@lemieuxbedard.com)
 * @since 1.0.0
 * @version 1.0.0
 */
Moo.Finder = new Class
({
	/**
	 * @var object The context to search in.
	 */
	context : null,

	/**
	 * Constructor. Set the context used to search in. The context may be a
	 * simple dom object or an ajax response object. This object will search
	 * in both type.
	 * @param object The context.
	 * @return object This class.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */
	initialize : function(context) {
		this.setContext(context);
		return this;
	},

	/**
	 * Set the context used to search in. The context may be a simple dom object
	 * or an ajax response object. This object will search in both type.
	 * @param object The context.
	 * @return object A reference to this object.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */
	setContext : function(context) {
		if (context) { 
			if (typeof context == 'string') { 			
				var html = context.match(/<body[^>]*>([\s\S]*?)<\/body>/i);
				html = (html) ? html[1] : context;		
				this.context = new Element('div').setHtml(html);
			}
		}
		return this;
	},

	/**
	 * Return the context used to search in. The context may be a simple dom object
	 * or an ajax response object. This object will search in both type.
	 * @return object The context.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */
	getContext : function() {
		return this.context;
	},

	/**
	 * Try to find an element in the current context. It's important to specify
	 * both the element id and tag so the search will be successfully in a
	 * ajax response type of context.
	 * @param string The search id.
	 * @param string The search type.
	 * @return object The result.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */
	find : function(selector) {
		if (this.context == null) return document.getElement(selector);
		// the getElement method will only for some reason find class selectors and
		// not selectors with an id. If we are looking for an id we must search it
		// the old string way
		if (selector.search('#') > -1) {
			selector = selector.replace('#', '');
			selector = selector.replace(' ', '');
			return this.context.getElement('*[id=' + selector + ']');
		} else {
			return this.context.getElement(selector);
		}
	}
});Moo.Fx={}; 
 
/* A workaround for IE issues in mootools 1.2.1
 * - Recreates FX.Scroll() but utilises 1.2.0's getPosition/getOffset routines.
 */
Moo.Fx.ScrollTo = new Class({
 
    'Extends': Fx.Scroll,
 
    'styleString': Element.getComputedStyle,
    'styleNumber': function(element, style) {
        return this.styleString(element, style).toInt() || 0;
    },
    'borderBox': function(element) {
        return this.styleString(element, '-moz-box-sizing') == 'border-box';
    },
    'topBorder': function(element) {
    return 0;
        return this.styleNumber(element, 'border-top-width');
    },
    'leftBorder': function(element) {
    return 0;
        return this.styleNumber(element, 'border-left-width');
    },
    'isBody': function(element) {
        return (/^(?:body|html)$/i).test(element.tagName);
    }, 
    'toElement': function(el) {
        var offset   = {x: 0, y: 0};
        var element  = $(el);
       
        if (this.isBody(element)) {
            return offset;
        }
        var scroll = element.getScrolls();
               
        while (element && !this.isBody(element)){
            offset.x += element.offsetLeft;
            offset.y += element.offsetTop;
           
            if (Browser.Engine.gecko){
                if (!this.borderBox(element)){
                    offset.x += this.leftBorder(element);
                    offset.y += this.topBorder(element);
                }
                var parent = element.parentNode;
                if (parent && this.styleString(parent, 'overflow') != 'visible'){
                    offset.x += this.leftBorder(parent);
                    offset.y += this.topBorder(parent);
                }
            } else if (Browser.Engine.trident || Browser.Engine.webkit){
                offset.x += this.leftBorder(element);
                offset.y += this.topBorder(element);
            }
 
            element = element.offsetParent;
            if (Browser.Engine.trident) {
                while (element && !element.currentStyle.hasLayout) {
                    element = element.offsetParent;
                }
            }
        }
        if (Browser.Engine.gecko && !this.borderBox(element)){
            offset.x -= this.leftBorder(element);
            offset.y -= this.topBorder(element);
        }
       
        var relative = this.element;
        var relativePosition = (relative && (relative = $(relative))) ? relative.getPosition() : {x: 0, y: 0};
        var position = {x: offset.x - scroll.x, y: offset.y - scroll.y};
       
        return this.start(position.x - relativePosition.x, position.y - relativePosition.y);
    }
});
 
 // +---------------------------------------------------------------------------+
// | This file is part of the Mootools Addon project.                          |
// | Copyright (C) Lemieux BÅ½dard Communication                                |
// |                                                                           |
// | For the full copyright and license information, please view the LICENSE   |
// | file that was distributed with this source code.                          |
// +---------------------------------------------------------------------------+

Moo.Swiff = {};// +---------------------------------------------------------------------------+
// | This file is part of the Mootools Addon project.                          |
// | Copyright (C) Lemieux BÅ½dard Communication                                |
// |                                                                           |
// | For the full copyright and license information, please view the LICENSE   |
// | file that was distributed with this source code.                          |
// +---------------------------------------------------------------------------+

/**
 * Moo.Swiff.Uploader - Flash FileReference Control
 * @version 3.0
 * @license MIT License
 * @author Harald Kirschner <http://digitarald.de>
 * @author Valerio Proietti, <http://mad4milk.net>
 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
 * @copyright Authors
 * @since 2.0.0
 * @version 2.0.0 
 */
Moo.Swiff.Uploader = new Class({

	Extends: Swiff,

	Implements: Events,

	options: {
		path: 'swf/Moo.Swiff.Uploader.swf',
		target: null,
		zIndex: 9999,
		height: 30,
		width: 100,
		callBacks: null,
		params: {
			wMode: 'opaque',
			menu: 'false',
			allowScriptAccess: 'always'
		},

		typeFilter: null,
		multiple: true,
		queued: true,
		verbose: false,

		url: null,
		method: null,
		data: null,
		mergeData: true,
		fieldName: null,

		fileSizeMin: 1,
		fileSizeMax: null, // Official limit is 100 MB for FileReference, but I tested up to 2Gb!
		allowDuplicates: false,
		timeLimit: (Browser.Platform.linux) ? 0 : 30,

		buttonImage: null,
		policyFile: null,
		
		fileListMax: 0,
		fileListSizeMax: 0,

		instantStart: false,
		appendCookieData: false,
		
		fileClass: null
		/*
		onLoad: $empty,
		onFail: $empty,
		onStart: $empty,
		onQueue: $empty,
		onComplete: $empty,
		onBrowse: $empty,
		onDisabledBrowse: $empty,
		onCancel: $empty,
		onSelect: $empty,
		onSelectSuccess: $empty,
		onSelectFail: $empty,
		
		onButtonEnter: $empty,
		onButtonLeave: $empty,
		onButtonDown: $empty,
		onButtonDisable: $empty,
		
		onFileStart: $empty,
		onFileStop: $empty,
		onFileRequeue: $empty,
		onFileOpen: $empty,
		onFileProgress: $empty,
		onFileComplete: $empty,
		onFileRemove: $empty,
		
		onBeforeStart: $empty,
		onBeforeStop: $empty,
		onBeforeRemove: $empty
		*/
	},

	initialize: function(options) {
		// protected events to control the class, added
		// before setting options (which adds own events)
		this.addEvent('load', this.initializeSwiff, true)
			.addEvent('select', this.processFiles, true)
			.addEvent('complete', this.update, true)
			.addEvent('fileRemove', function(file) {
				this.fileList.erase(file);
			}.bind(this), true);

		this.setOptions(options);

		// callbacks are no longer in the options, every callback
		// is fired as event, this is just compat
		if (this.options.callBacks) {
			Hash.each(this.options.callBacks, function(fn, name) {
				this.addEvent(name, fn);
			}, this);
		}

		this.options.callBacks = {
			fireCallback: this.fireCallback.bind(this)
		};

		var path = this.options.path;
		if (!path.contains('?')) path += '?noCache=' + $time(); // cache in IE

		// container options for Swiff class
		this.options.container = this.box = new Element('span', {'class': 'swiff-uploader-box'}).inject($(this.options.container) || document.body);

		// target 
		this.target = $(this.options.target);
		if (this.target) {
			var scroll = window.getScroll();
			this.box.setStyles({
				position: 'absolute',
				visibility: 'visible',
				zIndex: this.options.zIndex,
				overflow: 'hidden',
				height: 1, width: 1,
				top: scroll.y, left: scroll.x
			});
			
			// we force wMode to transparent for the overlay effect
			this.parent(path, {
				params: {
					wMode: 'transparent'
				},
				height: '100%',
				width: '100%'
			});
			
			this.target.addEvent('mouseenter', this.reposition.bind(this, []));
			
			// button interactions, relayed to to the target
			this.addEvents({
				buttonEnter: this.targetRelay.bind(this, ['mouseenter']),
				buttonLeave: this.targetRelay.bind(this, ['mouseleave']),
				buttonDown: this.targetRelay.bind(this, ['mousedown']),
				buttonDisable: this.targetRelay.bind(this, ['disable'])
			});
			
			this.reposition();
			window.addEvent('resize', this.reposition.bind(this, []));
		} else {
			this.parent(path);
		}

		this.inject(this.box);

		this.fileList = [];
		
		this.size = this.uploading = this.bytesLoaded = this.percentLoaded = 0;
		
		if (Browser.Plugins.Flash.version < 9) {
			this.fireEvent('fail', ['flash']);
		} else {
			this.verifyLoad.delay(1000, this);
		}
	},
	
	verifyLoad: function() {
		if (this.loaded) return;
		if (!this.object.parentNode) {
			this.fireEvent('fail', ['disabled']);
		} else if (this.object.style.display == 'none') {
			this.fireEvent('fail', ['hidden']);
		} else if (!this.object.offsetWidth) {
			this.fireEvent('fail', ['empty']);
		}
	},

	fireCallback: function(name, args) {
		// file* callbacks are relayed to the specific file
		if (name.substr(0, 4) == 'file') {
			// updated queue data is the second argument
			if (args.length > 1) this.update(args[1]);
			var data = args[0];
			
			var file = this.findFile(data.id);
			this.fireEvent(name, file || data, 5);
			if (file) {
				var fire = name.replace(/^file([A-Z])/, function($0, $1) {
					return $1.toLowerCase();
				});
				file.update(data).fireEvent(fire, [data], 10);
			}
		} else {
			this.fireEvent(name, args, 5);
		}
	},

	update: function(data) {
		// the data is saved right to the instance 
		$extend(this, data);
		this.fireEvent('queue', [this], 10);
		return this;
	},

	findFile: function(id) {
		for (var i = 0; i < this.fileList.length; i++) {
			if (this.fileList[i].id == id) return this.fileList[i];
		}
		return null;
	},

	initializeSwiff: function() {
		// extracted options for the swf 
		this.remote('initialize', {
			width: this.options.width,
			height: this.options.height,
			typeFilter: this.options.typeFilter,
			multiple: this.options.multiple,
			queued: this.options.queued,
			url: this.options.url,
			method: this.options.method,
			data: this.options.data,
			mergeData: this.options.mergeData,
			fieldName: this.options.fieldName,
			verbose: this.options.verbose,
			fileSizeMin: this.options.fileSizeMin,
			fileSizeMax: this.options.fileSizeMax,
			allowDuplicates: this.options.allowDuplicates,
			timeLimit: this.options.timeLimit,
			buttonImage: this.options.buttonImage,
			policyFile: this.options.policyFile
		});

		this.loaded = true;

		this.appendCookieData();
	},
	
	targetRelay: function(name) {
		if (this.target) this.target.fireEvent(name);
	},

	reposition: function(coords) {
		// update coordinates, manual or automatically
		coords = coords || (this.target && this.target.offsetHeight)
			? this.target.getCoordinates(this.box.getOffsetParent())
			: {top: window.getScrollTop(), left: 0, width: 40, height: 40}
		this.box.setStyles(coords);
		this.fireEvent('reposition', [coords, this.box, this.target]);
	},

	setOptions: function(options) {
		if (options) {
			if (options.url) options.url = Moo.Swiff.Uploader.qualifyPath(options.url);
			if (options.buttonImage) options.buttonImage = Moo.Swiff.Uploader.qualifyPath(options.buttonImage);
			this.parent(options);
			if (this.loaded) this.remote('setOptions', options);
		}
		return this;
	},

	setEnabled: function(status) {
		this.remote('setEnabled', status);
	},

	start: function() {
		this.fireEvent('beforeStart');
		this.remote('start');
	},

	stop: function() {
		this.fireEvent('beforeStop');
		this.remote('stop');
	},

	remove: function() {
		this.fireEvent('beforeRemove');
		this.remote('remove');
	},

	fileStart: function(file) {
		this.remote('fileStart', file.id);
	},

	fileStop: function(file) {
		this.remote('fileStop', file.id);
	},

	fileRemove: function(file) {
		this.remote('fileRemove', file.id);
	},

	fileRequeue: function(file) {
		this.remote('fileRequeue', file.id);
	},

	appendCookieData: function() {
		var append = this.options.appendCookieData;
		if (!append) return;
		
		var hash = {};
		document.cookie.split(/;\s*/).each(function(cookie) {
			cookie = cookie.split('=');
			if (cookie.length == 2) {
				hash[decodeURIComponent(cookie[0])] = decodeURIComponent(cookie[1]);
			}
		});

		var data = this.options.data || {};
		if ($type(append) == 'string') data[append] = hash;
		else $extend(data, hash);

		this.setOptions({data: data});
	},

	processFiles: function(successraw, failraw, queue) {
		var cls = this.options.fileClass || Moo.Swiff.Uploader.File;

		var fail = [], success = [];

		if (successraw) {
			successraw.each(function(data) {
				var ret = new cls(this, data);
				if (!ret.validate()) {
					ret.remove.delay(10, ret);
					fail.push(ret);
				} else {
					this.size += data.size;
					this.fileList.push(ret);
					success.push(ret);
					ret.render();
				}
			}, this);

			this.fireEvent('selectSuccess', [success], 10);
		}

		if (failraw || fail.length) {
			fail.extend((failraw) ? failraw.map(function(data) {
				return new cls(this, data);
			}, this) : []).each(function(file) {
				file.invalidate().render();
			});

			this.fireEvent('selectFail', [fail], 10);
		}

		this.update(queue);

		if (this.options.instantStart && success.length) this.start();
	}

});

$extend(Moo.Swiff.Uploader, {

	STATUS_QUEUED: 0,
	STATUS_RUNNING: 1,
	STATUS_ERROR: 2,
	STATUS_COMPLETE: 3,
	STATUS_STOPPED: 4,

	log: function() {
		if (window.console && console.info) console.info.apply(console, arguments);
	},

	unitLabels: {
		b: [{min: 1, unit: 'B'}, {min: 1024, unit: 'kB'}, {min: 1048576, unit: 'MB'}, {min: 1073741824, unit: 'GB'}],
		s: [{min: 1, unit: 's'}, {min: 60, unit: 'm'}, {min: 3600, unit: 'h'}, {min: 86400, unit: 'd'}]
	},

	formatUnit: function(base, type, join) {
		var labels = Moo.Swiff.Uploader.unitLabels[(type == 'bps') ? 'b' : type];
		var append = (type == 'bps') ? '/s' : '';
		var i, l = labels.length, value;

		if (base < 1) return '0 ' + labels[0].unit + append;

		if (type == 's') {
			var units = [];

			for (i = l - 1; i >= 0; i--) {
				value = Math.floor(base / labels[i].min);
				if (value) {
					units.push(value + ' ' + labels[i].unit);
					base -= value * labels[i].min;
					if (!base) break;
				}
			}

			return (join === false) ? units : units.join(join || ', ');
		}

		for (i = l - 1; i >= 0; i--) {
			value = labels[i].min;
			if (base >= value) break;
		}

		return (base / value).toFixed(1) + ' ' + labels[i].unit + append;
	}

});

Moo.Swiff.Uploader.qualifyPath = (function() {
	
	var anchor;
	
	return function(path) {
		(anchor || (anchor = new Element('a'))).href = path;
		return anchor.href;
	};

})();

Moo.Swiff.Uploader.File = new Class({

	Implements: Events,

	initialize: function(base, data) {
		this.base = base;
		this.update(data);
	},

	update: function(data) {
		return $extend(this, data);
	},

	validate: function() {
		var options = this.base.options;
		
		if (options.fileListMax && this.base.fileList.length >= options.fileListMax) {
			this.validationError = 'fileListMax';
			return false;
		}
		
		if (options.fileListSizeMax && (this.base.size + this.size) > options.fileListSizeMax) {
			this.validationError = 'fileListSizeMax';
			return false;
		}
		
		return true;
	},

	invalidate: function() {
		this.invalid = true;
		this.base.fireEvent('fileInvalid', this, 10);
		return this.fireEvent('invalid', this, 10);
	},

	render: function() {
		return this;
	},

	setOptions: function(options) {
		if (options) {
			if (options.url) options.url = Moo.Swiff.Uploader.qualifyPath(options.url);
			this.base.remote('fileSetOptions', this.id, options);
			this.options = $merge(this.options, options);
		}
		return this;
	},

	start: function() {
		this.base.fileStart(this);
		return this;
	},

	stop: function() {
		this.base.fileStop(this);
		return this;
	},

	remove: function() {
		this.base.fileRemove(this);
		return this;
	},

	requeue: function() {
		this.base.fileRequeue(this);
	} 

});
// +---------------------------------------------------------------------------+
// | This file is part of the Mootools Addon project.                          |
// | Copyright (C) Lemieux BÃ©dard Communication                                |
// |                                                                           |
// | For the full copyright and license information, please view the LICENSE   |
// | file that was distributed with this source code.                          |
// +---------------------------------------------------------------------------+

Moo.Request = {};
Moo.Request.Form = {};// +---------------------------------------------------------------------------+
// | This file is part of the Mootools Addon project.                          |
// | Copyright (C) Lemieux BÃ©dard Communication                                |
// |                                                                           |
// | For the full copyright and license information, please view the LICENSE   |
// | file that was distributed with this source code.                          |
// +---------------------------------------------------------------------------+

/**
 * Moo.Request.Element
 * This class handles a form by sending the form contents using a json request.
 * @package Request
 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
 * @copyright Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
 * @since 2.0.0
 * @version 2.0.0
 */
Moo.Request.Element = new Class
({
	/**
	 * @implements Events, Options
	 */
	Implements: [Events, Options],
	
	/**
	 * @var string The selector.
	 */
	selector: null,
	
	/**
	 * @var string The request url.
	 */
	url: null,
	
	/**
	 * @var object The options.
	 */
	options: {
		onRequest: $empty,
		onComplete: $empty,
		evalScripts: false,
		method: 'get'		
	},
	
	/**
	 * Initialize the class.
	 * @param string The url to load from.
	 * @param object The options.
	 * @return object A reference to this object.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */
	initialize: function(url, options) {
		this.url = url;
		this.setOptions(options);
		return this;
	},
	
	/**
	 * Retrieve the element using the selector.
	 * @param string The selector string.
	 * @return object A reference to this object.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */	
	retrieve: function(selector) {
		this.selector = selector;
		new Request.HTML({
			url: this.url, 
			method: this.options.method, 
			evalScripts: this.options.evalScripts,
			headers: {'Agavi-Request-For': 'agavi-bare-layout'},
			onRequest: this.onRetrieveRequest.bind(this),
			onComplete: this.onRetrieveComplete.bind(this)
		}).send();			
	},
	
	/**
	 * This happens when request start.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */		
	onRetrieveRequest: function() {
		this.fireEvent('request');
	},
	
	/**
	 * This happens when request completes.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */	
	onRetrieveComplete: function(responseTree, responseElements, responseHtml, responseJavascript) {
		// it's possible to use this class without a selector. If the selecotr is not present
		// the method will return the whole response minus the head and body tags otherwise
		// it will try to retrieve the elemenet and fire the event.
		if (this.selector) {
			var response = new Moo.Finder(responseHtml).find(this.selector);
			if (response) {
				this.fireEvent('complete', [response, responseHtml, responseJavascript, true]);
				return;
			}
		} 
		// at this point there is either no selectos or the selector used returned
		// nothing in particular
		var response = responseHtml.match(/<body[^>]*>([\s\S]*?)<\/body>/i);
		response = (response) ? response[1] : responseHtml;	
		response = new Element('div').setHtml(response);
		this.fireEvent('complete', [response, responseHtml, responseJavascript, false]);				
	}
});// +---------------------------------------------------------------------------+
// | This file is part of the Mootools Addon project.                          |
// | Copyright (C) Lemieux BÃ©dard Communication                                |
// |                                                                           |
// | For the full copyright and license information, please view the LICENSE   |
// | file that was distributed with this source code.                          |
// +---------------------------------------------------------------------------+

/**
 * Moo.Request.Element.Update
 * This class is used to refresh a given element.
 * @package Request
 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
 * @copyright Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
 * @since 2.0.0
 * @version 2.0.0
 */
Moo.Request.Element.Update = new Class
({
	/**
	 * @implements Events, Options
	 */
	Implements: [Events, Options, Moo.Animable],
	
	/**
	 * @var object The options.
	 */	
	options: {
		onRequest: $empty,
		onComplete: $empty,
		location: null,
		animations: {
			replace: null
		}
	},
	
	/**
	 * @var object The element to update.
	 */
	element: null,
	
	/**
	 * Initialize the class.
	 * @param object The options.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */		
	initialize: function(options) {
		this.setOptions(options);
		return this;
	},
	
	/**
	 * Initialize the class.
	 * @param object The options.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */		
	refresh: function(element) {
		this.element = element;
		var location = this.options.location ? this.options.location : window.location.href;
		var elementId = this.element.getId();
		if (elementId) {
			// it's easy to simply find an element using and id and replace 
			// it with it's updated version
			new Moo.Request.Element(location, {
				onRequest: this.onUpdateWithIdRequest.bind(this),
				onComplete: this.onUpdateWithIdComplete.bind(this)
			}).retrieve('#%s'.sprintf(elementId));
		} else {
			if (console.log) {
				console.log('Moo.Request.Element.Update');
				console.log('You are trying to refresh an element that does not have an ID.');
			}
			// the element does not have an id. In that case we look for
			// all the elements with the same tag and we compare
			// new Moo.Request.Element(location, {
			// 	onRequest: this.onUpdateWithoutIdRequest.bind(this),
			//	onComplete: this.onUpdateWithoutIdComplete.bind(this)
			// }).retrieve();
		}
	},

	/**
	 * Initialize the class.
	 * @param object The options.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */	
	replace: function(oldElement, newElement) {
		oldElement.copyActors(newElement);
		newElement.replaces(oldElement);
		this.animate(newElement, this.options.animations.replace, function() {} );
	},
	
	/**
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */		
	onUpdateWithIdRequest: function() {
		this.fireEvent('request');
	},
	
	/**
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */	
	onUpdateWithIdComplete: function(response) {
		this.replace(this.element, response);
		this.fireEvent('complete', [response]);
		this.element = null;
	},
	
	/**
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */		
	onUpdateWithoutIdRequest: function() {
		this.fireEvent('request');
	},
	
	/**
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */		
	onUpdateWithoutIdComplete: function(response) {
		response.getElements(this.element.tagName.toLowerCase()).each(function(el) {
			if (el === this.element) {
				this.replace(this.element, el);
				this.fireEvent('complete', [response]);
			}
		}, this);
	}
});// +---------------------------------------------------------------------------+
// | This file is part of the Mootools Addon project.                          |
// | Copyright (C) Lemieux BÃ©dard Communication                                |
// |                                                                           |
// | For the full copyright and license information, please view the LICENSE   |
// | file that was distributed with this source code.                          |
// +---------------------------------------------------------------------------+

/**
 * Moo.Request.Form.HTML
 * This class handles a form by sending the form contents using a json request.
 * @package Request
 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
 * @copyright Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
 * @since 2.0.0
 * @version 2.0.0
 */
Moo.Request.Form.HTML = new Class
({	
	/**
	 * @implements Events Options The basic element methods.
	 */
	Implements: [Events, Options, Class.ToElement],
	
	/**
	 * @var object The options.
	 */
	options: {
		onRequest: $empty,
		onComplete: $empty,
		evalScripts: true
	},
		
	/**
	 * @var object The form element.
	 */
	element: null,
	
	/**
	 * @var bool Whether or not the form is currently requesting.
	 */
	requesting: false,
	
	/**
	 * @var object The target that triggered the submit event.
	 */
	sender: null,
	
	/**
	 * Initialize the form.
	 * @param object The form element.
	 * @param object The options.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */
	initialize: function(element, options) {
		this.setOptions(options);
		this.element = $(element);
		this.element.addEvent('submit', this.onSubmit.bind(this));
		var request = this.element.get('send');
		request.addEvent('request', this.onSubmitRequest.bind(this));
		request.addEvent('complete', this.onSubmitComplete.bind(this));
		request.evalScripts = this.options.evalScripts;
	},

	/**
	 * Send the form.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */		
	submit: function() {
		this.element.send();
	},	
	
	/**
	 * Indicate whether or not the form is currently requesting.
	 * @return Bool Whether or not the form is currently requesting.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0 
	 */
	isRequesting: function() {
		return this.requesting;
	},
	
	/**
	 * Fired when the form is submited.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */		
	onSubmit: function(e) {
		e.stop();
		if (this.requesting == false) {
			this.requesting = true;
			this.sender = e.event.explicitOriginalTarget;
			this.submit();
		}
		return false;
	},
	
	/**
	 * Fired when the form is requested.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */	
	onSubmitRequest: function() {
		this.requesting = true;
		this.fireEvent('request', [this.sender]);
	},
	
	/**
	 * Fired when the form sending is received.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */		
	onSubmitComplete: function(responseHtml) {
		this.requesting = false;
		this.fireEvent('complete', [responseHtml, this.sender]);
	},
	
	/**
	 * Return the element binded to this object.
	 * @return object The element  binded to this object
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */	
	toElement: function() {
		return this.element;
	}	
});
// +---------------------------------------------------------------------------+
// | This file is part of the Mootools Addon project.                          |
// | Copyright (C) Lemieux BÃ©dard Communication                                |
// |                                                                           |
// | For the full copyright and license information, please view the LICENSE   |
// | file that was distributed with this source code.                          |
// +---------------------------------------------------------------------------+

/**
 * An actor is a class that will basically activate itself upon an element using
 * the applyOn method of the element.
 * @package Request
 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
 * @copyright Lemieux Bedard Communication (francois.patry@lemieuxbedard.com)
 * @since 2.0.0
 * @version 2.0.0
 */
Moo.Request.Form.HTMLActor = new Class
({
	/**
	 * @extends Moo.Actor
	 */
	Extends: Moo.Actor,
	
	/**
	 * This method will create and return the object which will act as the
	 * performer of the actor. All arguments will be passed to this method
	 * except for the element.
	 * @param object The arguments.
	 * @return object The performer object
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */
	perform: function(options) {
		return new Moo.Request.Form.HTML(this.element, options);
	}		
});
// +---------------------------------------------------------------------------+
// | This file is part of the Mootools Addon project.                          |
// | Copyright (C) Lemieux BÃ©dard Communication                                |
// |                                                                           |
// | For the full copyright and license information, please view the LICENSE   |
// | file that was distributed with this source code.                          |
// +---------------------------------------------------------------------------+

/**
 * This class handles a form by sending the form contents using a json 
 * request. In this case, the response returned will be valid json.
 * @package Request
 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
 * @copyright Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
 * @since 2.0.0
 * @version 2.0.0
 */
Moo.Request.Form.JSON = new Class
({
	/**
	 * @extend Moo.Request.Form.HTML
	 */
	Extends: Moo.Request.Form.HTML,

	/**
	 * Send the form.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */		
	submit: function() {
		var request = this.element.get('send');
		request.setHeader('Accept', 'application/json');
		request.setHeader('X-Request', 'JSON');
		this.element.send();
	},	
	
	/**
	 * Fired when the form is requested.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */	
	onSubmitRequest: function() {
		this.requesting = true;
		this.fireEvent('request', [this.sender]);
	},
	
	/**
	 * Fired when the form sending is received.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */		
	onSubmitComplete: function(responseHtml) {
		this.requesting = false;
		this.fireEvent('complete', [JSON.decode(responseHtml), this.sender]);
	}
});
// +---------------------------------------------------------------------------+
// | This file is part of the Mootools Addon project.                          |
// | Copyright (C) Lemieux BÃ©dard Communication                                |
// |                                                                           |
// | For the full copyright and license information, please view the LICENSE   |
// | file that was distributed with this source code.                          |
// +---------------------------------------------------------------------------+

/**
 * An actor is a class that will basically activate itself upon an element using
 * the applyOn method of the element.
 * @package Request
 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
 * @copyright Lemieux Bedard Communication (francois.patry@lemieuxbedard.com)
 * @since 2.0.0
 * @version 2.0.0
 */
Moo.Request.Form.JSONActor = new Class
({
	/**
	 * @extends Moo.Actor
	 */
	Extends: Moo.Actor,
	
	/**
	 * This method will create and return the object which will act as the
	 * performer of the actor. All arguments will be passed to this method
	 * except for the element.
	 * @param object The arguments.
	 * @return object The performer object
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */
	perform: function(options) {
		return new Moo.Request.Form.JSON(this.element, options);
	}	
});
// +---------------------------------------------------------------------------+
// | This file is part of the Mootools Addon project.                          |
// | Copyright (C) Lemieux BÅ½dard Communication                                |
// |                                                                           |
// | For the full copyright and license information, please view the LICENSE   |
// | file that was distributed with this source code.                          |
// +---------------------------------------------------------------------------+

/**
 * Moo.Request.Upload
 *	<div class="upload">
 *		<div class="progress"></div>
 *		<div class="button">Choisir</div>
 *		<div class="status">Aucun fichier sÃ©lectionnÃ©</div>
 *		<div class="clear"></div>
 *	</div>	
 * @version 1.1
 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
 * @copyright Authors
 * @since 2.0.0
 * @version 2.0.0  
 */
Moo.Request.Upload = new Class
({
	/**
	 * @implements Events, Options
	 */
	Implements: [Events, Options],
	
	/**
	 * @var object The options.
	 */
	options: {
		onBegin: $empty,
		onProgress: $empty,
		onComplete: $empty,
		onError: $empty,
		path: 'swf/Moo.Swiff.Uploader.swf'			
	},

	/**
	 * @var string The url to post to.
	 */
	url: null,

	/**
	 * @var object The uplaoder.
	 */
	uploader: null,
	
	/**
	 * @var object The progress bar controller.
	 */
	progress: null,
	
	/**
	 * @var object The differents elements.
	 */
	buttonElement: null,
	statusElement: null,
	progressElement: null,
	
	/**
	 * Initialize the uploader
	 * @param object The root file upload element.
	 * @param string The url to post to.
	 * @param object The options.
	 * @return object A reference to this object.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */
	initialize: function(element, url, options) {
		this.setOptions(options);
		this.buttonElement = element.getElement('.button');
		this.statusElement = element.getElement('.status');
		this.progressElement = element.getElement('.progress');		
		this.progressElement.fade('hide');
		this.progress = new Moo.UI.Progress(this.progressElement);
		this.uploader = new Moo.Swiff.Uploader({
			path: 'swf/Moo.Swiff.Uploader.swf',
			url: url,
			queued: false,
			multiple: false,
			target: this.buttonElement,
			instantStart: true,
			fileSizeMax: 1000 * 1024 * 1024,
			onSelectSuccess: this.onBegin.bind(this),
			onFileComplete: this.onComplete.bind(this),
			onQueue: this.onProgress.bind(this),
			container: element
		});
		return this;
	},
	
	/**
	 * This event happens when a file start to upload.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */	
	onBegin: function() {
		this.buttonElement.fade(0);
		this.statusElement.fade(0);
		this.progressElement.fade(1);
		this.fireEvent('begin');
	},
	
	/**
	 * This event happens when the progress bar get updated.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */		
	onProgress: function() {
		if (this.uploader.uploading) {
			this.progress.setValue(this.uploader.percentLoaded);
		}
	},
	
	/**
	 * This event happens when the progress bar get updated.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */	
	onComplete: function(file) {
		if (file.response.error) {
			this.statusElement.set('text', 'Erreur');
			this.fireEvent('error', file.response.error);
			return;
		}
		var file = JSON.decode(file.response.text);
		if (file) {
			this.statusElement.set('text', file.name);
			this.fireEvent('complete', file.name);
		}
		this.progressElement.fade(0);				
		this.buttonElement.fade(1);
		this.statusElement.fade(1);
	}	
});// +---------------------------------------------------------------------------+
// | This file is part of the Mootools Addon project.                          |
// | Copyright (C) Lemieux BÅ½dard Communication                                |
// |                                                                           |
// | For the full copyright and license information, please view the LICENSE   |
// | file that was distributed with this source code.                          |
// +---------------------------------------------------------------------------+

/**
 * Moo.Request.Upload.Fancy
 * FancyUpload - Flash meets Ajax for powerful and elegant uploads.
 * Updated to latest 3.0 API. Hopefully 100% compat!
 * @version 1.1
 * @license MIT License
 * @author Harald Kirschner <mail [at] digitarald [dot] de>
 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
 * @copyright Authors
 * @since 2.0.0
 * @version 2.0.0  
 */
Moo.Request.Upload.Fancy = new Class({

	/**
	 * @extends Moo.Swiff.Uploader.
	 */
	Extends: Moo.Swiff.Uploader,
	
	/**
	 * @var object The options.
	 */
	options: {
		queued: 1,
		limitSize: 0,
		limitFiles: 0,
		validateFile: $lambda(true)
	},
	
	/**
	 * @author Harald Kirschner <mail [at] digitarald [dot] de>
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */
	initialize: function(status, list, options) {
		this.status = $(status);
		this.list = $(list);

		// compat
		options.fileClass = options.fileClass || Moo.Request.Upload.Fancy.File;
		options.fileSizeMax = options.limitSize || options.fileSizeMax;
		options.fileListMax = options.limitFiles || options.fileListMax;

		this.parent(options);

		this.addEvents({
			'load': this.render,
			'select': this.onSelect,
			'cancel': this.onCancel,
			'start': this.onStart,
			'queue': this.onQueue,
			'complete': this.onComplete
		});
	},

	/**
	 * @author Harald Kirschner <mail [at] digitarald [dot] de>
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */
	render: function() {
		this.overallTitle = this.status.getElement('.overall-title');
		this.currentTitle = this.status.getElement('.current-title');
		this.currentText = this.status.getElement('.current-text');
//		var progress = this.status.getElement('.overall-progress');
//		this.overallProgress = new Moo.UI.Progress(progress, {
//			text: new Element('span', {'class': 'progress-text'}).inject(progress, 'after')
//		});
//		progress = this.status.getElement('.current-progress')
//		this.currentProgress = new Moo.UI.Progress(progress, {
//			text: new Element('span', {'class': 'progress-text'}).inject(progress, 'after')
//		});
		this.updateOverall();
	},

	/**
	 * @author Harald Kirschner <mail [at] digitarald [dot] de>
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */
	onSelect: function() {
		this.status.removeClass('status-browsing');
	},

	/**
	 * @author Harald Kirschner <mail [at] digitarald [dot] de>
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */
	onCancel: function() {
		this.status.removeClass('file-browsing');
	},

	/**
	 * @author Harald Kirschner <mail [at] digitarald [dot] de>
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */
	onStart: function() {
		this.status.addClass('file-uploading');
		this.overallProgress.set(0);
	},

	/**
	 * @author Harald Kirschner <mail [at] digitarald [dot] de>
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */
	onQueue: function() {
		this.updateOverall();
	},

	/**
	 * @author Harald Kirschner <mail [at] digitarald [dot] de>
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */
	onComplete: function() {
		this.status.removeClass('file-uploading');
		if (this.size) {
			this.overallProgress.start(100);
		} else {
			this.overallProgress.set(0);
			this.currentProgress.set(0);
		}
		
	},

	/**
	 * @author Harald Kirschner <mail [at] digitarald [dot] de>
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */
	updateOverall: function() {
		this.overallTitle.set('html', MooTools.lang.get('FancyUpload', 'progressOverall').substitute({
			total: Moo.Swiff.Uploader.formatUnit(this.size, 'b')
		}));
		if (!this.size) {
			this.currentTitle.set('html', MooTools.lang.get('FancyUpload', 'currentTitle'));
			this.currentText.set('html', '');
		}
	},
	
	/**
	 * @author Harald Kirschner <mail [at] digitarald [dot] de>
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */
	upload: function() {
		this.start();
	},
	
	/**
	 * @author Harald Kirschner <mail [at] digitarald [dot] de>
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */	
	removeFile: function() {
		return this.remove();
	}

});

Moo.Request.Upload.Fancy.File = new Class({
	
	Extends: Moo.Swiff.Uploader.File,

	/**
	 * @author Harald Kirschner <mail [at] digitarald [dot] de>
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */
	render: function() {
		if (this.invalid) {
			if (this.validationError) {
				var msg = MooTools.lang.get('FancyUpload', 'validationErrors')[this.validationError] || this.validationError;
				this.validationErrorMessage = msg.substitute({
					name: this.name,
					size: Moo.Swiff.Uploader.formatUnit(this.size, 'b'),
					fileSizeMin: Moo.Swiff.Uploader.formatUnit(this.base.options.fileSizeMin || 0, 'b'),
					fileSizeMax: Moo.Swiff.Uploader.formatUnit(this.base.options.fileSizeMax || 0, 'b'),
					fileListMax: this.base.options.fileListMax || 0,
					fileListSizeMax: Moo.Swiff.Uploader.formatUnit(this.base.options.fileListSizeMax || 0, 'b')
				});
			}
			this.remove();
			return;
		}
		
		this.addEvents({
			'start': this.onStart,
			'progress': this.onProgress,
			'complete': this.onComplete,
			'error': this.onError,
			'remove': this.onRemove
		});
		
		this.info = new Element('span', {'class': 'file-info'});
		this.element = new Element('li', {'class': 'file'}).adopt(
			new Element('span', {'class': 'file-size', 'html': Moo.Swiff.Uploader.formatUnit(this.size, 'b')}),
			new Element('a', {
				'class': 'file-remove',
				href: '#',
				html: MooTools.lang.get('FancyUpload', 'remove'),
				title: MooTools.lang.get('FancyUpload', 'removeTitle'),
				events: {
					click: function() {
						this.remove();
						return false;
					}.bind(this)
				}
			}),
			new Element('span', {'class': 'file-name', 'html': MooTools.lang.get('FancyUpload', 'fileName').substitute(this)}),
			this.info
		).inject(this.base.list);
	},
	
	/**
	 * @author Harald Kirschner <mail [at] digitarald [dot] de>
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */	
	validate: function() {
		return (this.parent() && this.base.options.validateFile(this));
	},
	
	/**
	 * @author Harald Kirschner <mail [at] digitarald [dot] de>
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */	
	onStart: function() {
		this.element.addClass('file-uploading');
		this.base.currentProgress.cancel().set(0);
		this.base.currentTitle.set('html', MooTools.lang.get('FancyUpload', 'currentFile').substitute(this));
	},

	/**
	 * @author Harald Kirschner <mail [at] digitarald [dot] de>
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */
	onProgress: function() {
		this.base.overallProgress.start(this.base.percentLoaded);
		this.base.currentText.set('html', MooTools.lang.get('FancyUpload', 'currentProgress').substitute({
			rate: (this.progress.rate) ? Moo.Swiff.Uploader.formatUnit(this.progress.rate, 'bps') : '- B',
			bytesLoaded: Moo.Swiff.Uploader.formatUnit(this.progress.bytesLoaded, 'b'),
			timeRemaining: (this.progress.timeRemaining) ? Moo.Swiff.Uploader.formatUnit(this.progress.timeRemaining, 's') : '-'
		}));
		this.base.currentProgress.start(this.progress.percentLoaded);
	},
	
	/**
	 * @author Harald Kirschner <mail [at] digitarald [dot] de>
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */	
	onComplete: function() {
		this.element.removeClass('file-uploading');
		this.base.currentText.set('html', 'Upload completed');
		this.base.currentProgress.start(100);
		if (this.response.error) {
			var msg = MooTools.lang.get('FancyUpload', 'errors')[this.response.error] || '{error} #{code}';
			this.errorMessage = msg.substitute($extend({
				name: this.name,
				size: Moo.Swiff.Uploader.formatUnit(this.size, 'b')
			}, this.response));
			var args = [this, this.errorMessage, this.response];
			this.fireEvent('error', args).base.fireEvent('fileError', args);
		} else {
			this.base.fireEvent('fileSuccess', [this, this.response.text || '']);
		}
	},

	/**
	 * @author Harald Kirschner <mail [at] digitarald [dot] de>
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */
	onError: function() {
		this.element.addClass('file-failed');
		var error = MooTools.lang.get('FancyUpload', 'fileError').substitute(this);
		this.info.set('html', '<strong>' + error + ':</strong> ' + this.errorMessage);
	},

	/**
	 * @author Harald Kirschner <mail [at] digitarald [dot] de>
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */
	onRemove: function() {
		this.element.getElements('a').setStyle('visibility', 'hidden');
		this.element.fade('out').retrieve('tween').chain(Element.destroy.bind(Element, this.element));
	}
});

// avoiding MooTools.lang dependency

(function() {
	var phrases = {
		'progressOverall': 'Overall Progress ({total})',
		'currentTitle': 'File Progress',
		'currentFile': 'Uploading "{name}"',
		'currentProgress': 'Upload: {bytesLoaded} with {rate}, {timeRemaining} remaining.',
		'fileName': '{name}',
		'remove': 'Remove',
		'removeTitle': 'Click to remove this entry.',
		'fileError': 'Upload failed',
		'validationErrors': {
			'duplicate': 'File <em>{name}</em> is already added, duplicates are not allowed.',
			'sizeLimitMin': 'File <em>{name}</em> (<em>{size}</em>) is too small, the minimal file size is {fileSizeMin}.',
			'sizeLimitMax': 'File <em>{name}</em> (<em>{size}</em>) is too big, the maximal file size is <em>{fileSizeMax}</em>.',
			'fileListMax': 'File <em>{name}</em> could not be added, amount of <em>{fileListMax} files</em> exceeded.',
			'fileListSizeMax': 'File <em>{name}</em> (<em>{size}</em>) is too big, overall filesize of <em>{fileListSizeMax}</em> exceeded.'
		},
		'errors': {
			'httpStatus': 'Server returned HTTP-Status <code>#{code}</code>',
			'securityError': 'Security error occured ({text})',
			'ioError': 'Error caused a send or load operation to fail ({text})'
		}
	};
	
	if (MooTools.lang) {
		MooTools.lang.set('en-US', 'FancyUpload', phrases);
	} else {
		MooTools.lang = {
			get: function(from, key) {
				return phrases[key];
			}
		};
	}
})();
// +---------------------------------------------------------------------------+
// | This file is part of the Mootools Addon project.                          |
// | Copyright (C) Lemieux BÅ½dard Communication                                |
// |                                                                           |
// | For the full copyright and license information, please view the LICENSE   |
// | file that was distributed with this source code.                          |
// +---------------------------------------------------------------------------+

/**
 * FancyUpload.Attach - Flash meets Ajax for powerful and elegant uploads.
 * @version 3.0 rc3
 * @license MIT License
 * @author Harald Kirschner <mail [at] digitarald [dot] de>
 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
 * @copyright Authors
 */
Moo.Request.Upload.Fancy.Attach = new Class({

	Extends: Moo.Swiff.Uploader,
	
	options: {
		queued: false,
		instantStart: true
	},

	/**
	 * @author Harald Kirschner <mail [at] digitarald [dot] de>
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */
	initialize: function(list, selects, options) {
		this.list = $(list);
		this.selects = $(selects) ? $$($(selects)) : $$(selects);
				
		options.target = this.selects[0];
		options.fileClass = options.fileClass || Moo.Request.Upload.Fancy.Attach.File;
		
		this.parent(options);

		var self = this;
		
		this.selects.addEvents({
			click: function() {
				return false;
			},
			mouseenter: function() {
				this.addClass('hover');
				self.reposition();
			},
			mouseleave: function() {
				this.removeClass('hover');
				this.blur();
			},
			mousedown: function() {
				this.focus();
			}
		});
		
		if (this.selects.length == 2) {
			this.selects[1].setStyle('display', 'none');
			this.addEvents({
				'selectSuccess': this.onSelectSuccess,
				'fileRemove': this.onFileRemove
			});
		}
	},

	/**
	 * @author Harald Kirschner <mail [at] digitarald [dot] de>
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */	
	onSelectSuccess: function() {
		if (this.fileList.length > 0) {
			this.selects[0].setStyle('display', 'none');
			this.selects[1].setStyle('display', 'inline');
			this.target = this.selects[1];
			this.reposition();
		}
	},
	
	/**
	 * @author Harald Kirschner <mail [at] digitarald [dot] de>
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */	
	onFileRemove: function() {
		if (this.fileList.length == 0) {
			this.selects[0].setStyle('display', 'inline');
			this.selects[1].setStyle('display', 'none');
			this.target = this.selects[0];
			this.reposition();
		}
	},
	
	/**
	 * @author Harald Kirschner <mail [at] digitarald [dot] de>
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */	
	start: function() {
		if (Browser.Platform.linux && window.confirm(MooTools.lang.get('FancyUpload', 'linuxWarning'))) return this;
		return this.parent();
	}
	
});

Moo.Request.Upload.Fancy.Attach.File = new Class({

	Extends: Moo.Swiff.Uploader.File,

	/**
	 * @author Harald Kirschner <mail [at] digitarald [dot] de>
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */
	render: function() {
		if (this.invalid) {
			if (this.validationError) {
				var msg = MooTools.lang.get('FancyUpload', 'validationErrors')[this.validationError] || this.validationError;
				this.validationErrorMessage = msg.substitute({
					name: this.name,
					size: Moo.Swiff.Uploader.formatUnit(this.size, 'b'),
					fileSizeMin: Moo.Swiff.Uploader.formatUnit(this.base.options.fileSizeMin || 0, 'b'),
					fileSizeMax: Moo.Swiff.Uploader.formatUnit(this.base.options.fileSizeMax || 0, 'b'),
					fileListMax: this.base.options.fileListMax || 0,
					fileListSizeMax: Moo.Swiff.Uploader.formatUnit(this.base.options.fileListSizeMax || 0, 'b')
				});
			}
			this.remove();
			return;
		}
		
		this.addEvents({
			'open': this.onOpen,
			'remove': this.onRemove,
			'requeue': this.onRequeue,
			'progress': this.onProgress,
			'stop': this.onStop,
			'complete': this.onComplete,
			'error': this.onError
		});
		
		this.ui = {};
		
		this.ui.element = new Element('li', {'class': 'file', id: 'file-' + this.id});
		this.ui.title = new Element('span', {'class': 'file-title', text: this.name});
		this.ui.size = new Element('span', {'class': 'file-size', text: Moo.Swiff.Uploader.formatUnit(this.size, 'b')});
		
		this.ui.cancel = new Element('a', {'class': 'file-cancel', text: 'Cancel', href: '#'});
		this.ui.cancel.addEvent('click', function() {
			this.remove();
			return false;
		}.bind(this));
		
		this.ui.element.adopt(
			this.ui.title,
			this.ui.size,
			this.ui.cancel
		).inject(this.base.list).highlight();
		
		var progress = new Element('img', {'class': 'file-progress', src: '../../assets/progress-bar/bar.gif'}).inject(this.ui.size, 'after');
		this.ui.progress = new Moo.UI.Progress(progress, {
			fit: true
		}).set(0);
					
		this.base.reposition();

		return this.parent();
	},

	/**
	 * @author Harald Kirschner <mail [at] digitarald [dot] de>
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */
	onOpen: function() {
		this.ui.element.addClass('file-uploading');
		if (this.ui.progress) this.ui.progress.set(0);
	},

	/**
	 * @author Harald Kirschner <mail [at] digitarald [dot] de>
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */
	onRemove: function() {
		this.ui = this.ui.element.destroy();
	},

	/**
	 * @author Harald Kirschner <mail [at] digitarald [dot] de>
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */
	onProgress: function() {
		if (this.ui.progress) this.ui.progress.start(this.progress.percentLoaded);
	},

	/**
	 * @author Harald Kirschner <mail [at] digitarald [dot] de>
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */
	onStop: function() {
		this.remove();
	},
	
	/**
	 * @author Harald Kirschner <mail [at] digitarald [dot] de>
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */	
	onComplete: function() {
		this.ui.element.removeClass('file-uploading');

		if (this.response.error) {
			var msg = MooTools.lang.get('FancyUpload', 'errors')[this.response.error] || '{error} #{code}';
			this.errorMessage = msg.substitute($extend({name: this.name}, this.response));
			
			this.base.fireEvent('fileError', [this, this.response, this.errorMessage]);
			this.fireEvent('error', [this, this.response, this.errorMessage]);
			return;
		}
		
		if (this.ui.progress) this.ui.progress = this.ui.progress.cancel().element.destroy();
		this.ui.cancel = this.ui.cancel.destroy();
		
		var response = this.response.text || '';
		this.base.fireEvent('fileSuccess', [this, response]);
	},

	/**
	 * @author Harald Kirschner <mail [at] digitarald [dot] de>
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */
	onError: function() {
		this.ui.element.addClass('file-failed');		
	}

});

// avoiding MooTools.lang dependency

(function() {
	var phrases = {
		'fileName': '{name}',
		'cancel': 'Cancel',
		'cancelTitle': 'Click to cancel and remove this entry.',
		'validationErrors': {
			'duplicate': 'File <em>{name}</em> is already added, duplicates are not allowed.',
			'sizeLimitMin': 'File <em>{name}</em> (<em>{size}</em>) is too small, the minimal file size is {fileSizeMin}.',
			'sizeLimitMax': 'File <em>{name}</em> (<em>{size}</em>) is too big, the maximal file size is <em>{fileSizeMax}</em>.',
			'fileListMax': 'File <em>{name}</em> could not be added, amount of <em>{fileListMax} files</em> exceeded.',
			'fileListSizeMax': 'File <em>{name}</em> (<em>{size}</em>) is too big, overall filesize of <em>{fileListSizeMax}</em> exceeded.'
		},
		'errors': {
			'httpStatus': 'Server returned HTTP-Status #{code}',
			'securityError': 'Security error occured ({text})',
			'ioError': 'Error caused a send or load operation to fail ({text})'
		},
		'linuxWarning': 'Warning: Due to a misbehaviour of Adobe Flash Player on Linux,\nthe browser will probably freeze during the upload process.\nDo you want to start the upload anyway?'
	};
	
	if (MooTools.lang) {
		MooTools.lang.set('en-US', 'FancyUpload', phrases);
	} else {
		MooTools.lang = {
			get: function(from, key) {
				return phrases[key];
			}
		};
	}
})();// +---------------------------------------------------------------------------+
// | This file is part of the Mootools Addon project.                          |
// | Copyright (C) Lemieux BÃ©dard Communication                                |
// |                                                                           |
// | For the full copyright and license information, please view the LICENSE   |
// | file that was distributed with this source code.                          |
// +---------------------------------------------------------------------------+

// +---------------------------------------------------------------------------+
// | This file is part of the Mootools Addon project.                          |
// | Copyright (C) Lemieux BŽdard Communication                                |
// |                                                                           |
// | For the full copyright and license information, please view the LICENSE   |
// | file that was distributed with this source code.                          |
// +---------------------------------------------------------------------------+

/**
 * Moo.Selector is a mootools implementation of the original event selector.
 * This been ported to mootools by rossco.
 * @package Selector
 * @author Ross Lawley
 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
 * @copyright Ross Lawley
 * @since 2.0.0
 * @version 2.0.0
 */
Moo.Selector = new Class
({
	/**
	 * Apply the selector rules when the dom ready event is called. This event
	 * is similar to the load event except it does not wait until the image are
	 * fully loaded. This is much faster.
	 * @param object The rules.
	 * @return object This class.
	 * @author Ross Lawley
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */
	start : function(rules) {
		window.addEvent('domready', function() {
			this.assign(rules);
		}.bind(this));
		return this;
	},

	/**
	 * Assign the selector rules when the dom ready event is called. This event
	 * is similar to the load event except it does not wait until the image are
	 * fully loaded. This is much faster.
	 * @param object The rules.
	 * @return object This class.
	 * @author Ross Lawley	 
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */
	assign : function(rules) {
		for (var key in rules) {
			var rule = rules[key];
			key.clean().split(',').each(function(selector) {
				var pair = selector.split('::');
				$$(pair[0]).each(function(elem) {
					if (pair.length == 1) return rule(elem);
					// attach the event on the selector
					elem.addEvent(pair[1], rule.pass(elem));
				});
			});
		}
	}
});
// +---------------------------------------------------------------------------+
// | This file is part of the Mootools Addon project.                          |
// | Copyright (C) Lemieux BÃ©dard Communication                                |
// |                                                                           |
// | For the full copyright and license information, please view the LICENSE   |
// | file that was distributed with this source code.                          |
// +---------------------------------------------------------------------------+

Moo.UI = {};
Moo.UI.Form = {};
Moo.UI.Navigation = {};
Moo.UI.Editor = {};
Moo.UI.Window = {};// +---------------------------------------------------------------------------+
// | This file is part of the Mootools Addon project.                          |
// | Copyright (C) Lemieux BÃ©dard Communication                                |
// |                                                                           |
// | For the full copyright and license information, please view the LICENSE   |
// | file that was distributed with this source code.                          |
// +---------------------------------------------------------------------------+

/**
 * Moo.UI.Form
 * This class handles a form with ajax only. onError and onSuccess events are
 * only available when the json request is selected because it's just not possible
 * to really detect errors otherwise.
 * @package UI.Form
 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
 * @copyright Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
 * @since 2.0.0
 * @version 2.0.0
 */
Moo.UI.Form = new Class
({
	/**
	 * @implements Events, Options.
	 */
	Implements: [Events, Options, Class.ToElement],
	
	/**
	 * @var object The options.
	 */
	options: {
		onSubmit: $empty,
		onComplete: $empty,
		onError: $empty,
		onSuccess: $empty,
		onRedirect: $empty,
		request: 'json',
		errorHandling: true,
		errorHandler: null,
		spinner: true,
		spinnerPosition: {},
		spinnerInjectPosition: 'after',
		animations: {
			error: null
		}
	},
	
	/**
	 * @var object The form element.
	 */
	element: null,
	
	/**
	 * @var object The request handler.
	 */
	request: null,
	
	/**
	 * @var object Whether or not the form is saving.
	 */
	saving: false,
	
	/**
	 * @var object The spinner.
	 */
	savingSpinner: null,
	
	/**
	 * Initialize the form.
	 * @param object The form.
	 * @param object The options.
	 * @return object A reference to this object.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */
	initialize: function(element, options) {
		this.element = $(element);
		this.setOptions(options);
		var requestHandlerOptions = {
			'onRequest': this.onFormRequest.bind(this),
			'onComplete': this.onFormComplete.bind(this)
		};
		switch (this.options.request) {
			case 'json': this.request = new Moo.Request.Form.JSON(this.element, requestHandlerOptions); break;
			case 'html': this.request = new Moo.Request.Form.HTML(this.element, requestHandlerOptions); break;
		}
		// look whether we have to use a custom error handling 
		// class instead of the default one		
		if (this.options.errorHandling) {
			this.options.errorHandler = this.options.errorHandler
				? this.options.errorHandler
				: new Moo.UI.Form.Errors(this, {
					animations: {
						error: this.options.animations.error
					}
				});
		}
		return this;
	},

	/**
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */
	submit: function() { 
		this.request.submit();
	},

	/**
	 * Reset a form.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */
	reset: function() {
		this.element.reset();
	},

	/**
	 * Return a field from the form using the name.
	 * @param string The field name.
	 * @return object The field.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */
	getFieldByName: function(name) {
		return this.element.getElement('*[name="' + name + '"]');
	},
	
	/**
	 * Return a field from the form using the id.
	 * @param string The field id.
	 * @return object The field.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */	
	getFieldById: function(id) {
		return this.element.getElement('*[id="' + id + '"]');
	},

	/**
	 * @return object A reference to this object.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */	
	onFormRequest: function(sender) {
		this.saving = true;
		this.showSavingSpinner();
		this.fireEvent('request', [sender]);
	},
	
	/**
	 * @return object A reference to this object.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */	
	onFormComplete: function(response, sender) {
		this.saving = false;
		this.hideSavingSpinner();
		this.fireEvent('complete', [response, sender]);
		// it's only possible to handle errors when the request is a json type
		// otherwise there is no perfect way to know if an error was triggered
		if (this.options.request == 'json') {
			switch (response.status) {
				// handles a simple success event where the data is
				// retransmited to the success event
				case 'success': 
					if (this.options.errorHandling) this.options.errorHandler.removeErrors();
					this.fireEvent('success', [response.message, response.code, sender, response]);				
					break;
				// handle the error by possibly managing the error display and 
				// transmiting the error data to the error event 
				case 'error':
					if (this.options.errorHandling) this.options.errorHandler.handleErrors(response.message);
					this.fireEvent('error', [response.message, response.code, sender, response]);
					break;
				// handle a redirect response by simply changing the 
				// window location address
				case 'redirect':
					if (this.options.errorHandling) this.options.errorHandler.removeErrors();
					window.location = response.message;
					break;
				// handle an unknown status. In this case we simply trigger a new event
				// based on the name of the status and fire it
				default:
					this.fireEvent(response.status, [response.message, response.code, response.status, response]);
					break;
			}
		}
	},
	
	/**
	 * Show the spinner element on the selected item.
	 * @param object The item.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */	
	showSavingSpinner: function() {
		if (this.options.spinner) {
			var button = this.element.getElement('input[type=submit]');
			if (this.savingSpinner) {
				this.savingSpinner.dispose();
				this.savingSpinner = null;
			}
			this.savingSpinner = new Moo.UI.Spinner(button, this.options.spinnerInjectPosition);
			this.savingSpinner.show();	
		}
	},
	
	/**
	 * Hide the current spinner.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */	
	hideSavingSpinner: function() {
		if (this.options.spinner) {
			if (this.savingSpinner) this.savingSpinner.hide();
		}	
	},
	
	/**
	 * Return the element binded to this object.
	 * @return object The element  binded to this object
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */	
	toElement: function() {
		return this.element;
	}
});
// +---------------------------------------------------------------------------+
// | This file is part of the Mootools Addon project.                          |
// | Copyright (C) Lemieux BÃ©dard Communication                                |
// |                                                                           |
// | For the full copyright and license information, please view the LICENSE   |
// | file that was distributed with this source code.                          |
// +---------------------------------------------------------------------------+

/**
 * Moo.UI.Form.Errors
 * Handle the displays of errors.
 * @package UI.Form
 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
 * @copyright Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
 * @since 2.0.0
 * @version 2.0.0
 */
Moo.UI.Form.Errors = new Class
({
	/**
	 * @implements Events, Options
	 */
	Implements: [Events, Options, Moo.Animable],
	
	/**
	 * @var object The options.
	 */
	 options: {
	 	injectInputError: 'after',
	 	injectFormError: 'top',
	 	animations: {
	 		error: null
	 	}
	 },
	
	/**
	 * @var object The form object which handles the form element.
	 */
	form: null,
	
	/**
	 * @var array The errors.
	 */
	errors: null,
	
	/**
	 * Initialize the form error handler.
	 * @param object The form.
	 * @param object The options.
	 * @return object A reference to this object.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */
	initialize: function(form, options) {
		this.setOptions(options);
		this.form = form;
		this.errors = [];
	},
				
	/**
	 * Handle the errors when they are received. This will clear the current
	 * errors and inject the new errors.
	 * @param array The errors.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0	 
	 */
	handleErrors: function(incidents) {
		this.removeErrors();
		incidents.each(function(incident) {
			var error = incident.error;
			var field = incident.field[0];
			var input = this.form.getFieldByName(field);
			this.addError(error, input);
		}.bind(this));
	},

	/**
	 * Remove all the errors from the form.
	 * @param array The errors.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0	 
	 */		
	removeErrors: function() {
		for (var i = 0; i < this.errors.length; i++) {
			this.errors[i].dispose();
			this.errors[i] = null;	
		}
		this.errors = this.errors.clean();
	},
	
	/**
	 * Handle the errors when they are received.
	 * @param array The errors.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0	 
	 */	
	addError: function(message, input) {
		if (input)	this.addErrorToInput(message, input);
		else 		this.addErrorToForm(message);
	},
	
	/**
	 * Add an error next to an input.
	 * @param object The error message.
	 * @param object The associated input.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0	 
	 */		
	addErrorToInput: function(message, input) {
		var div1 = new Element('div', {'class': 'errors'});
		var div2 = new Element('div', {'class': 'error', 'html': message});
		div1.grab(div2);
		div1.hide();
		div1.inject(input, this.options.injectInputError);
		this.errors.push(div1);
		this.animate(div1, this.options.animations.error, 
			function() {
				div1.show();
			}
		);
	},
	
	/**
	 * Add an error inside the form.
	 * @param object The error message.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0	 
	 */	
	addErrorToForm: function(message) {
		var div1 = this.form.element.getElement('div.errors');
		if (div1 == null) {
			var div1 = new Element('div', {'class': 'errors'});
			var div2 = new Element('div', {'class': 'error', 'html': message});
			div1.grab(div2);
			div1.hide();
			div1.inject(this.form.element, this.options.injectFormError);			
			this.errors.push(div1);
			this.animate(div1, this.options.animations.error, 
				function() {
					div1.show();
				}
			);
		} else {
			var div2 = new Element('div', {'class': 'error', 'html': message});
			div1.grab(div2);
			div2.hide();
			this.animate(div2, this.options.animations.error, 
				function() {
					div2.show();
				}
			);			
		}
	},
	
	/**
	 * Find whether or not an input has an error displayed.
	 * @param string The error message.
	 * @param object The input element.
	 * @return bool True if the input has the error.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0	 
	 */		
	hasError: function(message, input) {
		var element = null;
		if (element == null) element = input.getNext('div.errors div.error:contains("' + error + '")');
		if (element == null) element = input.getPrevious('div.errors div.error:contains("' + error + '")');
		return element == null;
	}
});// +---------------------------------------------------------------------------+
// | This file is part of the Mootools Addon project.                          |
// | Copyright (C) Lemieux BÃ©dard Communication                                |
// |                                                                           |
// | For the full copyright and license information, please view the LICENSE   |
// | file that was distributed with this source code.                          |
// +---------------------------------------------------------------------------+

/**
 * Moo.UI.FormActor
 * An actor is a class that will basically activate itself upon an element using
 * the applyOn method of the element.
 * @subpackage UI.Form
 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
 * @copyright Lemieux Bedard Communication (francois.patry@lemieuxbedard.com)
 * @since 2.0.0
 * @version 2.0.0
 */
Moo.UI.FormActor = new Class
({
	/**
	 * @extends Moo.Actor
	 */
	Extends: Moo.Actor,
		
	/**
	 * This method will create and return the object which will act as the
	 * performer of the actor. All arguments will be passed to this method
	 * except for the element.
	 * @param object The arguments.
	 * @return object The performer object
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */
	perform: function(options) {
		return new Moo.UI.Form(this.element, options);
	}		
});
// +---------------------------------------------------------------------------+
// | This file is part of the Mootools Addon project.                          |
// | Copyright (C) Lemieux BÃ©dard Communication                                |
// |                                                                           |
// | For the full copyright and license information, please view the LICENSE   |
// | file that was distributed with this source code.                          |
// +---------------------------------------------------------------------------+

/**
 * Moo.UI.Form.Steps
 * Display the form using a series of step.
 * @package UI.Form
 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
 * @copyright Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
 * @since 2.0.0
 * @version 2.0.0
 */
Moo.UI.Form.Steps = new Class
({
	/**
	 * @implements Events, Options, Moo.Animable
	 */
	Implements: [Events, Options, Moo.Animable],
	
	/**
	 * @var array The steps.
	 */
	steps: null,

	/**
	 * @var int The current step.
	 */
	step: -1,
	
	/**
	 * @var object The actual form.
	 */
	form: null,
	
	/**
	 * @var object The next button.
	 */
	nextButton: null,
	
	/**
	 * @var string The next button label.
	 */
	nextButtonLabel: '',
	
	/**
	 * @var object The previous button.
	 */
	prevButton: null,
	
	/**
	 * @var string The previous button.
	 */	
	prevButtonLabel: '',
	
	/**
	 * @var object The submit.
	 */
	sendButton: null,
	
	/**
	 * @var object The options.
	 */
	options: {
		onChangeStep: $empty,
		onFormSubmit: $empty,
		onFormSuccess: $empty,
		onFormError: $empty,
		animations: {
			showStep: null,
			hideStep: null,
			showSendButton: null,
			hideSendButton: null,
			disableButton: null,
			enableButton: null
		},
		remoteForm: true,
		nextButtonSelector: '.next-step',
		prevButtonSelector: '.prev-step',
		sendButtonSelector: 'input[type=submit]',
		errorSelector: '.error'
	},
	
	/**
	 * Initialize the class.
	 * @param string The selector for the steps.
	 * @param object The options.
	 * @return object A reference to this object.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */	 
	initialize: function(steps, form, options) {	
		this.setOptions(options);		
		this.form = document.getElement(form);
		this.step = 0;
		this.steps = document.getElements(steps);		
		this.steps.hide();
		this.initializeForm();
		this.initializeButtons();
		this.gotoLogicalStep();
		return this;
	},
	
	/**
	 * Initialize the form.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */	
	initializeForm: function() {
		if (this.options.remoteForm) {
			this.form = new Moo.UI.Form(this.form, {
				onSubmit:  this.onFormSubmit.bind(this),
				onSuccess: this.onFormSuccess.bind(this),
				onError:   this.onFormError.bind(this)
			});
		}
	},	
	
	/**
	 * Initialize the buttons.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */
	initializeButtons: function() {
		this.nextButton = document.getElement(this.options.nextButtonSelector);
		this.prevButton = document.getElement(this.options.prevButtonSelector);
		this.sendButton = document.getElement(this.options.sendButtonSelector);
		this.nextButtonLabel = this.nextButton.value;
		this.prevButtonLabel = this.prevButton.value;
		this.nextButton.addEvent('click', this.onNextButtonClick.bind(this));
		this.prevButton.addEvent('click', this.onPrevButtonClick.bind(this));
		this.nextButton.disabled = false;
		this.prevButton.disabled = true;
		this.sendButton.hide();
	},
	
	/**
	 * Move to the given step.
	 * @param int The step to move to.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */
	gotoStep: function(step) {
		if (this.hasStep(step)) {
			if (this.isMinStep(step)) {
				this.setButtonEnabled(this.nextButton, true);
				this.setButtonEnabled(this.prevButton, false);
			} else if (this.isMaxStep(step)) {
				this.setButtonEnabled(this.nextButton, false);
				this.setButtonEnabled(this.prevButton, true);
				this.sendButton.show();
			} else {
				if (this.isMinStep(step - 1)) this.setButtonEnabled(this.prevButton, true);
				if (this.isMaxStep(step + 1)) this.setButtonEnabled(this.nextButton, true);
				this.sendButton.hide();
			}
			this.hideStep(this.step);
			this.step = step;
			this.showStep(this.step);
			this.fireEvent('changeStep', [step]);
		}
	},
	
	/**
	 * Look whether or not there is an error in a step. In that case we simply 
	 * show that step otherwise we show the first step.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */	
	gotoLogicalStep: function() {
		for (var i = 0; i < this.steps.length; i++) {
			if (this.steps[i].getElement(this.options.errorSelector)) {
				this.gotoStep(i);
				return;				
			}		
		}
		this.gotoStep(0);
	},	
	
	/**
	 * Move to the next slide.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */
	nextStep: function() {
		this.gotoStep(this.step + 1);
	},
	
	/**
	 * Move back to the previous slide.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */	
	prevStep: function() {
		this.gotoStep(this.step - 1);
	},
	
	/**
	 * Indicate whether or not a step exists.
	 * @param int The step.
	 * @return bool True if the step exists.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */
	hasStep: function(step) {
		return this.steps[step] != undefined && this.steps[step] != null;
	},
	
	/**
	 * Indicate whether or not the given step is the max step.
	 * @param int The step.
	 * @return bool True if the step is the max one.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */	
	isMaxStep: function(step) {
		return step == this.steps.length - 1;
	},
	
	/**
	 * Indicate whether or not the given step is the minimum step.
	 * @param int The step.
	 * @return bool True if the step is the min one.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */		
	isMinStep: function(step) {
		return step == 0;
	},
			
	/**
	 * Show a given step in the form.
	 * @param int The step.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */	
	showStep: function(step) {
		this.animate(this.steps[step], this.options.animations.onShowStep, function() { this.steps[step].show(); }.bind(this));
	},
	
	/**
	 * Hide a given step in the form.
	 * @param int The step.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */	
	hideStep: function(step) {
		this.animate(this.steps[step], this.options.animations.onHideStep, function() { this.steps[step].hide(); }.bind(this));
	},
	
	/**
	 * Enable or disable a button.
	 * @param object The button.
	 * @param bool Enable or disable the button.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */	
	setButtonEnabled: function(button, enabled) {
		if (enabled == false) this.animate(button, this.options.animations.onDisableButton, function() { button.disabled = true; }.bind(this));
		if (enabled == true) this.animate(button, this.options.animations.onEnableButton, function() { button.disabled = false; }.bind(this));
	},

	/**
	 * Return the form used by the step makes.
	 * @return object The form.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */	
	getForm: function() {
		return this.form;
	},
		
	/**
	 * This event happens when the next step button is clicked.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */	
	onNextButtonClick: function() {
		this.nextStep();
	},
	
	/**
	 * This event happens when the previous step button is clicked.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */		
	onPrevButtonClick: function() {
		this.prevStep();
	},
	
	/**
	 * Some form events.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */		
	onFormSubmit: function() {},
	onFormSuccess: function() {},
	onFormError: function() {
		this.gotoLogicalStep();
	}
});// +---------------------------------------------------------------------------+
// | This file is part of the Mootools Addon project.                          |
// | Copyright (C) Lemieux BÅ½dard Communication                                |
// |                                                                           |
// | For the full copyright and license information, please view the LICENSE   |
// | file that was distributed with this source code.                          |
// +---------------------------------------------------------------------------+

/**
 * Moo.UI.Spinner
 * Display a spinning animation relative to an element.
 * @package UI
 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
 * @copyright Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
 * @since 2.0.0
 * @version 2.0.0
 */
Moo.UI.Spinner = new Class
({
	/**
	 * @implements Events, Options.
	 */
	Implements: [Events, Options, Moo.Animable],
	
	/**
	 * @var object The options.
	 */
	options: {
		display: 'inline-block',
		css: {
			spinner: 'spinner',
			loading: 'spinner-loading'
		},
		animations: {
			show: null,
			hide: null,
			dispose: null
		}
	},
	
	/**
	 * @var object The element.
	 */
	element: null,
	
	/**
	 * @return object A reference to this object.	 
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */	
	initialize: function(relative, where, options) {
		this.setOptions(options);
		this.create();
		this.inject(relative, where);
		this.hide();
	},
	
	/**
	 * Create the spinner element.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */		
	create: function() {
		this.element = new Element('div').addClass(this.options.css.spinner);
	},

	/**
	 * Inject the element.
	 * @param object The element to inject.
	 * @param string Where to inject the element.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */			
	inject: function(element, where) {
		this.element.inject(element, where);
	},
	
	/**
	 * Show the spinner.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */		
	show: function() {
		this.animate(this.element, this.options.animations.show,
			function() {
				this.element.show(this.options.display);	
			}.bind(this)
		);
	},
	
	/**
	 * Hide the spinner.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */		
	hide: function() {
		this.animate(this.element, this.options.animations.hide,
			function() {
				this.element.hide();
			}.bind(this)
		);
	},
	
	/**
	 * @return object A reference to this object.	 
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */			
	dispose: function() {
		this.animate(this.element, this.options.animations.hide,
			function() {
				this.element.dispose();
			}.bind(this)
		);
	}
});// +---------------------------------------------------------------------------+
// | This file is part of the Mootools Addon project.                          |
// | Copyright (C) Lemieux BÃ©dard Communication                                |
// |                                                                           |
// | For the full copyright and license information, please view the LICENSE   |
// | file that was distributed with this source code.                          |
// +---------------------------------------------------------------------------+

/**
 * Moo.UI.Dock
 * This class represents a dock where menus can be attached at runtime. To use 
 * this dock you must have the html on your site since the base look and feel 
 * can change. However, you must have an element with the class items.
 * The html should look like this.
 *	<div id="dock">
 *		<div class="wrap">
 *			<div class="title">Lemieux BÃ©dard</div>
 *			<div class="items">
 *				<ul>
 *					<li class="item">
 *						<div class="item-title">Interface</div>
 *						<div class="item-child">
 *							<ol>
 *								<li>Item 1</li>
 *								<li>Item 2</li>
 *								<li>Item 3</li>	
 *							</ol>
 *						</div>
 *					</li>
 *					<li class="item">
 *						<div class="item-title">PrÃ©fÃ©rences</div>
 *						<div class="item-child">
 *							<ol>
 *								<li>Item 1</li>
 *								<li>Item 2</li>
 *								<li>Item 3</li>	
 *							</ol>
 *						</div>
 *					</li>
 *				</ul>
 *			</div>
 *		</div>
 *	</div>
 * @package UI
 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
 * @copyright Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
 * @since 2.0.0
 * @version 2.0.0
 */
Moo.UI.Dock = new Class
({		
	/**
	 * @implements Events Options The basic element methods.
	 */
	Implements: [Events, Options, Moo.Animable, Class.ToElement],

	/**
	 * @var object The root element of the dock
	 */
	element: null,
	
	/**
	 * @var object The child elements
	 */
	elements: {
		title: null,
		items: null
	},
	
	/**
	 * @var int The height of the dock.
	 */
	height: 0,
	
	/**
	 * @var bool Whether or not the dock is expanding.
	 */
	resizing: false,
	
	/**
	 * @var object The dock item that's currently expanded.
	 */
	expandedDockItem: null,
	
	/**
	 * @var object The options.
	 */	
	options: {
		onMouseEnter: $empty,
		onMouseLeave: $empty
	},
		
	/**
	 * Initialize the dock.
	 * @param object The root dock element.
	 * @param object The options.
	 * @return object A reference to this object.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */
	initialize: function(dock, options) {
		this.setOptions(options);
		this.element = $(dock);
		this.elements.title = this.element.getElement('.title');
		this.elements.items = this.element.getElement('.items');
		this.height = this.elements.items.getSize().y;
		this.element.addEvent('mouseenter', function() { this.fireEvent('mouseEnter'); }.bind(this));
		this.element.addEvent('mouseleave', function() { this.fireEvent('mouseLeave'); }.bind(this));
		this.initializeDockItems();
		return this;
	},

	/**
	 * Initialize all the dock items.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */					
	initializeDockItems: function() {
		var items = this.element.getElements('.items > ul > li');
		if (items) {
			items.each(function(item) {
				var itemChild = item.getElement('.item-child');
				if (itemChild) itemChild.fade('hide');
				item.addEvent('click', function(e) {
					this.toggleDockItem(itemChild);
				}.bind(this));
			}, this);
		}
	},
	
	/**
	 * Create a new doc item. 
	 * @param string The dock item id.
	 * @param string The dock item title.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */	
	createDockItem: function(id, title) {
		var itemTitle = new Element('div').addClass('item-title');
		var itemChild = new Element('div').addClass('item-child');
		var dockItem = new Element('li').addClass('item');
		dockItem.setId(id);
		dockItem.getChildCount = function() { return this.retrieve('item-child-count', 0); };
		dockItem.incrementChildCount = function() { this.store('item-child-count', this.retrieve('item-child-count', 0) + 1); };
		dockItem.decrementChildCount = function() { this.store('item-child-count', this.retrieve('item-child-count', 0) - 1); };
		dockItem.grab(itemTitle);
		dockItem.grab(itemChild);
		var ul = this.elements.items.getElement('ul');
		if (ul == null) {
			ul = new Element('ul');
			ul.inject(this.elements.items);
		}
		ul.grab(dockItem);		
		itemChild.grab(new Element('ol'));
	},
	
	/**
	 * Remove a given dock item from the list. 
	 * @param string The dock item id.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */		
	removeDockItem: function(id) {
		var dockItem = $(id);
		if (dockItem) {
			dockItem.dispose();
		}
	},

	/**
	 * Show a dock item.
	 * @param string The dock item id.
	 * @param object The possible animation.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */	
	showDockItem: function(id, animation) {
		var dockItem = $(id);
		if (dockItem) {
			this.animate(dockItem, animation, function() {
				// the default behavior
				dockItem.show();
			});			
		}
	},

	/**
	 * hide a dock item.
	 * @param string The dock item id.
	 * @param object The possible animation.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */	
	hideDockItem: function(id, animation) {
		var dockItem = $(id);
		if (dockItem) {
			this.animate(dockItem, animation, function() {
				dockItem.hide();
			});	
		}
	},

	/**
	 * Either show or hide a sub menu.
	 * @param object The parent of the submenu to show or hide.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */					
	toggleDockItem: function(item) {
		if (this.resizing == false) {
			var expanded = item.retrieve('expanded', false);			
			if (expanded == false) {
				if (this.expandedDockItem) {
					// hide the previously opened sub menu. This will also
					// reset the expandedDockItem variable to a null value
					this.shortenDockItem(this.expandedDockItem);
				} 
				this.expandDockItem(item);
				this.expandDock(item);
			} else {	
				this.shortenDockItem(item);
				this.shortenDock(item);
			}
		}
	},
	
	/**
	 * Open a sub menu.
	 * @param object The sub menu to show.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */	
	expandDockItem: function(item) {
		item.store('expanded', true);
		item.fade(1);
		this.expandedDockItem = item;
	},	

	/**
	 * Hide a sub menu.
	 * @param object The sub menu to hide.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */				
	shortenDockItem: function(item) {
		item.store('expanded', false);
		item.fade(0);
		this.expandedDockItem = null;
	},

	/**
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */			
	expandDock: function(sm) {
		this.resizing = true;
		var curr = this.footerHeight;
		var next = this.expandedDockItem.getSize().y + 40; // <-- This is an unknown constant
		new Fx.Tween(this.elements.items, {duration:'short'}).start('height', curr, next).chain(function() {
			this.height = next;
			this.resizing = false;
		}.bind(this));	
	},
	
	/**
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */			
	shortenDock: function(sm) {
		this.resizing = true;
		var curr = this.footerHeight;
		var next = 35; // <-- Once again an unknown constant
		new Fx.Tween(this.elements.items, {duration:'short'}).start('height', curr, next).chain(function() {
			this.height = next;
			this.resizing = false;
		}.bind(this));
	},
	
	toElement: function() {
		return this.element;
	}

});	// +---------------------------------------------------------------------------+
// | This file is part of the Mootools Addon project.                          |
// | Copyright (C) Lemieux BÅ½dard Communication                                |
// |                                                                           |
// | For the full copyright and license information, please view the LICENSE   |
// | file that was distributed with this source code.                          |
// +---------------------------------------------------------------------------+

/**
 * Moo.UI.Progress
 * Displays and handles a progress bar.
 * @package UI
 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
 * @copyright Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
 * @since 2.0.0
 * @version 2.0.0
 */
Moo.UI.Progress = new Class
({	
	/**
	 * @implements Events, Options.
	 */
	Implements: [Events, Options],

	/**
	 * @var object The options.
	 */
	options: {
		onBegin: $empty,
		onFinish: $empty,
		duration: 10
	},
	
	/**
	 * @var object The root element.
	 */
	element: null,

	/**
	 * @var object The child elements.
	 */		
	elements: {
		line: null,
		text: null
	},
		
	/**
	 * @var int The value.
	 */
	value: 0,
	
	/**
	 * Initialize the progress element.
	 * @param object The root element.
	 * @param object The options.
	 * @return object A reference to this object.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */
	initialize: function(element, options) {
		this.setOptions(options);
		this.element = $(element);
		this.create();
		return this;
	},
	
	/**
	 * @return object A reference to this object.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */
	create: function() {
		this.elements.line = new Element('div', {'class': 'line-indicator', style: 'width: 0%'});
		this.elements.text = new Element('div', {'class': 'text-indicator'});
		this.element.grab(this.elements.line);
		this.element.grab(this.elements.text);
	},

	/**
	 * Animate the progress bar to a certain location.
	 * @param int The location in percent.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */
	animate: function(to) {
		to = to.toInt();
		this.elements.text.set('text', to + '%');
		this.elements.line.set('morph', {unit: '%', duration: this.options.duration, link: 'cancel'});
		this.elements.line.morph({width: to});
	},
	
	/**
	 * Set the value and animate the bar to match this value.
	 * @param int The value.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */	
	setValue: function(value) {
		this.value = value;
		this.animate(value);
	},
	
	/**
	 * Return the value.
	 * @return int The value.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */		
	getValue: function(value) {
		return this.value;
	}
});// +---------------------------------------------------------------------------+
// | This file is part of the Mootools Addon project.                          |
// | Copyright (C) Lemieux BÅ½dard Communication                                |
// |                                                                           |
// | For the full copyright and license information, please view the LICENSE   |
// | file that was distributed with this source code.                          |
// +---------------------------------------------------------------------------+

Moo.UI.Notification = new Class
({
    /**
     * @implements Events, Options.
     */
    Implements: [Options, Events],

    /**
     *  Notification elements list.
     */
    elements: [],

    /**
     *  Used to properly work with the scroll relocation transition
     */
    scrollTimeOut: null,

    /**
     *  Options to configure the notification manager.
     *  @param String parent -> parent element where notifications are to be inserted (defaults to 'body' tag)
     *  @param Number height -> height of the notification DOM element (in pixels -defaults to 50-)
     *  @param Number width -> width of the notification DOM element (in pixels -defaults to 300-)
     *  @param Number visibleTime -> time the notification is displayed (in miliseconds -defaults to 5000-)
     *  @param String locationVType -> whether you want the notifications to be shown on the top or the bottom of the parent element (defaults to 'top')
     *  @param String locationHType -> whether you want the notification to be shown at the left or right of the parent element (defaults to 'right')
     *  @param Number locationVBase -> vertical base position for the notifications (in pixels -defaults to 10-)
     *  @param Number locationHBase -> horizontal base position for the notifications (in pixels -defaults to 10-)
     *  @param Number notificationsMargin -> margin between notifications (in pixels -defaults to 5-)
     *  @param Number opacityTransitionTime -> duration for notification opacity transition (in miliseconds -defaults to 750-)
     *  @param Number closeRelocationTransitionTime -> duration for notification relocation transition when one of them is close (in miliseconds -defaults to 750-)
     *  @param Number scrollRelocationTransitionTime -> duration for notification relocation transition when parent scroll is moved (in miliseconds -defaults to 250-)
     *	@param Number notificationOpacity -> opacity used when the notification is displayed (defaults to 0.95)
     *  @param Function onShow -> callback to be executed when the notification is displayed. The notification element is passed as a parameter.
     *  @param Function onClose -> callback to be executed when the notification id closed. The notification element is passed as a parameter.
     */
    options: {
        parent: '', // This value needs to be set into the initializer
        height: 'auto',
        width: 300,
        visibleTime: 5000, // notifications are visible for 5 seconds by default
        locationVType: 'top',
        locationHType: 'right',
        locationVBase: 10,
        locationHBase: 10,
        notificationsMargin: 5,
        opacityTransitionTime : 750,
        closeRelocationTransitionTime: 750,
        scrollRelocationTransitionTime: 500,
        notificationOpacity : 0.95 /*,
        onShow: $empty,
        onClose: $empty */
    },

    /**
     * Initilize instance.
     * @param Hash options -> (see code above)
     * @author PaquitoSoft (http://paquitosoft.com/)
     * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
     * @since 2.0.0     
     */
    initialize: function(options) {
        this.options.parent = $(document.body);
        if (options) {
            if (options.parent) options.parent = $(options.parent);
            this.setOptions(options);
        }
        var manager = this;
        // Track scroll in parent element
        this.options.parent.addEvent('scroll', function() {
            $clear(this.scrollTimeOut);
            this.scrollTimeOut = (function() { manager._relocateActiveNotifications(manager.TYPE_RELOCATE_SCROLL) }).delay(200);
        }, this);           
        window.addEvent('scroll', function() {
            $clear(manager.scrollTimeOut);
            manager.scrollTimeOut = (function() { manager._relocateActiveNotifications(manager.TYPE_RELOCATE_SCROLL) }).delay(200);
        });
        // Insert default element into array
        this.elements.push(
            this.createNotificationElement(this.options)
        );
    },

    /**
     *  Creates and initializes an element to show the notification
     * @author PaquitoSoft (http://paquitosoft.com/)
     * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
     * @since 2.0.0     
     */
    createNotificationElement: function() {
        var el = new Element('div', {
			'class': 'notification'
		});
        el.setStyle(this.options.locationVType, this.options.locationVBase);
        el.setStyle(this.options.locationHType, this.options.locationHBase);
        el.adopt(new Element('div', { 'class': 'title' }));
        el.adopt(new Element('div', { 'class': 'message' }));
        el.setStyle('width', this.options.width);
        el.setStyle('height', this.options.height);
        // Element default tween instance is used to handle opacity
        el.store('working', false);
        el.set('tween', {
            link: 'chain',
            duration: this.options.opacityTransitionTime
        });
        el.set('opacity', 0);
        // This tween instance is used to move the notification when another one is closed
        var fx1 = new Fx.Tween(el, {
            property: this.options.locationVType,
            link: 'chain',
            duration: this.options.closeRelocationTransitionTime
        });
        el.store('baseTween', fx1);
        // This tween instance is used to move the notification when scroll is moved
        var fx2 = new Fx.Tween(el, {
            property: this.options.locationVType,
            link: 'chain',
            duration: this.options.scrollRelocationTransitionTime
        });
        el.store('scrollTween', fx2);
        // Close the notification when the user click inside
        el.addEvent('click', function(event) {
            event.stop();
            this.close(el);
        }.bind(this));
        return el;
    },

    /**
     *  Function to actually show a notification.
     *  @param String title (required) -> Title for the notification
     *  @param String message (required) -> Message for the notification
     *  @param booleam sticky (optional) -> Whether the notification won't be removed until user interaction (defaults to false)
     *  @param int visibleTime (optional) -> Time for the notification to be displayed (in milliseconds). If this isn't provided, the global one will be used.
     *  @param int width (optional) -> Width fot the notification (in pixels). If this isn't provided, the global one will be used.
     *	@param String customClasses (optional) -> Custom classes you want to apply to this notification. (list of classes separated by a blank space)
     * @author PaquitoSoft (http://paquitosoft.com/)
     * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
     * @since 2.0.0     
     */
    show: function(options) {        
        var manager = this;
        // Get the base for the notification
        var nextBase = this._applyScrollPosition(this.options.locationVBase);        
        var el = this.elements.filter(function(el) {
            var w = el.retrieve('working');
            if (w) {        
                nextBase = el.getStyle(this.options.locationVType).toInt() + el.getSize().y + this.options.notificationsMargin;
            }
            return !w;
        }, this).getLast();
       // Create element if there is no available one
       if (!el) {
         el = this.createNotificationElement();
         this.elements.push(el);
       } 
       // Set base and 'working' flag
       el.setStyle(this.options.locationVType, nextBase);
       el.store('working', true);
       // Check if a custom width has been provided
       if (options.width) el.setStyle('width', options.width);
       // Set notification content
       if (options.title) {
       	el.getElement('div.title').set('html', options.title);
       }
       el.getElement('div.message').set('html', options.message);
	   // Add custom classes
	   if (options.customClasses) el.addClass(customClasses);
       // Once the notification is populated, we check to see if there is any link inside so we can
       // configure it in order not to close the notification when it's clicked
       el.getElements('a').addEvent('click', function(event) {           
            event.stopPropagation();
        });
       // Insert the element into the DOM
       this.options.parent.adopt(el);
       // This must be done after the element is inserted into DOM. Previously (on Lost!) the element does not have coordinates (obviously)
       this._checkSize(el);
       // Show the element with a lot of style
       el.get('tween').start('opacity', this.options.notificationOpacity).chain(function() {
	       	// Set close notification with options visibleTime delay
	       	if ((options.sticky) ? !options.sticky : true) {
	           (function() { manager.close(el); } ).delay((options.visibleTime) ? options.visibleTime : manager.options.visibleTime, manager);
	       	}
	       	// Fire callback
	       	manager.fireEvent('show', el);
       });
    },

    /**
     * Function to close the notification.
     * It also deals with moving other still visible notifications.
     * @param Element element -> element to be removed
     * @author PaquitoSoft (http://paquitosoft.com/)
     * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
     * @since 2.0.0     
     */
    close: function(element) {
        // Hide and reset notification. Destroy it when it's not the last one.
        var manager = this;
        var nots = manager.elements;
        element.get('tween').start('opacity', 0).chain(function() {             
            if (nots.length > 1) {
                nots.elements = nots.erase(element);
                element.destroy();
            }
            manager._resetNotificationElement(element);
            // If there are more notifications on screen, move them!
            manager._relocateActiveNotifications(manager.TYPE_RELOCATE_CLOSE);
            manager.fireEvent('close', element);
        });
    },

    /**
     * Function to relocate active notifications when needed
     * (notification closed or scroll moved).
     * @param int sourceEvent -> the event that cause the movement (see events at the bottom)
     *                      1.- notification closed
     *                      2.- scroll moved
     * @author PaquitoSoft (http://paquitosoft.com/)   
     * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
     * @since 2.0.0  
     */
    _relocateActiveNotifications: function(sourceEvent) {
        var base = this._applyScrollPosition(this.options.locationVBase);
        for (var index = 0; index < this.elements.length; index++) {
            var el = this.elements[index];
            if (el.retrieve('working')) {
                if (this.TYPE_RELOCATE_CLOSE == sourceEvent) {
                    el.retrieve('baseTween').start(base);
                } else {
                    el.retrieve('scrollTween').start(base);
                }
                base += el.getSize().y + this.options.notificationsMargin;
            }
        }
    },

    /**
     * Function to check if the size of the notification element has space enough
     * to show the message.
     * In case it hasn't, the element is resized.
     * @author PaquitoSoft (http://paquitosoft.com/)
     * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
     * @since 2.0.0     
     */
    _checkSize: function(element) {
      var notificationElHeight = element.getStyle('height').toInt();
      var titleHeight = element.getElement('div.title').getSize().y;
      var messageHeight = element.getElement('div.message').getSize().y;      
      if (messageHeight > (notificationElHeight - titleHeight)) {
          element.setStyle('height', notificationElHeight + (messageHeight - (notificationElHeight - titleHeight)));
      }
    },

    /**
     * Function used to reset the attributes of a used element to the original state.
     * It only resets the attributes that could be changed before.
     * @author PaquitoSoft (http://paquitosoft.com/)
     * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
     * @since 2.0.0
     */
    _resetNotificationElement: function(element) {
        element.store('working', false);
        element.setStyle(this.options.locationVType, this.options.locationVBase);
        element.setStyle('height', this.options.height);
        element.setStyle('width', this.options.width);        
    },

    /**
     * Helper function to apply scroll location to element base.
     * @author PaquitoSoft (http://paquitosoft.com/)
     * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
     * @since 2.0.0     
     */
    _applyScrollPosition: function(base) {
        if (this.options.locationVType == 'top') {
            base +=this.options.parent.getScroll().y;
        } else {
            base -=this.options.parent.getScroll().y;
        }
        return base;
    },

    /*
    * Constants for transitions
    * @author PaquitoSoft (http://paquitosoft.com/) 
    */
    TYPE_RELOCATE_CLOSE: 1,
    TYPE_RELOCATE_SCROLL: 2

});// +---------------------------------------------------------------------------+
// | This file is part of the Mootools Addon project.                          |
// | Copyright (C) Lemieux BÃ©dard Communication                                |
// |                                                                           |
// | For the full copyright and license information, please view the LICENSE   |
// | file that was distributed with this source code.                          |
// +---------------------------------------------------------------------------+

/**
 * Moo.UI.Panel
 * This class handles a panel.
 * @package UI
 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
 * @copyright Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
 * @since 2.0.0
 * @version 2.0.0
 */
Moo.UI.Panel = new Class
({
	/**
	 * @implements Events, Options, Moo.Animable.
	 */
	Implements: [Events, Options, Moo.Animable, Moo.Actor],
	
	/**
	 * @var object The element.
	 */
	element: null,
	
	/**
	 * @var array The tabs.
	 */
	tabs: null,

	/**
	 * @var array The panes.
	 */
	panes: null,	
	
	/**
	 * @var object The current tab.
	 */
	currentTab: null,

	/**
	 * @var object The current pane.
	 */
	currentPane: null,
	
	/**
	 * @var int The current index.
	 */
	currentIndex: null,
	
	/**
	 * @var object The options.
	 */
	options: {
		onCollapse: $empty,
		onChange: $empty,
		defaultTabId: null,
		defaultTabIndex: 0,
		collapsable: false,
		animations: { 
			collapse: null,
			show: null, 
			hide: null
		},
		css: { 
			collapsable: 'collapsable', 
			collapsed: 'collapsed', 
			current: 'cur'
		}
	},
	
	/**
	 * Initialize the panel.
	 * @param string The tabs selector.
	 * @param string The panes selector.
	 * @return object A reference to this object.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */
	initialize: function(panel, tabs, panes, options) {
		this.element = $(panel);
		this.setOptions(options);
		this.initializeTabs(tabs);
		this.initializePanes(panes);
		if (this.options.collapsable == false) {
			if (this.options.defaultTabId) { 
				// use an id for the default tab
				this.setCurrentTab(this.getTabById(this.options.defaultTabId)); 
				return;
			}
			if (this.options.defaultTabIndex >= 0) {
				// use an index for the default tab
				this.setCurrentTab(this.getTabByIndex(this.options.defaultTabIndex));
				return;
			}
		} else {
			this.element.addClass(this.options.css.collapsable);
			this.element.addClass(this.options.css.collapsed);
		}
	},
	
	/**
	 * Initialize the panel tabs.
	 * @param mixed The tabs.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */	
	initializeTabs: function(tabs) {
		var type = $type(tabs);
		if (type == 'array') this.tabs = tabs;
		if (type == 'string') this.tabs = document.getElements(tabs);
		this.tabs.each(
			function(tab) {
				var a = tab.getElement('a');
				if (a) {
					a.disposeHrefProperty();
					a.addEvent('click', this.onTabClick.bind(this));
				}
			}, this
		);
	},
	
	/**
	 * Initialize the panel panes.
	 * @param string The panes
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */	
	initializePanes: function(panes) {
		var type = $type(panes);
		if (type == 'array') this.panes = panes;
		if (type == 'string') this.panes = document.getElements(panes);
	},
	
	/**
	 * Set the current tab.
	 * @param int The current tab index.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */	
	setCurrentTabByIndex: function(index) {
		this.setCurrentTab(this.getTabByIndex(index));
	},
	
	/**
	 * Set the current tab.
	 * @param object The tab.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */
	setCurrentTab: function(tab) {
		if (tab) {
			if (this.options.collapsable) {
				// the user clicked again on the current tab and the pane are
				// collapsable. In this case we hide the panes and remove the
				// special styles for the current tab
				if (this.currentTab === tab) {	
					var t = this.getTabByIndex(this.currentIndex);
					var p = this.getPaneByIndex(this.currentIndex);
					this.animate(p, this.options.animations.collapse, function() {
						if (t) t.removeClass(this.options.css.current);
						if (p) {
							p.removeClass(this.options.css.current);
							p.hide();
						}
					}.bind(this));
					this.currentTab = null;
					this.currentPane = null;
					this.currentIndex = null;
					this.element.addClass(this.options.css.collapsed);
					this.fireEvent('collapse', this.currentIndex);
					return;
				} else {
					this.element.removeClass(this.options.css.collapsed);
				}
			}
			if (this.currentTab !== tab) {
				// hide the current tab and pane
				var t = this.getTabByIndex(this.currentIndex);
				var p = this.getPaneByIndex(this.currentIndex);
				this.animate(p, this.options.animations.hide, function() {
					if (t) t.removeClass(this.options.css.current);
					if (p) {
						p.removeClass(this.options.css.current);
						p.hide();
					}
				}.bind(this));
				this.fireEvent('hide', [t, p, this.currentIndex]);
				// show the next tab
				this.currentIndex = this.getTabIndex(tab);			
				this.currentPane = this.getPaneByIndex(this.currentIndex);		
				this.currentTab = this.getTabByIndex(this.currentIndex);				
				this.animate(this.currentPane, this.options.animations.show, function() {
					this.currentTab.addClass(this.options.css.current);
					this.currentPane.addClass(this.options.css.current);
					this.currentPane.show();
				}.bind(this));
				this.fireEvent('change', [this.currentTab, this.currentPane, this.currentIndex]);
			}			
		}
	},

	/**
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */
	getCurrentTab: function() {
		return this.currentTab;
	},
	
	/**
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */	
	getCurrentPane: function() {
		return this.currentPane;
	},
	
	/**
	 * Return the index of a tab.
	 * @param object The tab.
	 * @return int The index.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */	
	getTabIndex: function(tab) {
		var index = null;
		this.tabs.each(function(t, i) { if (t === tab) index = i; });
		return index;
	},
	
	/**
	 * Return a tab using it's index.
	 * @param int The index.
	 * @return object The tab.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */
	getTabByIndex: function(index) {
		return this.tabs[index] != undefined ? this.tabs[index] : null;
	},
	
	/**
	 * Return a tab using it's id.
	 * @param int The id.
	 * @return object The tab.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */	
	getTabById: function(id) {
		var t = null;
		this.tabs.each(function(tab) { if (tab.get('id') == id ||Â tab.getProperty('id') == id) t = tab; });
		return t;
	},
	
	/**
	 * Return a pane using it's index.
	 * @param int The index.
	 * @return object The pane.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */	
	getPaneByIndex: function(index) {
		return this.panes[index] != undefined ? this.panes[index] : null;
	},
	
	/**
	 * Return a tab using it's id.
	 * @param int The id.
	 * @return object The tab.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */	
	getPaneById: function(id) {
		var p = null;
		this.panes.each(function(pane) { if (pane.get('id') == id ||Â pane.getProperty('id') == id) p = pane; });
		return p;
	},
	
	/**
	 * This event happens when the user clicks on a tab.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */	
	close: function() {
		if (this.options.collapsable) this.setCurrentTab(this.currentTab);
	},	
	
	/**
	 * This event happens when the user clicks on a tab.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */
	onTabClick: function(e) {
		this.setCurrentTab(e.target.getParent('li'));
	}	
});// +---------------------------------------------------------------------------+
// | This file is part of the Mootools Addon project.                          |
// | Copyright (C) Lemieux BÃ©dard Communication                                |
// |                                                                           |
// | For the full copyright and license information, please view the LICENSE   |
// | file that was distributed with this source code.                          |
// +---------------------------------------------------------------------------+

/**
 * Moo.UI.PanelActor
 * An actor is a class that will basically activate itself upon an element using
 * the applyOn method of the element.
 * @subpackage UI.Form
 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
 * @copyright Lemieux Bedard Communication (francois.patry@lemieuxbedard.com)
 * @since 2.0.0
 * @version 2.0.0
 */
Moo.UI.PanelActor = new Class
({
	/**
	 * @extends Moo.Actor
	 */
	Extends: Moo.Actor,
		
	/**
	 * This method will create and return the object which will act as the
	 * performer of the actor. All arguments will be passed to this method
	 * except for the element.
	 * @param object The arguments.
	 * @return object The performer object
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */
	perform: function(tabs, panes, options) {
		return new Moo.UI.Panel(this.element, tabs, panes, options);
	}	
});
// +---------------------------------------------------------------------------+
// | This file is part of the Mootools Addon project.                          |
// | Copyright (C) Lemieux BÃ©dard Communication                                |
// |                                                                           |
// | For the full copyright and license information, please view the LICENSE   |
// | file that was distributed with this source code.                          |
// +---------------------------------------------------------------------------+

/**
 * Moo.UI.Navigation.Tab
 * This class handles a navigation tab.
 * @package UI.Navigation
 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
 * @copyright Lemieux Bedard Communication (francois.patry@lemieuxbedard.com)
 * @since 1.0.0
 * @version 2.0.0
 */
Moo.UI.Navigation.Tab = new Class
({		
	/**
	 * @implements Events Options.
	 */
	Implements: [Events, Options],

	/**
	 * @var object The options.
	 */
	options: {
		onEmphasize: $empty,
		onNormalize: $empty,
		css: {
			current: 'cur',
			alternate: 'alt'	
		}
	},
	
	/**
	 * @var object The root tab eleement.
	 */
	element: null,

	/**
	 * This method is called when an object is given as an actor to an element.
	 * @param object The element to apply everything's on.
	 * @return object A reference to this object.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */
	initialize: function(element, options) {
		this.setOptions(options);
		this.element = $(element);
		this.element.addEvent('mouseenter', this.onEmphasize.bind(this));
		this.element.addEvent('mouseleave', this.onNormalize.bind(this));
		return this;
	},
	
	/**
	 * This event is called when the mouse moves over the tab for the first
	 * time. This method will basically highlight the tab.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */
	onEmphasize: function() {
		if (this.isCurrent() == false) {
			this.element.addClass(this.options.css.alternate);
			this.fireEvent('onEmphasize');
		}
	},

	/**
	 * This event is called when the mouse moves out the tab for the first
	 * time. This method will basically unhighlight the tab.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */
	onNormalize: function() {
		if (this.isCurrent() == false) {
			this.element.removeClass(this.options.css.alternate);
			this.fireEvent('onNormalize');
		}
	},

	/**
	 * Indicate wheter or not the tab is considered has the current tab. This
	 * is given by the class named current.
	 * @return bool True if the tab is a current one.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */
	isCurrent: function() {
		return this.element.hasClass(this.options.css.current);
	}	
});// +---------------------------------------------------------------------------+
// | This file is part of the Mootools Addon project.                          |
// | Copyright (C) Lemieux BÃ©dard Communication                                |
// |                                                                           |
// | For the full copyright and license information, please view the LICENSE   |
// | file that was distributed with this source code.                          |
// +---------------------------------------------------------------------------+

/**
 * Moo.UI.Navigation.TabActor
 * This actor will handle the look of the different navigation element.
 * @package UI.Navigation
 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
 * @copyright Lemieux Bedard Communication (francois.patry@lemieuxbedard.com)
 * @since 1.0.0
 * @version 2.0.0
 */
Moo.UI.Navigation.TabActor = new Class
({
	/**
	 * @extends Moo.Actor
	 */
	Extends: Moo.Actor,
	
	/**
	 * This method will create and return the object which will act as the
	 * performer of the actor. All arguments will be passed to this method
	 * except for the element.
	 * @param object The arguments.
	 * @return object The performer object
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */
	perform: function(options) {
		return new Moo.UI.Navigation.Tab(this.element, options);
	}	
});// +---------------------------------------------------------------------------+
// | This file is part of the Mootools Addon project.                          |
// | Copyright (C) Lemieux BÃ©dard Communication                                |
// |                                                                           |
// | For the full copyright and license information, please view the LICENSE   |
// | file that was distributed with this source code.                          |
// +---------------------------------------------------------------------------+

/**
 * Moo.UI.Window.
 * Display a simple window.
 * @package UI
 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
 * @copyright Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
 * @since 2.0.0
 * @version 2.0.0
 */
Moo.UI.Window = new Class
({
	/**
	 * @implements Events, Options, Moo.Animable.
	 */
	Implements: [Events, Options, Moo.Animable],

	/**
	 * @var object The window element.
	 */
	element: null,

	/**
	 * @var object The content.
	 */
	content: null,
	
	/**
	 * @var object The mask.
	 */
	mask: null,
	
	/**
	 * @var object The options.
	 */
	 options: {
	 	modal: false,
	 	closer: '.close',
		height: 'auto',
		width: 450,		
	 	position: { position: 'center' },
	 	animations: {
			onShow: new Moo.Animation.FadeIn(),
			onHide: new Moo.Animation.FadeOut()
		}
	 },
	 
	 /**
	  * Initialize the window.
	  * @param object The content.
	  * @param object The options.
	  * @return object A reference to this object.
	  * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	  * @since 1.0.0
	  */
	 initialize: function(content, options) {
	 	this.setOptions(options);
	 	this.content = content;
	 	return this;
	 },
	 
	 /**
	  * Create the window element and add the contents.
	  * @return void
	  * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	  * @since 1.0.0
	  */	 
	 create: function() {
	 	if (this.options.modal) {
	 		this.mask = new Mask();
	 		this.mask.hide();	
	 		this.mask.addEvent('click', this.hide.bind(this));
	 	}
		if (this.element == null) {
			this.element = new Element('div', {'class': 'window'});
			this.element.setStyle('width', this.options.width);
			this.element.setStyle('height', this.options.height);
			this.element.hide();
			if (this.options.closer) {
				var closer = this.element.getElement(this.options.closer);
				if (closer) {
					closer.addEvent('click', this.hide.bind(this));
				}
			}
		}
		switch ($type(this.content)) {
			case 'string'  : this.element.setHtml(this.content); break;
			case 'element' : this.element.grab(this.content); break;
			case 'object'  : this.element.grab(this.content); break;
		}

		document.body.grab(this.element);

		this.element.position(this.options.position);
	 },
	 
	 /**
	  * Display the window.
	  * @return void
	  * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	  * @since 1.0.0
	  */	 
	 show: function() {
	 	if (this.element == null) this.create();
		this.animate(this.element, this.options.animations.onShow, function() {
			if (this.options.modal) this.mask.show();
			this.element.show();
		}.bind(this));
	 },
	 
	 /**
	  * Hide and dispose the window.
	  * @return void
	  * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	  * @since 1.0.0
	  */
	 hide: function() {
		this.animate(this.element, this.options.animations.onHide, function() {
		 	this.element.dispose();
		 	this.element = null;
		 	if (this.options.modal) {
		 		this.mask.destroy();		
		 		this.mask = null;
		 	}
		}.bind(this));
	 } 	
});// +---------------------------------------------------------------------------+
// | This file is part of the Mootools Addon project.                          |
// | Copyright (C) Lemieux BÃ©dard Communication                                |
// |                                                                           |
// | For the full copyright and license information, please view the LICENSE   |
// | file that was distributed with this source code.                          |
// +---------------------------------------------------------------------------+// +---------------------------------------------------------------------------+
// | This file is part of the Mootools Addon project.                          |
// | Copyright (C) Lemieux BÅ½dard Communication                                |
// |                                                                           |
// | For the full copyright and license information, please view the LICENSE   |
// | file that was distributed with this source code.                          |
// +---------------------------------------------------------------------------+

(function() {

var blockEls = /^(H[1-6]|HR|P|DIV|ADDRESS|PRE|FORM|TABLE|LI|OL|UL|TD|CAPTION|BLOCKQUOTE|CENTER|DL|DT|DD)$/i;
var urlRegex = /^(https?|ftp|rmtp|mms):\/\/(([A-Z0-9][A-Z0-9_-]*)(\.[A-Z0-9][A-Z0-9_-]*)+)(:(\d+))?\/?/i;

/**
 * Moo.UI.Editor
 * Creates a WYSIWYG editor, for contentEditable-capable browsers.
 * @author Lim Chee Aun
 * @author Radovan Lozej
 * @author Ryan Mitchell
 * @author Olivier Refalo
 * @author T.J. Leahy
 * @copyright Authors
 * @copyright Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
 * @since 2.0.0
 * @version 2.0.0 
 */
this.Moo.UI.Editor = new Class
({
	/**
	 * @implements Events, Options.
	 */
	Implements: [Events, Options],

	/**
	 * @var object The options.
	 */
	options: {
		toolbar: true,
		cleanup: true,
		paragraphise: true,
		xhtml : true,
		semantics : true,
		actions: 'formatBlock | bold italic underline strikethrough | insertunorderedlist insertorderedlist indent outdent | undo redo | createlink unlink | urlimage | toggleview',
		handleSubmit: true,
		handleLabel: true,
		disabled: false,
		baseCSS: 'html{ height: 100%; cursor: text; } body{ background: #ffffff; font-family: sans-serif; }',
		moreCSS: '',
		externalCSS: '',
		html: '<!DOCTYPE html><html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><style>{BASECSS} {EXTRACSS}</style>{EXTERNALCSS}</head><body>{CONTENT}</body></html>',
		rootElement: 'p'
	},

	/**
	 * Initialize the progress element.
	 * @param object The root element.
	 * @param object The options.
	 * @return object A reference to this object.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */
	initialize: function(el, options) {
		this.setOptions(options);
		this.textarea = document.id(el);
		this.textarea.store('Moo.UI.Editor', this);
		this.actions = this.options.actions.clean().split(' ');
		this.keys = {};
		this.dialogs = {};
		this.actions.each(function(action) {
			var act = Moo.UI.Editor.Actions[action];
			if (!act) return;
			if (act.options) {
				var key = act.options.shortcut;
				if (key) this.keys[key] = action;
			}
			if (act.dialogs) {
				$each(act.dialogs, function(dialog, name) {
					dialog = dialog.attempt(this);
					dialog.name = action + ':' + name;
					if ($type(this.dialogs[action]) != 'object') this.dialogs[action] = {};
					this.dialogs[action][name] = dialog;
				}, this);
			}
			if (act.events) {
				$each(act.events, function(fn, event) {
					this.addEvent(event, fn);
				}, this);
			}
		}.bind(this));
		this.render();
	},
	
	/**
	 * @author Original authors.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */	
	toElement: function() {
		return this.textarea;
	},
	
	/**
	 * @author Original authors.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */		
	render: function() {
		var self = this;
		
		// dimensions
		var dimensions = this.textarea.getSize();
		
		// build the container
		this.container = new Element('div', {
			id: (this.textarea.id) ? this.textarea.id + '-editor-container' : null,
			'class': 'editor-container',
			styles: {
				width: dimensions.x
			}
		});

		// pverride all textarea styles
		this.textarea.addClass('editor-textarea').setStyle('height', dimensions.y);
		
		// build the iframe
		this.iframe = new IFrame({
			'class': 'editor-iframe',
			frameBorder: 0,
			src: 'javascript:""', // Workaround for HTTPs warning in IE6/7
			styles: {
				height: dimensions.y
			}
		});
		
		this.toolbar = new Moo.UI.Editor.Toolbar({
			onItemAction: function() {
				var args = $splat(arguments);
				var item = args[0];
				self.action(item.name, args);
			}
		});
		
		this.attach.delay(1, this);
		
		// update the event for textarea's corresponding labels
		if (this.options.handleLabel && this.textarea.id) $$('label[for="'+this.textarea.id+'"]').addEvent('click', function(e) {
			if (self.mode != 'iframe') return;
			e.preventDefault();
			self.focus();
		});

		// update & cleanup content before submit
		if (this.options.handleSubmit) {
			this.form = this.textarea.getParent('form');
			if (!this.form) return;
			this.form.addEvent('submit', function() {
				if (self.mode == 'iframe') self.saveContent();
			});
		}
		
		this.fireEvent('render', this);
	},

	/**
	 * @author Original authors.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */	
	attach: function() {
		var self = this;

		// assign view mode
		this.mode = 'iframe';
		
		// editor iframe state
		this.editorDisabled = false;

		// put textarea inside container
		this.container.wraps(this.textarea);

		this.textarea.setStyle('display', 'none');
		
		this.iframe.setStyle('display', '').inject(this.textarea, 'before');
		
		$each(this.dialogs, function(action, name) {
			$each(action, function(dialog) {
				document.id(dialog).inject(self.iframe, 'before');
				var range;
				dialog.addEvents({
					open: function() {
						range = self.selection.getRange();
						self.editorDisabled = true;
						self.toolbar.disable(name);
						self.fireEvent('dialogOpen', this);
					},
					close: function() {
						self.toolbar.enable();
						self.editorDisabled = false;
						self.focus();
						if (range) self.selection.setRange(range);
						self.fireEvent('dialogClose', this);
					}
				});
			});
		});

		// contentWindow and document references
		this.win = this.iframe.contentWindow;
		this.doc = this.win.document;

		// build the content of iframe
		var docHTML = this.options.html.substitute({
			BASECSS: this.options.baseCSS,
			EXTRACSS: this.options.moreCSS,
			EXTERNALCSS: (this.options.externalCSS) ? '<link rel="stylesheet" href="' + this.options.externalCSS + '">': '',
			CONTENT: this.cleanup(this.textarea.get('value'))
		});
		
		this.doc.open();
		this.doc.write(docHTML);
		this.doc.close();

		// turn on design mode
		// IE fired load event twice if designMode is set
		(Browser.Engine.trident) ? this.doc.body.contentEditable = true : this.doc.designMode = 'On';

		// mootoolize window, document and body
		if (!this.win.$family) new Window(this.win);
		if (!this.doc.$family) new Document(this.doc);
		document.id(this.doc.body);

		// bind all events
		this.doc.addEvents({
			mouseup: this.editorMouseUp.bind(this),
			mousedown: this.editorMouseDown.bind(this),
			mouseover: this.editorMouseOver.bind(this),
			mouseout: this.editorMouseOut.bind(this),
			mouseenter: this.editorMouseEnter.bind(this),
			mouseleave: this.editorMouseLeave.bind(this),
			contextmenu: this.editorContextMenu.bind(this),
			click: this.editorClick.bind(this),
			dbllick: this.editorDoubleClick.bind(this),
			keypress: this.editorKeyPress.bind(this),
			keyup: this.editorKeyUp.bind(this),
			keydown: this.editorKeyDown.bind(this),
			focus: this.editorFocus.bind(this),
			blur: this.editorBlur.bind(this)
		});
		
		this.win.addEvents({
			focus: this.editorFocus.bind(this),
			blur: this.editorBlur.bind(this)
		});
		
		['cut', 'copy', 'paste'].each(function(event) {
			self.doc.body.addListener(event, self['editor' + event.capitalize()].bind(self));
		});
		
		this.textarea.addEvent('keypress', this.textarea.retrieve('editor:textareaKeyListener', this.keyListener.bind(this)));
		
		// fix window focus event not firing on Firefox 2
		if (Browser.Engine.gecko && Browser.Engine.version == 18) this.doc.addEvent('focus', function() {
			self.win.fireEvent('focus').focus();
		});

		// styleWithCSS, not supported in IE and Opera
		if (!(/trident|presto/i).test(Browser.Engine.name)) {
			var styleCSS = function() {
				self.execute('styleWithCSS', false, false);
				self.doc.removeEvent('focus', styleCSS);
			};
			this.win.addEvent('focus', styleCSS);
		}

		if (this.options.toolbar) {
			document.id(this.toolbar).inject(this.container, 'top');
			this.toolbar.render(this.actions);
		}
		
		if (this.options.disabled) this.disable();

		this.selection = new Moo.UI.Editor.Selection(this.win);
		
		this.oldContent = this.getContent();
		
		this.fireEvent('attach', this);
		
		return this;
	},
	
	/**
	 * @author Original authors.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */		
	detach: function() {
		this.saveContent();
		this.textarea.setStyle('display', '').removeClass('editor-textarea').inject(this.container, 'before');
		this.textarea.removeEvent('keypress', this.textarea.retrieve('editor:textareaKeyListener'));
		this.container.dispose();
		this.fireEvent('detach', this);
		return this;
	},
	
	/**
	 * @author Original authors.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */		
	enable: function() {
		this.editorDisabled = false;
		this.toolbar.enable();
		return this;
	},
	
	/**
	 * @author Original authors.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */		
	disable: function() {
		this.editorDisabled = true;
		this.toolbar.disable();
		return this;
	},
	
	/**
	 * @author Original authors.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */		
	editorFocus: function(e) {
		this.oldContent = '';
		this.fireEvent('editorFocus', [e, this]);
	},
	
	/**
	 * @author Original authors.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */		
	editorBlur: function(e) {
		this.oldContent = this.saveContent().getContent();
		this.fireEvent('editorBlur', [e, this]);
	},
	
	/**
	 * @author Original authors.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */		
	editorMouseUp: function(e) {
		if (this.editorDisabled) {
			e.stop();
			return;
		}
		
		if (this.options.toolbar) this.checkStates();
		
		this.fireEvent('editorMouseUp', [e, this]);
	},
	
	/**
	 * @author Original authors.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */		
	editorMouseDown: function(e) {
		if (this.editorDisabled) {
			e.stop();
			return;
		}
		
		this.fireEvent('editorMouseDown', [e, this]);
	},
	
	/**
	 * @author Original authors.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */		
	editorMouseOver: function(e) {
		if (this.editorDisabled) {
			e.stop();
			return;
		}
		
		this.fireEvent('editorMouseOver', [e, this]);
	},
	
	/**
	 * @author Original authors.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */		
	editorMouseOut: function(e) {
		if (this.editorDisabled) {
			e.stop();
			return;
		}
		
		this.fireEvent('editorMouseOut', [e, this]);
	},
	
	/**
	 * @author Original authors.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */		
	editorMouseEnter: function(e) {
		if (this.editorDisabled) {
			e.stop();
			return;
		}
		
		if (this.oldContent && this.getContent() != this.oldContent) {
			this.focus();
			this.fireEvent('editorPaste', [e, this]);
		}
		
		this.fireEvent('editorMouseEnter', [e, this]);
	},
	
	/**
	 * @author Original authors.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */		
	editorMouseLeave: function(e) {
		if (this.editorDisabled) {
			e.stop();
			return;
		}
		
		this.fireEvent('editorMouseLeave', [e, this]);
	},
	
	/**
	 * @author Original authors.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */		
	editorContextMenu: function(e) {
		if (this.editorDisabled) {
			e.stop();
			return;
		}
		
		this.fireEvent('editorContextMenu', [e, this]);
	},
	
	/**
	 * @author Original authors.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */		
	editorClick: function(e) {
		// make images selectable and draggable in Safari
		if (Browser.Engine.webkit) {
			var el = e.target;
			if (el.get('tag') == 'img') {
				this.selection.selectNode(el);
			}
		}
		
		this.fireEvent('editorClick', [e, this]);
	},
	
	/**
	 * @author Original authors.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */		
	editorDoubleClick: function(e) {
		this.fireEvent('editorDoubleClick', [e, this]);
	},
	
	/**
	 * @author Original authors.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */		
	editorKeyPress: function(e) {
		if (this.editorDisabled) {
			e.stop();
			return;
		}
		
		this.keyListener(e);
		
		this.fireEvent('editorKeyPress', [e, this]);
	},
	
	/**
	 * @author Original authors.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */		
	editorKeyUp: function(e) {
		if (this.editorDisabled) {
			e.stop();
			return;
		}
		
		var c = e.code;
		// 33-36 = pageup, pagedown, end, home; 45 = insert
		if (this.options.toolbar && (/^enter|left|up|right|down|delete|backspace$/i.test(e.key) || (c >= 33 && c <= 36) || c == 45 || e.meta || e.control)) {
			if (Browser.Engines.trident4) { // Delay for less cpu usage when you are typing
				$clear(this.checkStatesDelay);
				this.checkStatesDelay = this.checkStates.delay(500, this);
			} else {
				this.checkStates();
			}
		}
		
		this.fireEvent('editorKeyUp', [e, this]);
	},
	
	/**
	 * @author Original authors.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */		
	editorKeyDown: function(e) {
		if (this.editorDisabled) {
			e.stop();
			return;
		}
		
		if (e.key == 'enter') {
			if (this.options.paragraphise) {
				if (e.shift && Browser.Engine.webkit) {
					var s = this.selection;
					var r = s.getRange();
					
					// insert BR element
					var br = this.doc.createElement('br');
					r.insertNode(br);
					
					// place caret after BR
					r.setStartAfter(br);
					r.setEndAfter(br);
					s.setRange(r);
					
					// could not place caret after BR then insert an nbsp entity and move the caret
					if (s.getSelection().focusNode == br.previousSibling) {
						var nbsp = this.doc.createTextNode('\u00a0');
						var p = br.parentNode;
						var ns = br.nextSibling;
						(ns) ? p.insertBefore(nbsp, ns) : p.appendChild(nbsp);
						s.selectNode(nbsp);
						s.collapse(1);
					}
					
					// scroll to new position, scrollIntoView can't be used due to bug: http://bugs.webkit.org/show_bug.cgi?id=16117
					this.win.scrollTo(0, Element.getOffsets(s.getRange().startContainer).y);
					
					e.preventDefault();
				} else if (Browser.Engine.gecko || Browser.Engine.webkit) {
					var node = this.selection.getNode();
					var isBlock = node.getParents().include(node).some(function(el) {
						return el.nodeName.test(blockEls);
					});
					if (!isBlock) this.execute('insertparagraph');
				}
			} else {
				if (Browser.Engine.trident) {
					var r = this.selection.getRange();
					var node = this.selection.getNode();
					if (r && node.get('tag') != 'li') {
						this.selection.insertContent('<br>');
						this.selection.collapse(false);
					}
					e.preventDefault();
				}
			}
		}
		
		if (Browser.Engine.presto) {
			var ctrlmeta = e.control || e.meta;
			if (ctrlmeta && e.key == 'x') {
				this.fireEvent('editorCut', [e, this]);
			} else if (ctrlmeta && e.key == 'c') {
				this.fireEvent('editorCopy', [e, this]);
			} else if ((ctrlmeta && e.key == 'v') || (e.shift && e.code == 45)) {
				this.fireEvent('editorPaste', [e, this]);
			}
		}
		
		this.fireEvent('editorKeyDown', [e, this]);
	},
	
	/**
	 * @author Original authors.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */		
	editorCut: function(e) {
		if (this.editorDisabled) {
			e.stop();
			return;
		}
		
		this.fireEvent('editorCut', [e, this]);
	},
	
	/**
	 * @author Original authors.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */		
	editorCopy: function(e) {
		if (this.editorDisabled) {
			e.stop();
			return;
		}
		
		this.fireEvent('editorCopy', [e, this]);
	},
	
	/**
	 * @author Original authors.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */		
	editorPaste: function(e) {
		if (this.editorDisabled) {
			e.stop();
			return;
		}
		
		this.fireEvent('editorPaste', [e, this]);
	},
	
	/**
	 * @author Original authors.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */		
	keyListener: function(e) {
		var key = (Browser.Platform.mac) ? e.meta : e.control;
		if (!key || !this.keys[e.key]) return;
		e.preventDefault();
		var item = this.toolbar.getItem(this.keys[e.key]);
		item.action(e);
	},

	/**
	 * @author Original authors.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */	
	focus: function() {
		(this.mode == 'iframe' ? this.win : this.textarea).focus();
		this.fireEvent('focus', this);
		return this;
	},

	/**
	 * @author Original authors.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */	
	action: function(command, args) {
		var action = Moo.UI.Editor.Actions[command];
		if (action.command && $type(action.command) == 'function') {
			action.command.run(args, this);
		} else {
			this.focus();
			this.execute(command, false, args);
			if (this.mode == 'iframe') this.checkStates();
		}
	},

	/**
	 * @author Original authors.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */	
	execute: function(command, param1, param2) {
		if (this.busy) return;
		this.busy = true;
		this.doc.execCommand(command, param1, param2);
		this.saveContent();
		this.busy = false;
		return false;
	},

	/**
	 * @author Original authors.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */	
	toggleView: function() {
		this.fireEvent('beforeToggleView', this);
		if (this.mode == 'textarea') {
			this.mode = 'iframe';
			this.iframe.setStyle('display', '');
			this.setContent(this.textarea.value);
			this.textarea.setStyle('display', 'none');
		} else {
			this.saveContent();
			this.mode = 'textarea';
			this.textarea.setStyle('display', '');
			this.iframe.setStyle('display', 'none');
		}
		this.fireEvent('toggleView', this);
		this.focus.delay(10, this);
		return this;
	},

	/**
	 * @author Original authors.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */	
	getContent: function() {
		return this.cleanup(this.doc.body.get('html'));
	},

	/**
	 * @author Original authors.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */	
	setContent: function(newContent) {
		this.doc.body.set('html', this.ensureRootElement(newContent));
		return this;
	},

	/**
	 * @author Original authors.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */	
	saveContent: function() {
		if (this.mode == 'iframe') {
			this.textarea.set('value', this.ensureRootElement(this.getContent()));
		}
		return this;
	},
	
	/**
	 * @author Original authors.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */		
	ensureRootElement: function(val) {
		if (this.options.rootElement) {
			var el = new Element('div', {html: val.trim()});
			var start = -1;
			var create = false;
			var html = '';
			var length = el.childNodes.length;
			for (i=0; i<length; i++) {
				var childNode = el.childNodes[i];
				var nodeName = childNode.nodeName;
				if (!nodeName.test(blockEls) && nodeName !== '#comment') {
					if (nodeName === '#text') {
						if (childNode.nodeValue.trim()) {
							if (start < 0) start = i;
							html += childNode.nodeValue;
						}
					} else {
						if (start < 0) start = i;
						html += new Element('div').adopt($(childNode).clone()).get('html');
					}
				} else {
					create = true;
				}
				if (i == (length-1)) create = true;
				if (start >= 0 && create) {
					var newel = new Element(this.options.rootElement, {html: html});
					el.replaceChild(newel, el.childNodes[start]);
					for (k=start+1; k<i; k++) { 
						el.removeChild(el.childNodes[k]);
						length--;
						i--;
						k--;
					}
					start = -1;
					create = false;
					html = '';
				}
			}
			val = el.get('html').replace(/\n\n/g, '');
		}
		return val;
	},

	/**
	 * @author Original authors.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */	
	checkStates: function() {
		var element = this.selection.getNode();
		if (!element) return;
		if ($type(element) != 'element') return;
		
		this.actions.each(function(action) {
			var item = this.toolbar.getItem(action);
			if (!item) return;
			item.deactivate();

			var states = Moo.UI.Editor.Actions[action]['states'];
			if (!states) return;
			
			// custom checkState
			if ($type(states) == 'function') {
				states.attempt([document.id(element), item], this);
				return;
			}
			
			try{
				if (this.doc.queryCommandState(action)) {
					item.activate();
					return;
				}
			} catch(e) {}
			
			if (states.tags) {
				var el = element;
				do {
					var tag = el.tagName.toLowerCase();
					if (states.tags.contains(tag)) {
						item.activate(tag);
						break;
					}
				}
				while ((el = Element.getParent(el)) != null);
			}

			if (states.css) {
				var el = element;
				do {
					var found = false;
					for (var prop in states.css) {
						var css = states.css[prop];
						if (Element.getStyle(el, prop).contains(css)) {
							item.activate(css);
							found = true;
						}
					}
					if (found || el.tagName.test(blockEls)) break;
				}
				while ((el = Element.getParent(el)) != null);
			}
		}.bind(this));
	},

	/**
	 * @author Original authors.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */	
	cleanup: function(source) {
		if (!this.options.cleanup) return source.trim();
		
		do {
			var oSource = source;

			// webkit cleanup
			source = source.replace(/<br class\="webkit-block-placeholder">/gi, "<br />");
			source = source.replace(/<span class="Apple-style-span">(.*)<\/span>/gi, '$1');
			source = source.replace(/ class="Apple-style-span"/gi, '');
			source = source.replace(/<span style="">/gi, '');

			// remove padded paragraphs
			source = source.replace(/<p>\s*<br ?\/?>\s*<\/p>/gi, '<p>\u00a0</p>');
			source = source.replace(/<p>(&nbsp;|\s)*<\/p>/gi, '<p>\u00a0</p>');
			if (!this.options.semantics) {
				source = source.replace(/\s*<br ?\/?>\s*<\/p>/gi, '</p>');
			}

			// replace improper BRs (only if XHTML : true)
			if (this.options.xhtml) {
				source = source.replace(/<br>/gi, "<br />");
			}

			if (this.options.semantics) {
				
				// remove divs from <li>
				if (Browser.Engine.trident) {
					source = source.replace(/<li>\s*<div>(.+?)<\/div><\/li>/g, '<li>$1</li>');
				}
				
				// remove stupid apple divs
				if (Browser.Engine.webkit) {
					source = source.replace(/^([\w\s]+.*?)<div>/i, '<p>$1</p><div>');
					source = source.replace(/<div>(.+?)<\/div>/ig, '<p>$1</p>');
				}

				//<p> tags around a list will get moved to after the list
				if (['gecko', 'presto', 'webkit'].contains(Browser.Engine.name)) {
					// not working properly in safari?
					source = source.replace(/<p>[\s\n]*(<(?:ul|ol)>.*?<\/(?:ul|ol)>)(.*?)<\/p>/ig, '$1<p>$2</p>');
					source = source.replace(/<\/(ol|ul)>\s*(?!<(?:p|ol|ul|img).*?>)((?:<[^>]*>)?\w.*)$/g, '</$1><p>$2</p>');
				}

				source = source.replace(/<br[^>]*><\/p>/g, '</p>'); // remove <br>'s that end a paragraph here.
				source = source.replace(/<p>\s*(<img[^>]+>)\s*<\/p>/ig, '$1\n'); // if a <p> only contains <img>, remove the <p> tags

				// format the source
				source = source.replace(/<p([^>]*)>(.*?)<\/p>(?!\n)/g, '<p$1>$2</p>\n'); // break after paragraphs
				source = source.replace(/<\/(ul|ol|p)>(?!\n)/g, '</$1>\n'); // break after </p></ol></ul> tags
				source = source.replace(/><li>/g, '>\n\t<li>'); // break and indent <li>
				source = source.replace(/([^\n])<\/(ol|ul)>/g, '$1\n</$2>'); //break before </ol></ul> tags
				source = source.replace(/([^\n])<img/ig, '$1\n<img'); // move images to their own line
				source = source.replace(/^\s*$/g, ''); // delete empty lines in the source code (not working in opera)
			}

			// remove leading and trailing BRs
			source = source.replace(/<br ?\/?>$/gi, '');
			source = source.replace(/^<br ?\/?>/gi, '');

			// remove useless BRs
			source = source.replace(/><br ?\/?>/gi, '>');

			// remove BRs right before the end of blocks
			source = source.replace(/<br ?\/?>\s*<\/(h1|h2|h3|h4|h5|h6|li|p)/gi, '</$1');

			// semantic conversion
			source = source.replace(/<span style="font-weight: bold;">(.*)<\/span>/gi, '<strong>$1</strong>');
			source = source.replace(/<span style="font-style: italic;">(.*)<\/span>/gi, '<em>$1</em>');
			source = source.replace(/<b\b[^>]*>(.*?)<\/b[^>]*>/gi, '<strong>$1</strong>');
			source = source.replace(/<i\b[^>]*>(.*?)<\/i[^>]*>/gi, '<em>$1</em>');
			source = source.replace(/<u\b[^>]*>(.*?)<\/u[^>]*>/gi, '<span style="text-decoration: underline;">$1</span>');
			source = source.replace(/<strong><span style="font-weight: normal;">(.*)<\/span><\/strong>/gi, '$1');
			source = source.replace(/<em><span style="font-weight: normal;">(.*)<\/span><\/em>/gi, '$1');
			source = source.replace(/<span style="text-decoration: underline;"><span style="font-weight: normal;">(.*)<\/span><\/span>/gi, '$1');
			source = source.replace(/<strong style="font-weight: normal;">(.*)<\/strong>/gi, '$1');
			source = source.replace(/<em style="font-weight: normal;">(.*)<\/em>/gi, '$1');

			// replace uppercase element names with lowercase
			source = source.replace(/<[^> ]*/g, function(match) {return match.toLowerCase();});

			// replace uppercase attribute names with lowercase
			source = source.replace(/<[^>]*>/g, function(match) {
				   match = match.replace(/ [^=]+=/g, function(match2) {return match2.toLowerCase();});
				   return match;
			});

			// rut quotes around unquoted attributes
			source = source.replace(/<[^>]*>/g, function(match) {
				   match = match.replace(/( [^=]+=)([^"][^ >]*)/g, "$1\"$2\"");
				   return match;
			});

			// make img tags xhtml compatible <img>,<img></img> -> <img/>
			if (this.options.xhtml) {
				source = source.replace(/<img([^>]+)(\s*[^\/])>(<\/img>)*/gi, '<img$1$2 />');
			}
			
			// remove double <p> tags and empty <p> tags
			source = source.replace(/<p>(?:\s*)<p>/g, '<p>');
			source = source.replace(/<\/p>\s*<\/p>/g, '</p>');
			
			// replace <br>s inside <pre> automatically added by some browsers
			source = source.replace(/<pre[^>]*>.*?<\/pre>/gi, function(match) {
				return match.replace(/<br ?\/?>/gi, '\n');
			});

			// final trim
			source = source.trim();
		} while (source != oSource);

		return source;
	}

});


Moo.UI.Editor.Selection = new Class({

	/**
	 * @author Original authors.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */	
	initialize: function(win) {
		this.win = win;
	},

	/**
	 * @author Original authors.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */	
	getSelection: function() {
		this.win.focus();
		return (this.win.getSelection) ? this.win.getSelection() : this.win.document.selection;
	},

	/**
	 * @author Original authors.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */	
	getRange: function() {
		var s = this.getSelection();

		if (!s) return null;

		try {
			return s.rangeCount > 0 ? s.getRangeAt(0) : (s.createRange ? s.createRange() : null);
		} catch(e) {
			// IE bug when used in frameset
			return this.doc.body.createTextRange();
		}
	},

	/**
	 * @author Original authors.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */	
	setRange: function(range) {
		if (range.select) {
			$try(function() {
				range.select();
			});
		} else {
			var s = this.getSelection();
			if (s.addRange) {
				s.removeAllRanges();
				s.addRange(range);
			}
		}
	},

	/**
	 * @author Original authors.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */	
	selectNode: function(node, collapse) {
		var s = this.getSelection();
		var r = this.getRange();		
		if (r.moveToElementText) {
			$try(function() {
				r.moveToElementText(node);
				r.select();
			});
		} else if (s.addRange) {
			collapse ? r.selectNodeContents(node) : r.selectNode(node);
			s.removeAllRanges();
			s.addRange(r);
		} else {
			s.setBaseAndExtent(node, 0, node, 1);
		}
		return node;
	},

	/**
	 * @author Original authors.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */	
	isCollapsed: function() {
		var r = this.getRange();
		if (r.item) return false;
		return r.boundingWidth == 0 || this.getSelection().isCollapsed;
	},

	/**
	 * @author Original authors.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */	
	collapse: function(toStart) {
		var r = this.getRange();
		var s = this.getSelection();
		if (r.select) {
			r.collapse(toStart);
			r.select();
		} else {
			toStart ? s.collapseToStart() : s.collapseToEnd();
		}
	},

	/**
	 * @author Original authors.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */	
	getContent: function() {
		var r = this.getRange();
		var body = new Element('body');

		if (this.isCollapsed()) return '';

		if (r.cloneContents) {
			body.appendChild(r.cloneContents());
		} else if ($defined(r.item) || $defined(r.htmlText)) {
			body.set('html', r.item ? r.item(0).outerHTML : r.htmlText);
		} else {
			body.set('html', r.toString());
		}

		var content = body.get('html');
		return content;
	},

	/**
	 * @author Original authors.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */	
	getText : function() {
		var r = this.getRange();
		var s = this.getSelection();
		return this.isCollapsed() ? '' : r.text || (s.toString ? s.toString() : '');
	},

	/**
	 * @author Original authors.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */	
	getNode: function() {
		var r = this.getRange();

		if (!Browser.Engine.trident) {
			var el = null;

			if (r) {
				el = r.commonAncestorContainer;

				// Handle selection a image or other control like element such as anchors
				if (!r.collapsed)
					if (r.startContainer == r.endContainer)
						if (r.startOffset - r.endOffset < 2)
							if (r.startContainer.hasChildNodes())
								el = r.startContainer.childNodes[r.startOffset];

				while ($type(el) != 'element') el = el.parentNode;
			}

			return document.id(el);
		}

		return document.id(r.item ? r.item(0) : r.parentElement());
	},
	
	/**
	 * @author Original authors.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */	
	insertContent: function(content) {
		if (Browser.Engine.trident) {
			var r = this.getRange();
			r.pasteHTML(content);
			r.collapse(false);
			r.select();
		} else {
			this.win.document.execCommand('insertHTML', false, content);
		}
	}

});

Moo.UI.Editor.Toolbar = new Class
({
	/**
	 * @implements Events, Options.
	 */
	Implements: [Events, Options],

	/**
	 * @var object The options.
	 */
	options: {
		onItemAction: $empty,
		'class': ''
	},
	
	/**
	 * @author Original authors.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */	    
	initialize: function(options) {
		this.setOptions(options);
		this.el = new Element('div', {'class': 'editor-ui-toolbar ' + this.options['class']});
		this.items = {};
		this.content = null;
	},
	
	/**
	 * @author Original authors.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */		
	toElement: function() {
		return this.el;
	},
	
	/**
	 * @author Original authors.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */		
	render: function(actions) {
		if (this.content) {
			this.el.adopt(this.content);
		} else {
			this.content = actions.map(function(action) {
				return (action == '|') ? this.addSeparator() : this.addItem(action);
			}.bind(this));
		}
		return this;
	},
	
	/**
	 * @author Original authors.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */		
	addItem: function(action) {
		var self = this;
		var act = Moo.UI.Editor.Actions[action];
		if (!act) return;
		var type = act.type || 'button';
		var options = act.options || {};
		var item = new Moo.UI.Editor[type.camelCase().capitalize()]($extend(options, {
			name: action,
			'class': action + '-item toolbar-item',
			title: act.title,
			onAction: self.itemAction.bind(self)
		}));
		this.items[action] = item;
		document.id(item).inject(this.el);
		return item;
	},
	
	/**
	 * @author Original authors.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */		
	getItem: function(action) {
		return this.items[action];
	},
	
	/**
	 * @author Original authors.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */		
	addSeparator: function() {
		return new Element('span', {'class': 'toolbar-separator'}).inject(this.el);
	},
	
	/**
	 * @author Original authors.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */		
	itemAction: function() {
		this.fireEvent('itemAction', arguments);
	},
	
	/**
	 * @author Original authors.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */	
	disable: function(except) {
		$each(this.items, function(item) {
			(item.name == except) ? item.activate() : item.deactivate().disable();
		});
		return this;
	},

	/**
	 * @author Original authors.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */	
	enable: function() {
		$each(this.items, function(item) {
			item.enable();
		});
		return this;
	},
	
	/**
	 * @author Original authors.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */		
	show: function() {
		this.el.setStyle('display', '');
		return this;
	},
	
	/**
	 * @author Original authors.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */		
	hide: function() {
		this.el.setStyle('display', 'none');
		return this;
	}
	
});

Moo.UI.Editor.Button = new Class
({
	/**
	 * @implements Events, Options.
	 */
	Implements: [Events, Options],

	/**
	 * @var object The options.
	 */
	options: {
		onAction: $empty,
		title: '',
		name: '',
		text: 'Button',
		'class': '',
		shortcut: '',
		mode: 'icon'
	},

	/**
	 * @author Original authors.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */	
	initialize: function(options) {
		this.setOptions(options);
		this.name = this.options.name;
		this.render();
	},
	
	/**
	 * @author Original authors.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */		
	toElement: function() {
		return this.el;
	},
	
	/**
	 * @author Original authors.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */		
	render: function() {
		var self = this;
		var key = (Browser.Platform.mac) ? 'Cmd' : 'Ctrl';
		var shortcut = (this.options.shortcut) ? ' ( ' + key + '+' + this.options.shortcut.toUpperCase() + ' )' : '';
		var text = this.options.title || name;
		var title = text + shortcut;
		this.el = new Element('button', {
			'class': 'editor-ui-button ' + self.options['class'],
			title: title,
			html: '<span class="button-icon"></span><span class="button-text">' + text + '</span>',
			events: {
				click: self.click.bind(self),
				mousedown: function(e) { e.preventDefault(); }
			}
		});
		if (this.options.mode != 'icon') this.el.addClass('editor-ui-button-' + this.options.mode);
		
		this.active = false;
		this.disabled = false;

		// add hover effect for IE
		if (Browser.Engine.trident) this.el.addEvents({
			mouseenter: function(e) { this.addClass('hover'); },
			mouseleave: function(e) { this.removeClass('hover'); }
		});
		
		return this;
	},
	
	/**
	 * @author Original authors.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */		
	click: function(e) {
		e.preventDefault();
		if (this.disabled) return;
		this.action(e);
	},
	
	/**
	 * @author Original authors.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */		
	action: function() {
		this.fireEvent('action', [this].concat($A(arguments)));
	},
	
	/**
	 * @author Original authors.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */		
	enable: function() {
		if (this.active) this.el.removeClass('onActive');
		if (!this.disabled) return;
		this.disabled = false;
		this.el.removeClass('disabled').set({
			disabled: false,
			opacity: 1
		});
		return this;
	},
	
	/**
	 * @author Original authors.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */		
	disable: function() {
		if (this.disabled) return;
		this.disabled = true;
		this.el.addClass('disabled').set({
			disabled: true,
			opacity: 0.4
		});
		return this;
	},
	
	/**
	 * @author Original authors.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */		
	activate: function() {
		if (this.disabled) return;
		this.active = true;
		this.el.addClass('onActive');
		return this;
	},
	
	/**
	 * @author Original authors.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */		
	deactivate: function() {
		this.active = false;
		this.el.removeClass('onActive');
		return this;
	}
});

Moo.UI.Editor.Dialog = new Class
({
	/**
	 * @implements Events, Options.
	 */
	Implements: [Events, Options],

	/**
	 * @var object The options.
	 */
	options: {
		onOpen: $empty,
		onClose: $empty,
		'class': '',
		contentClass: ''
	},

	/**
	 * @author Original authors.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */	
	initialize: function(html, options) {
		this.setOptions(options);
		this.html = html;
		
		var self = this;
		this.el = new Element('div', {
			'class': 'editor-ui-dialog ' + self.options['class'],
			html: '<div class="dialog-content ' + self.options.contentClass + '">' + html + '</div>',
			styles: {
				'display': 'none'
			},
			events: {
				click: self.click.bind(self)
			}
		});
	},
	
	/**
	 * @author Original authors.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */		
	toElement: function() {
		return this.el;
	},
	
	/**
	 * @author Original authors.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */		
	click: function() {
		this.fireEvent('click', arguments);
		return this;
	},
	
	/**
	 * @author Original authors.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */		
	open: function() {
		this.el.setStyle('display', '');
		this.fireEvent('open', this);
		return this;
	},
	
	/**
	 * @author Original authors.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */		
	close: function() {
		this.el.setStyle('display', 'none');
		this.fireEvent('close', this);
		return this;
	}
});

Moo.UI.Editor.AlertDialog = function(alertText) {
	if (!alertText) return;
	var html = alertText + ' <button class="dialog-ok-button">OK</button>';
	return new Moo.UI.Editor.Dialog(html, {
		'class': 'editor-alert-dialog',
		onOpen: function() {
			var button = this.el.getElement('.dialog-ok-button');
			(function() {
				button.focus();
			}).delay(10);
		},
		onClick: function(e) {
			e.preventDefault();
			if (e.target.tagName.toLowerCase() != 'button') return;
			if (document.id(e.target).hasClass('dialog-ok-button')) this.close();
		}
	});
};

Moo.UI.Editor.PromptDialog = function(questionText, answerText, fn) {
	if (!questionText) return;
	var html = '<label class="dialog-label">' + questionText
		+ ' <input type="text" class="text dialog-input" value="' + answerText + '">'
		+ '</label> <button class="dialog-button dialog-ok-button">OK</button>'
		+ '<button class="dialog-button dialog-cancel-button">Cancel</button>';
	return new Moo.UI.Editor.Dialog(html, {
		'class': 'editor-prompt-dialog',
		onOpen: function() {
			var input = this.el.getElement('.dialog-input');
			(function() {
				input.focus();
				input.select();
			}).delay(10);
		},
		onClick: function(e) {
			e.preventDefault();
			if (e.target.tagName.toLowerCase() != 'button') return;
			var button = document.id(e.target);
			var input = this.el.getElement('.dialog-input');
			if (button.hasClass('dialog-cancel-button')) {
				input.set('value', answerText);
				this.close();
			} else if (button.hasClass('dialog-ok-button')) {
				var answer = input.get('value');
				input.set('value', answerText);
				this.close();
				if (fn) fn.attempt(answer, this);
			}
		}
	});
};

Moo.UI.Editor.Actions = new Hash({

	bold: {
		title: 'Bold',
		options: {
			shortcut: 'b'
		},
		states: {
			tags: ['b', 'strong'],
			css: {'font-weight': 'bold'}
		},
		events: {
			beforeToggleView: function() {
				if(Browser.Engine.gecko) {
					var s = this.textarea.get('value')
					.replace(/<strong([^>]*)>/gi, '<b$1>')
					.replace(/<\/strong>/gi, '</b>')
					this.textarea.set('value',s);
				}
			},
			attach: function() {
				if(Browser.Engine.gecko) {
					var s = this.textarea.get('value')
					.replace(/<strong([^>]*)>/gi, '<b$1>')
					.replace(/<\/strong>/gi, '</b>')
					this.textarea.set('value',s);
					this.setContent(s);
				}
			}
		}
	},
	
	italic: {
		title: 'Italic',
		options: {
			shortcut: 'i'
		},
		states: {
			tags: ['i', 'em'],
			css: {'font-style': 'italic'}
		},
		events: {
			beforeToggleView: function() {
				if (Browser.Engine.gecko) {
					var s = this.textarea.get('value')
						.replace(/<embed([^>]*)>/gi, '<tmpembed$1>')
						.replace(/<em([^>]*)>/gi, '<i$1>')
						.replace(/<tmpembed([^>]*)>/gi, '<embed$1>')
						.replace(/<\/em>/gi, '</i>');
					this.textarea.set('value', s);
				}
			},
			attach: function() {
				if (Browser.Engine.gecko) {
					var s = this.textarea.get('value')
						.replace(/<embed([^>]*)>/gi, '<tmpembed$1>')
						.replace(/<em([^>]*)>/gi, '<i$1>')
						.replace(/<tmpembed([^>]*)>/gi, '<embed$1>')
						.replace(/<\/em>/gi, '</i>');
					this.textarea.set('value', s);
					this.setContent(s);
				}
			}
		}
	},
	
	underline: {
		title: 'Underline',
		options: {
			shortcut: 'u'
		},
		states: {
			tags: ['u'],
			css: {'text-decoration': 'underline'}
		}
	},
	
	strikethrough: {
		title: 'Strikethrough',
		options: {
			shortcut: 's'
		},
		states: {
			tags: ['s', 'strike'],
			css: {'text-decoration': 'line-through'}
		}
	},
	
	insertunorderedlist: {
		title: 'Unordered List',
		states: {
			tags: ['ul']
		}
	},
	
	insertorderedlist: {
		title: 'Ordered List',
		states: {
			tags: ['ol']
		}
	},
	
	indent: {
		title: 'Indent',
		states: {
			tags: ['blockquote']
		}
	},
	
	outdent: {
		title: 'Outdent'
	},
	
	undo: {
		title: 'Undo',
		options: {
			shortcut: 'z'
		}
	},
	
	redo: {
		title: 'Redo',
		options: {
			shortcut: 'y'
		}
	},
	
	unlink: {
		title: 'Remove Hyperlink'
	},

	createlink: {
		title: 'Add Hyperlink',
		options: {
			shortcut: 'l'
		},
		states: {
			tags: ['a']
		},
		dialogs: {
			alert: Moo.UI.Editor.AlertDialog.pass('Please select the text you wish to hyperlink.'),
			prompt: function(editor) {
				return Moo.UI.Editor.PromptDialog('Enter URL', 'http://', function(url) {
					editor.execute('createlink', false, url.trim());
				});
			}
		},
		command: function() {
			if (this.selection.isCollapsed()) {
				this.dialogs.createlink.alert.open();
			} else {
				var text = this.selection.getText();
				var prompt = this.dialogs.createlink.prompt;
				if (urlRegex.test(text)) prompt.el.getElement('.dialog-input').set('value', text);
				prompt.open();
			}
		}
	},

	urlimage: {
		title: 'Add Image',
		options: {
			shortcut: 'm'
		},
		dialogs: {
			prompt: function(editor) {
				return Moo.UI.Editor.PromptDialog('Enter image URL', 'http://', function(url) {
					editor.execute("insertimage", false, url.trim());
				});
			}
		},
		command: function() {
			this.dialogs.urlimage.prompt.open();
		}
	},

	toggleview: {
		title: 'Toggle View',
		command: function() {
			(this.mode == 'textarea') ? this.toolbar.enable() : this.toolbar.disable('toggleview');
			this.toggleView();
		}
	}

});

Moo.UI.Editor.Actions.Settings = {};

Element.Properties.editor = {

	set: function(options) {
		return this.eliminate('editor').store('editor:options', options);
	},

	get: function(options) {
		if (options || !this.retrieve('editor')) {
			if (options || !this.retrieve('editor:options')) this.set('editor', options);
			this.store('editor', new Moo.UI.Editor(this, this.retrieve('editor:options')));
		}
		return this.retrieve('editor');
	}

};

Element.implement({

	mooEditable: function(options) {
		return this.get('editor', options);
	}

});

})();
/*
---

script: Moo.UI.Editor.MenuList.js

description: UI Class to create a menu list (select) element.

license: MIT-style license

authors:
- Lim Chee Aun

requires:
# - Moo.UI.Editor
# - Moo.UI.Editor

provides: [Moo.UI.Editor.MenuList]

...
*/

Moo.UI.Editor.MenuList = new Class({

	Implements: [Events, Options],

	options: {
		/*
		onAction: $empty,
		*/
		title: '',
		name: '',
		'class': '',
		list: []
	},

	initialize: function(options) {
		this.setOptions(options);
		this.name = this.options.name;
		this.render();
	},
	
	toElement: function() {
		return this.el;
	},
	
	render: function() {
		var self = this;
		var html = '';
		this.options.list.each(function(item) {
			html += '<option value="{value}" style="{style}">{text}</option>'.substitute(item);
		});
		this.el = new Element('select', {
			'class': self.options['class'],
			title: self.options.title,
			html: html,
			styles: { 'height' : '25px' },
			events: {
				change: self.change.bind(self)
			}
		});
		
		this.disabled = false;

		// add hover effect for IE
		if (Browser.Engine.trident) this.el.addEvents({
			mouseenter: function(e) { this.addClass('hover'); },
			mouseleave: function(e) { this.removeClass('hover'); }
		});
		
		return this;
	},
	
	change: function(e) {
		e.preventDefault();
		if (this.disabled) return;
		var name = e.target.value;
		this.action(name);
	},
	
	action: function() {
		this.fireEvent('action', [this].concat($A(arguments)));
	},
	
	enable: function() {
		if (!this.disabled) return;
		this.disabled = false;
		this.el.set('disabled', false).removeClass('disabled').set({
			disabled: false,
			opacity: 1
		});
		return this;
	},
	
	disable: function() {
		if (this.disabled) return;
		this.disabled = true;
		this.el.set('disabled', true).addClass('disabled').set({
			disabled: true,
			opacity: 0.4
		});
		return this;
	},
	
	activate: function(value) {
		if (this.disabled) return;
		var index = 0;
		if (value) this.options.list.each(function(item, i) {
			if (item.value == value) index = i;
		});
		this.el.selectedIndex = index;
		return this;
	},
	
	deactivate: function() {
		this.el.selectedIndex = 0;
		this.el.removeClass('onActive');
		return this;
	}
	
});
// +---------------------------------------------------------------------------+
// | This file is part of the Mootools Addon project.                          |
// | Copyright (C) Lemieux BÅ½dard Communication                                |
// |                                                                           |
// | For the full copyright and license information, please view the LICENSE   |
// | file that was distributed with this source code.                          |
// +---------------------------------------------------------------------------+

/**
 * Moo.UI.Editor.ButtonOverlay.js
 * UI Class to create a button element with a popup overlay.
 * MIT-style license
 * Lim Chee Aun
 * requires:
 * # - Moo.UI.Editor
 * # - Moo.UI.Editor
 * provides: [Moo.UI.Editor.ButtonOverlay]
 */

Moo.UI.Editor.ButtonOverlay = new Class({

	Extends: Moo.UI.Editor.Button,

	options: {
		/*
		onOpenOverlay: $empty,
		onCloseOverlay: $empty,
		*/
		overlayHTML: '',
		overlayClass: '',
		overlaySize: {x: 150, y: 'auto'},
		overlayContentClass: ''
	},

	initialize: function(options) {
		this.parent(options);
		this.render();
		this.el.addClass('editor-ui-buttonOverlay');
		this.renderOverlay(this.options.overlayHTML);
	},
	
	renderOverlay: function(html) {
		var self = this;
		this.overlay = new Element('div', {
			'class': 'editor-ui-button-overlay ' + self.name + '-overlay ' + self.options.overlayClass,
			html: '<div class="overlay-content ' + self.options.overlayContentClass + '">' + html + '</div>',
			tabindex: 0,
			styles: {
				left: '-999em',
				position: 'absolute',
				width: self.options.overlaySize.x,
				height: self.options.overlaySize.y
			},
			events: {
				mousedown: self.clickOverlay.bind(self),
				focus: self.openOverlay.bind(self),
				blur: self.closeOverlay.bind(self)
			}
		}).inject(document.body).store('Moo.UI.Editor.ButtonOverlay', this);
		this.overlayVisible = false;
	},
	
	openOverlay: function() {
		if (this.overlayVisible) return;
		this.overlayVisible = true;
		this.activate();
		this.fireEvent('openOverlay', this);
		return this;
	},
	
	closeOverlay: function() {
		if (!this.overlayVisible) return;
		this.overlay.setStyle('left', '-999em');
		this.overlayVisible = false;
		this.deactivate();
		this.fireEvent('closeOverlay', this);
		return this;
	},
	
	clickOverlay: function(e) {
		if (e.target == this.overlay || e.target.parentNode == this.overlay) return;
		this.overlay.blur();
		e.preventDefault();
		this.action(e);
	},
	
	click: function(e) {
		e.preventDefault();
		if (this.disabled) return;
		if (this.overlayVisible) {
			this.overlay.blur();
			return;
		} else {
			var coords = this.el.getCoordinates();
			this.overlay.setStyles({
				top: coords.bottom,
				left: coords.left
			});
			this.overlay.focus();
		}
	}
	
});/*
---

script: Moo.UI.Editor.Extras.js

description: Extends Moo.UI.Editor to include more (simple) toolbar buttons.

license: MIT-style license

authors:
- Lim Chee Aun
- Ryan Mitchell

requires:
# - Moo.UI.Editor
# - Moo.UI.Editor
# - Moo.UI.Editor.MenuList

provides: 
- Moo.UI.Editor.Actions.formatBlock
- Moo.UI.Editor.Actions.justifyleft
- Moo.UI.Editor.Actions.justifyright
- Moo.UI.Editor.Actions.justifycenter
- Moo.UI.Editor.Actions.justifyfull
- Moo.UI.Editor.Actions.removeformat
- Moo.UI.Editor.Actions.insertHorizontalRule

...
*/

Moo.UI.Editor.Actions.extend({

	formatBlock: {
		title: 'Block Formatting',
		type: 'menu-list',
		options: {
			list: [
				{text: 'Paragraph', value: 'p'},
				{text: 'Heading 1', value: 'h1', style: 'color: #4084bf; font-family: Georgia; font-size:24px;'},
				{text: 'Heading 2', value: 'h2', style: 'color: #4084bf; font-family: Georgia; font-size:18px;'},
				{text: 'Heading 3', value: 'h3', style: 'color: #4084bf; font-family: Georgia; font-size:14px;'}
			]
		},
		states: {
			tags: ['p', 'h1', 'h2', 'h3']
		},
		command: function(menulist, name) {
			var argument = '<' + name + '>';
			this.focus();
			this.execute('formatBlock', false, argument);
		}
	},
	
	justifyleft:{
		title: 'Align Left',
		states: {
			css: {'text-align': 'left'}
		}
	},
	
	justifyright:{
		title: 'Align Right',
		states: {
			css: {'text-align': 'right'}
		}
	},
	
	justifycenter:{
		title: 'Align Center',
		states: {
			tags: ['center'],
			css: {'text-align': 'center'}
		}
	},
	
	justifyfull:{
		title: 'Align Justify',
		states: {
			css: {'text-align': 'justify'}
		}
	},
	
	removeformat: {
		title: 'Remove Formatting'
	},
	
	insertHorizontalRule: {
		title: 'Insert Horizontal Rule',
		states: {
			tags: ['hr']
		},
		command: function() {
			this.selection.insertContent('<hr>');
		}
	}

});
/*
---

script: Moo.UI.Editor.Group.js

description: Extends Moo.UI.Editor to have multiple instances on a page controlled by one toolbar.

license: MIT-style license

authors:
- Ryan Mitchell

requires:
# - Moo.UI.Editor
# - Moo.UI.Editor
# - Moo.UI.Editor.Actions

provides: [Moo.UI.Editor.Group]

...
*/

Moo.UI.Editor.Group = new Class({

	Implements: [Options],
	
	options: {
		actions: 'bold italic underline strikethrough | insertunorderedlist insertorderedlist indent outdent | undo redo | createlink unlink | urlimage | toggleview'
	},
    
	initialize: function(toolbarEl, options) {
		this.setOptions(options);
		this.actions = this.options.actions.clean().split(' ');
		var self = this;
		this.toolbar = new Moo.UI.Editor.Toolbar({
			onItemAction: function() {
				var args = $splat(arguments);
				var item = args[0];
				if (!self.activeEditor) return;
				self.activeEditor.focus();
				self.activeEditor.action(item.name, args);
				if (self.activeEditor.mode == 'iframe') self.activeEditor.checkStates();
			}
		}).render(this.actions);
		document.id(toolbarEl).adopt(this.toolbar);
	},

	add: function(textarea, options) {
		return this.activeEditor = new Moo.UI.Editor.Group.Item(textarea, this, $merge({toolbar: false}, this.options, options));
	}
	
});


Moo.UI.Editor.Group.Item = new Class({

	Extends: Moo.UI.Editor,

	initialize: function(textarea, group, options) {
		var self = this;
		this.group = group;
		this.parent(textarea, options);
		this.addEvent('attach', function() {
			var focus = function() {
				self.group.activeEditor = self;
			};
			self.textarea.addEvent('focus', focus);
			self.win.addEvent('focus', focus);
		});
	}

});/*
---

script: Moo.UI.Editor.Forecolor.js

description: Extends Moo.UI.Editor to change the color of the text from a list a predefined colors.

license: MIT-style license

authors:
- Olivier Refalo

requires:
# - Moo.UI.Editor
# - Moo.UI.Editor
# - Moo.UI.Editor.ButtonOverlay
# - Moo.UI.Editor.Actions

provides: [Moo.UI.Editor.Actions.forecolor]

usage: |
  Add the following tags in your html
  <link rel="stylesheet" href="Moo.UI.Editor.css">
  <link rel="stylesheet" href="Moo.UI.Editor.Forecolor.css">
  <script src="mootools.js"></script>
  <script src="Moo.UI.Editor.js"></script>
  <script src="Moo.UI.Editor.ButtonOverlay.js"></script>
  <script src="Moo.UI.Editor.Forecolor.js"></script>

  <script>
  window.addEvent('domready', function() {
    var editor = $('textarea-1').mooEditable({
      actions: 'bold italic underline strikethrough | forecolor | toggleview'
    });
  });
  </script>

...
*/

Moo.UI.Editor.Actions.Settings.forecolor = {
	colors: [
		['000000', '993300', '333300', '003300', '003366', '000077', '333399', '333333'],
		['770000', 'ff6600', '777700', '007700', '007777', '0000ff', '666699', '777777'],
		['ff0000', 'ff9900', '99cc00', '339966', '33cccc', '3366f0', '770077', '999999'],
		['ff00ff', 'ffcc00', 'ffff00', '00ff00', '00ffff', '00ccff', '993366', 'cccccc'],
		['ff99cc', 'ffcc99', 'ffff99', 'ccffcc', 'ccffff', '99ccff', 'cc9977', 'ffffff']
	]
};

Moo.UI.Editor.Actions.forecolor = {
	type: 'button-overlay',
	title: 'Change Color',
	options: {
		overlaySize: {x: 'auto'},
		overlayHTML: (function() {
			var html = '';
			Moo.UI.Editor.Actions.Settings.forecolor.colors.each(function(row) {
				row.each(function(c) {
					html += '<a href="#" class="forecolor-colorpicker-color" style="background-color: #' + c + '" title="#' + c.toUpperCase() + '"></a>'; 
				});
				html += '<span class="forecolor-colorpicker-br"></span>';
			});
			return html;
		})()
	},
	command: function(buttonOverlay, e) {
		var el = e.target;
		if (el.tagName.toLowerCase() != 'a') return;
		var color = $(el).getStyle('background-color');
		this.execute('forecolor', false, color);
		this.focus();
	}
};

/*
---

script: Moo.UI.Editor.Smiley.js

description: Extends Moo.UI.Editor to insert smiley/emoticons.

license: MIT-style license

authors:
- Olivier Refalo

requires:
# - Moo.UI.Editor
# - Moo.UI.Editor
# - Moo.UI.Editor.ButtonOverlay
# - Moo.UI.Editor.Actions

provides: [Moo.UI.Editor.Actions.smiley]

usage: |
  Add the following tags in your html
  <link rel="stylesheet" href="Moo.UI.Editor.css">
  <link rel="stylesheet" href="Moo.UI.Editor.Smiley.css">
  <script src="mootools.js"></script>
  <script src="Moo.UI.Editor.js"></script>
  <script src="Moo.UI.Editor.ButtonOverlay.js"></script>
  <script src="Moo.UI.Editor.Smiley.js"></script>

  <script>
  window.addEvent('domready', function() {
    var editor = $('textarea-1').mooEditable({
      actions: 'bold italic underline strikethrough | smiley | toggleview'
    });
  });
  </script>

...
*/

Moo.UI.Editor.Actions.Settings.smiley = {
	imagesPath: '../../Assets/Moo.UI.Editor/Smiley/',
	smileys: ['angryface', 'blush', 'gasp', 'grin', 'halo', 'lipsaresealed', 'smile', 'undecided', 'wink'],
	fileExt: '.png'
};

Moo.UI.Editor.Actions.smiley = {
	type: 'button-overlay',
	title: 'Insert Smiley',
	options: {
		overlaySize: {x: 'auto'},
		overlayHTML: (function() {
			var settings = Moo.UI.Editor.Actions.Settings.smiley;
			var html = '';
			settings.smileys.each(function(s) {
				html += '<img src="'+ settings.imagesPath + s + settings.fileExt + '" alt="" class="smiley-image">'; 
			});
			return html;
		})()
	},
	command: function(buttonOverlay, e) {
		var el = e.target;
		if (el.tagName.toLowerCase() != 'img') return;
		var src = $(el).get('src');
		var content = '<img style="border:0;" class="smiley" src="' + src + '" alt="">';
		this.focus();
		this.selection.insertContent(content);
	},
	events: {
		attach: function(editor) {
			if (Browser.Engine.trident) {
				// addListener instead of addEvent, because controlselect is a native event in IE
				editor.doc.addListener('controlselect', function(e) {
					var el = e.target;
					if (el.tagName.toLowerCase() != 'img') return;
					if (!$(el).hasClass('smiley')) return;
					e.preventDefault();
				});
			}
		},
		editorMouseDown: function(e, editor) {
			var el = e.target;
			var isSmiley = (el.tagName.toLowerCase() == 'img') && $(el).hasClass('smiley');
			$try(function() {
				editor.doc.execCommand('enableObjectResizing', false, !isSmiley);
			});
		}
	}
};
/*
---

script: Moo.UI.Editor.Table.js

description: Extends Moo.UI.Editor to insert table with manipulation options.

license: MIT-style license

authors:
- Radovan Lozej
- Ryan Mitchell

requires:
# - Moo.UI.Editor
# - Moo.UI.Editor
# - Moo.UI.Editor.Actions

provides:
- Moo.UI.Editor.TableDialog
- Moo.UI.Editor.Actions.tableadd
- Moo.UI.Editor.Actions.tableedit
- Moo.UI.Editor.Actions.tablerowadd
- Moo.UI.Editor.Actions.tablerowedit
- Moo.UI.Editor.Actions.tablerowspan
- Moo.UI.Editor.Actions.tablerowsplit
- Moo.UI.Editor.Actions.tablerowdelete
- Moo.UI.Editor.Actions.tablecoladd
- Moo.UI.Editor.Actions.tablecoledit
- Moo.UI.Editor.Actions.tablecolspan
- Moo.UI.Editor.Actions.tablecolsplit
- Moo.UI.Editor.Actions.tablecoldelete

usage: |
  Add the following tags in your html
  <link rel="stylesheet" href="Moo.UI.Editor.css">
  <link rel="stylesheet" href="Moo.UI.Editor.Table.css">
  <script src="mootools.js"></script>
  <script src="Moo.UI.Editor.js"></script>
  <script src="Moo.UI.Editor.Table.js"></script>

  <script>
  window.addEvent('domready', function() {
    var editor = $('textarea-1').mooEditable({
      actions: 'bold italic underline strikethrough | table | toggleview'
    });
  });
  </script>

...
*/

Moo.UI.Editor.TableDialog = function(editor, dialog) {
	var html = {
		tableadd: 'columns <input type="text" class="table-c" value="" size="4" /> '
			+ 'rows <input type="text" class="table-r" value="" size="4" /> ',
		tableedit: 'width <input type="text" class="table-w" value="" size="4" /> '
			+ 'class <input type="text" class="table-c" value="" size="15" /> ',
		tablerowedit: 'class <input type="text" class="table-c" value="" size="15" /> type <select class="table-c-type"><option value="th">Header</option><option value="td">Cell</option></select> ',
		tablecoledit: 'width <input type="text" class="table-w" value="" size="4" /> '
			+ 'class <input type="text" class="table-c" value="" size="15" /> '
			+ 'align <select class="table-a"><option>none</option><option>left</option><option>center</option><option>right</option></select> '
			+ 'valign <select class="table-va"><option>none</option><option>top</option><option>middle</option><option>bottom</option></select> '
	};
	html[dialog] += '<button class="dialog-button dialog-ok-button">OK</button>'
		+ '<button class="dialog-button dialog-cancel-button">Cancel</button>';
		
	var action = {
		tableadd: {
			click: function(e) {
				var col = this.el.getElement('.table-c').value.toInt();
				var row = this.el.getElement('.table-r').value.toInt();
				if (!(row>0 && col>0)) return;
				var div, table, tbody, ro = [];
				div = new Element('tdiv');
				table = new Element('table').set('border', 0).set('width', '100%').inject(div);
				tbody = new Element('tbody').inject(table);
				for (var r = 0;r<row;r++) {
					ro[r] = new Element('tr').inject(tbody, 'bottom');
					for (var c=0; c<col; c++) new Element('td').set('html', '&nbsp;').inject(ro[r], 'bottom');
				}
				editor.selection.insertContent(div.get('html'));
			}
		},
		tableedit: {
			load: function(e) {
				var node = editor.selection.getNode().getParent('table');
				this.el.getElement('.table-w').set('value', node.get('width'));
				this.el.getElement('.table-c').set('value', node.className);
			},
			click: function(e) {
				var node = editor.selection.getNode().getParent('table');
				node.set('width', this.el.getElement('.table-w').value);
				node.className = this.el.getElement('.table-c').value;
			}
		},
		tablerowedit: {
			load: function(e) {
				var node = editor.selection.getNode().getParent('tr');
				this.el.getElement('.table-c').set('value', node.className);
				this.el.getElement('.table-c-type').set('value', editor.selection.getNode().get('tag'));
			},
			click: function(e) {
				var node = editor.selection.getNode().getParent('tr');
				node.className = this.el.getElement('.table-c').value;
				node.getElements('td,th').each(function(c) {
					if (this.el.getElement('.table-c-type') != c.get('tag')) {
						var n = editor.doc.createElement(this.el.getElement('.table-c-type').get('value'));
						$(n).set('html',c.get('html')).replaces(c);
					}
				},this);
			}
		},
		tablecoledit: {
			load : function(e) {
				var node = editor.selection.getNode();
				if (node.get('tag') != 'td') node = node.getParent('td');
				this.el.getElement('.table-w').set('value', node.get('width'));
				this.el.getElement('.table-c').set('value', node.className);
				this.el.getElement('.table-a').set('value', node.get('align'));
				this.el.getElement('.table-va').set('value', node.get('valign'));
			},
			click: function(e) {
				var node = editor.selection.getNode();
				if (node.get('tag') != 'td') node = node.getParent('td');
				node.set('width', this.el.getElement('.table-w').value);
				node.className = this.el.getElement('.table-c').value;
				node.set('align', this.el.getElement('.table-a').value);
				node.set('valign', this.el.getElement('.table-va').value);
			}
		}
	};
	
	return new Moo.UI.Editor.Dialog(html[dialog], {
		'class': 'editor-prompt-dialog',
		onOpen: function() {
			if (action[dialog].load) action[dialog].load.apply(this);
			var input = this.el.getElement('input');
			(function() { input.focus(); }).delay(10);
		},
		onClick: function(e) {
			if (e.target.tagName.toLowerCase() == 'button') e.preventDefault();
			var button = document.id(e.target);
			if (button.hasClass('dialog-cancel-button')) {
				this.close();
			} else if (button.hasClass('dialog-ok-button')) {
				this.close();
				action[dialog].click.apply(this);
			}
		}
	});
};

Moo.UI.Editor.Actions.extend({

	tableadd:{
		title: 'Add Table',
		dialogs: {
			prompt: function(editor) {
				return Moo.UI.Editor.TableDialog(editor, 'tableadd');
			}
		},
		command: function() {
			this.dialogs.tableadd.prompt.open();
		}
	},
	
	tableedit:{
		title: 'Edit Table',
		dialogs: {
			prompt: function(editor) {
				return Moo.UI.Editor.TableDialog(editor, 'tableedit');
			}
		},
		command: function() {
			if (this.selection.getNode().getParent('table')) this.dialogs.tableedit.prompt.open();
		}
	},
	
	tablerowadd:{
		title: 'Add Row',
		command: function() {
			var node = this.selection.getNode().getParent('tr');
			if (node) node.clone().inject(node, 'after');
		}
	},
	
	tablerowedit:{
		title: 'Edit Row',
		dialogs: {
			prompt: function(editor) {
				return Moo.UI.Editor.TableDialog(editor, 'tablerowedit');
			}
		},
		command: function() {
			if (this.selection.getNode().getParent('table')) this.dialogs.tablerowedit.prompt.open();
		}
	},
	
	tablerowspan:{
		title: 'Merge Row',
		command: function() {
			var node = this.selection.getNode();
			if (node.get('tag') != 'td') node = node.getParent('td');
			if (node) {
				var index = node.cellIndex;
				var row = node.getParent().rowIndex;
				if (node.getParent().getParent().childNodes[row+node.rowSpan]) {
					node.getParent().getParent().childNodes[row+node.rowSpan].deleteCell(index);
					node.rowSpan++;
				}
			}
		}
	},
	
	tablerowsplit:{
		title: 'Split Row',
		command: function() {
			var node = this.selection.getNode();
			if (node.get('tag') != 'td') node = node.getParent('td');
			if (node) {
				var index = node.cellIndex;
				var row = node.getParent().rowIndex;
				if (node.getProperty('rowspan')) {
					var rows = parseInt(node.getProperty('rowspan'));
					for (i=1; i<rows; i++) {
						node.getParent().getParent().childNodes[row+i].insertCell(index);
					}
					node.removeProperty('rowspan');
				}
			}
		},
		states: function(node) {
			if (node.get('tag') != 'td') return;
			if (node) {
				if (node.getProperty('rowspan') && parseInt(node.getProperty('rowspan')) > 1) {
					this.el.addClass('onActive');
				}
			}
		}
	},
	
	tablerowdelete:{
		title: 'Delete Row',
		command: function() {
			var node = this.selection.getNode().getParent('tr');
			if (node) node.getParent().deleteRow(node.rowIndex);
		}
	},
	
	tablecoladd:{
		title: 'Add Column',
		command: function() {
			var node = this.selection.getNode();
			if (node.get('tag') != 'td') node = node.getParent('td');
			if (node) {
				var index = node.cellIndex;
				var len = node.getParent().getParent().childNodes.length;
				for (var i=0; i<len; i++) {
					var ref = $(node.getParent().getParent().childNodes[i].childNodes[index]);
					ref.clone().inject(ref, 'after');
				}
			}
		}
	},
	
	tablecoledit:{
		title: 'Edit Column',
		dialogs: {
			prompt: function(editor) {
				return Moo.UI.Editor.TableDialog(editor, 'tablecoledit');
			}
		},
		command: function() {
			if (this.selection.getNode().getParent('table')) this.dialogs.tablecoledit.prompt.open();
		}
	},
	
	tablecolspan:{
		title: 'Merge Cell',
		command: function() {
			var node = this.selection.getNode();
			if (node.get('tag')!='td') node = node.getParent('td');
			if (node) {
				var index = node.cellIndex + 1;
				if (node.getParent().childNodes[index]) {
					node.getParent().deleteCell(index);
					node.colSpan++;
				}
			}
		}
	},
		
	tablecolsplit:{
		title: 'Split Cell',
		command: function() {
			var node = this.selection.getNode();
			if (node.get('tag')!='td') node = node.getParent('td');
			if (node) {
				var index = node.cellIndex + 1;
				if(node.getProperty('colspan')) {
					var cols = parseInt(node.getProperty('colspan'));
					for (i=1;i<cols;i++) {
						node.getParent().insertCell(index+i);
					}
					node.removeProperty('colspan');
				}
			}
		},
		states: function(node) {
			if (node.get('tag')!='td') return;
			if (node) {
				if (node.getProperty('colspan') && parseInt(node.getProperty('colspan')) > 1) {
					this.el.addClass('onActive');
				}
			}
		}
	},
	
	tablecoldelete:{
		title: 'Delete Column',
		command: function() {
			var node = this.selection.getNode();
			if (node.get('tag') != 'td') node = node.getParent('td');
			if (node) {
				var len = node.getParent().getParent().childNodes.length;
				var index = node.cellIndex;
				var tt = node.getParent().getParent();
				for (var i=0; i<len; i++) tt.childNodes[i].deleteCell(index);
			}
		}
	}
	
});
/*
---

script: Moo.UI.Editor.Image.js

description: Extends Moo.UI.Editor to insert image with manipulation options.

license: MIT-style license

authors:
- Radovan Lozej

requires:
# - Moo.UI.Editor
# - Moo.UI.Editor
# - Moo.UI.Editor.Actions

provides: [Moo.UI.Editor.ImageDialog, Moo.UI.Editor.Actions.image]

usage: |
  Add the following tags in your html
  <link rel="stylesheet" href="Moo.UI.Editor.css">
  <link rel="stylesheet" href="Moo.UI.Editor.Image.css">
  <script src="mootools.js"></script>
  <script src="Moo.UI.Editor.js"></script>
  <script src="Moo.UI.Editor.Image.js"></script>

  <script>
  window.addEvent('domready', function() {
    var editor = $('textarea-1').mooEditable({
      actions: 'bold italic underline strikethrough | image | toggleview'
    });
  });
  </script>

...
*/

Moo.UI.Editor.ImageDialog = function(editor) {
	var html = 'url <input type="text" class="dialog-url" value="" size="15" /> '
		+ 'alt <input type="text" class="dialog-alt" value="" size="8" /> '
		+ 'class <input type="text" class="dialog-class" value="" size="8" /> '
		+ 'align <select class="dialog-align"><option>none</option><option>left</option><option>center</option><option>right</option></select> '
		+ '<button class="dialog-button dialog-ok-button">OK</button> '
		+ '<button class="dialog-button dialog-cancel-button">Cancel</button>';
		
	return new Moo.UI.Editor.Dialog(html, {
		'class': 'editor-prompt-dialog',
		onOpen: function() {
			var input = this.el.getElement('.dialog-url');
			var node = editor.selection.getNode();
			if (node.get('tag') == 'img') {
				this.el.getElement('.dialog-url').set('value', node.get('src'));
				this.el.getElement('.dialog-alt').set('value', node.get('alt'));
				this.el.getElement('.dialog-class').set('value', node.className);
				this.el.getElement('.dialog-align').set('align', node.get('align'));
			}
			(function() {
				input.focus();
				input.select();
			}).delay(10);
		},
		onClick: function(e) {
			if (e.target.tagName.toLowerCase() == 'button') e.preventDefault();
			var button = document.id(e.target);
			if (button.hasClass('dialog-cancel-button')) {
				this.close();
			} else if (button.hasClass('dialog-ok-button')) {
				this.close();
				var node = editor.selection.getNode();
				if (node.get('tag') == 'img') {
					node.set('src', this.el.getElement('.dialog-url').get('value').trim());
					node.set('alt', this.el.getElement('.dialog-alt').get('value').trim());
					node.className = this.el.getElement('.dialog-class').get('value').trim();
					node.set('align', this.el.getElement('.dialog-align').get('value'));
				} else {
					var div = new Element('div');
					new Element('img', {
						'src' : this.el.getElement('.dialog-url').get('value').trim(),
						'alt' : this.el.getElement('.dialog-alt').get('value').trim(),
						'class' : this.el.getElement('.dialog-class').get('value').trim(),
						'align' : this.el.getElement('.dialog-align').get('value')
					}).inject(div);
					editor.selection.insertContent(div.get('html'));
				}
			}
		}
	});
};

Moo.UI.Editor.Actions.extend({
	
	image: {
		title: 'Add/Edit Image',
		options: {
			shortcut: 'm'
		},
		dialogs: {
			prompt: function(editor) {
				return Moo.UI.Editor.ImageDialog(editor);
			}
		},
		command: function() {
			this.dialogs.image.prompt.open();
		}
	}
	
});
/*
---

script: Moo.UI.Editor.Flash.js

description: Extends Moo.UI.Editor to embed Flash.

license: MIT-style license

authors:
- Radovan Lozej

requires:
# - Moo.UI.Editor
# - Moo.UI.Editor
# - Moo.UI.Editor.Actions

provides: [Moo.UI.Editor.FlashDialog, Moo.UI.Editor.Actions.flash]

usage: |
  Add the following tags in your html
  <link rel="stylesheet" href="Moo.UI.Editor.css">
  <link rel="stylesheet" href="Moo.UI.Editor.Flash.css">
  <script src="mootools.js"></script>
  <script src="Moo.UI.Editor.js"></script>
  <script src="Moo.UI.Editor.Flash.js"></script>

  <script>
  window.addEvent('domready', function() {
    var editor = $('textarea-1').mooEditable({
      actions: 'bold italic underline strikethrough | flash | toggleview',
      externalCSS: '../../Assets/Moo.UI.Editor/Editable.css'
    });
  });
  </script>

...
*/

Moo.UI.Editor.FlashDialog = function(editor) {
	var html = 'embed <textarea class="dialog-f" value="" rows="2" cols="40" /></textarea> '
		+ '<button class="dialog-button dialog-ok-button">OK</button> '
		+ '<button class="dialog-button dialog-cancel-button">Cancel</button>';
	return new Moo.UI.Editor.Dialog(html, {
		'class': 'editor-prompt-dialog',
		onOpen: function() {
			var input = this.el.getElement('.dialog-f');
			(function() {
				input.focus();
				input.select();
			}).delay(10);
		},
		onClick: function(e) {
			if (e.target.tagName.toLowerCase() == 'button') e.preventDefault();
			var button = document.id(e.target);
			if (button.hasClass('dialog-cancel-button')) {
				this.close();
			} else if (button.hasClass('dialog-ok-button')) {
				this.close();
				var div = new Element('div').set('html', this.el.getElement('.dialog-f').get('value').trim());
				editor.selection.insertContent(div.get('html'));
			}
		}
	});
};

Moo.UI.Editor.Actions.extend({
	
	flash: {
		title: 'Flash embed',
		dialogs: {
			prompt: function(editor) {
				return Moo.UI.Editor.FlashDialog(editor);
			}
		},
		command: function() {
			this.dialogs.flash.prompt.open();
		}
	}
	
});
/*
---

script: Moo.UI.Editor.Charmap.js

description: Extends Moo.UI.Editor with a characters map

license: MIT-style license

authors:
- Ryan Mitchell

requires:
# - Moo.UI.Editor
# - Moo.UI.Editor
# - Moo.UI.Editor.Actions

provides: [Moo.UI.Editor.CharacterDialog, Moo.UI.Editor.Actions.charmap]

usage: |
  Add the following tags in your html
  <link rel="stylesheet" href="Moo.UI.Editor.css">
  <link rel="stylesheet" href="Moo.UI.Editor.Charmap.css">
  <script src="mootools.js"></script>
  <script src="Moo.UI.Editor.js"></script>
  <script src="Moo.UI.Editor.Charmap.js"></script>

  <script>
  window.addEvent('domready', function() {
    var editor = $('textarea-1').mooEditable({
      actions: 'bold italic underline strikethrough | charmap | toggleview',
      externalCSS: '../../Assets/Moo.UI.Editor/Editable.css'
    });
  });
  </script>

...
*/

Moo.UI.Editor.Actions.Settings.charmap = {
	chars: [
		['&nbsp;', '&#160;'],
		['&amp;', '&#38;'],
		['&quot;', '&#34;'],
		['&cent;', '&#162;'],
		['&euro;', '&#8364;'],
		['&pound;', '&#163;'],
		['&yen;', '&#165;'],
		['&copy;', '&#169;'],
		['&reg;', '&#174;'],
		['&trade;', '&#8482;'],
		['&permil;', '&#8240;'],
		['&micro;', '&#181;'],
		['&middot;', '&#183;'],
		['&bull;', '&#8226;'],
		['&hellip;', '&#8230;'],
		['&prime;', '&#8242;'],
		['&Prime;', '&#8243;'],
		['&sect;', '&#167;'],
		['&para;', '&#182;'],
		['&szlig;', '&#223;'],
		['&lsaquo;', '&#8249;'],
		['&rsaquo;', '&#8250;'],
		['&laquo;', '&#171;'],
		['&raquo;', '&#187;'],
		['&lsquo;', '&#8216;'],
		['&rsquo;', '&#8217;'],
		['&ldquo;', '&#8220;'],
		['&rdquo;', '&#8221;'],
		['&sbquo;', '&#8218;'],
		['&bdquo;', '&#8222;'],
		['&lt;', '&#60;'],
		['&gt;', '&#62;'],
		['&le;', '&#8804;'],
		['&ge;', '&#8805;'],
		['&ndash;', '&#8211;'],
		['&mdash;', '&#8212;'],
		['&macr;', '&#175;'],
		['&oline;', '&#8254;'],
		['&curren;', '&#164;'],
		['&brvbar;', '&#166;'],
		['&uml;', '&#168;'],
		['&iexcl;', '&#161;'],
		['&iquest;', '&#191;'],
		['&circ;', '&#710;'],
		['&tilde;', '&#732;'],
		['&deg;', '&#176;'],
		['&minus;', '&#8722;'],
		['&plusmn;', '&#177;'],
		['&divide;', '&#247;'],
		['&frasl;', '&#8260;'],
		['&times;', '&#215;'],
		['&sup1;', '&#185;'],
		['&sup2;', '&#178;'],
		['&sup3;', '&#179;'],
		['&frac14;', '&#188;'],
		['&frac12;', '&#189;'],
		['&frac34;', '&#190;'],
		['&fnof;', '&#402;'],
		['&int;', '&#8747;'],
		['&sum;', '&#8721;'],
		['&infin;', '&#8734;'],
		['&radic;', '&#8730;'],
		['&sim;', '&#8764;'],
		['&cong;', '&#8773;'],
		['&asymp;', '&#8776;'],
		['&ne;', '&#8800;'],
		['&equiv;', '&#8801;'],
		['&isin;', '&#8712;'],
		['&notin;', '&#8713;'],
		['&ni;', '&#8715;'],
		['&prod;', '&#8719;'],
		['&and;', '&#8743;'],
		['&or;', '&#8744;'],
		['&not;', '&#172;'],
		['&cap;', '&#8745;'],
		['&cup;', '&#8746;'],
		['&part;', '&#8706;'],
		['&forall;', '&#8704;'],
		['&exist;', '&#8707;'],
		['&empty;', '&#8709;'],
		['&nabla;', '&#8711;'],
		['&lowast;', '&#8727;'],
		['&prop;', '&#8733;'],
		['&ang;', '&#8736;'],
		['&acute;', '&#180;'],
		['&cedil;', '&#184;'],
		['&ordf;', '&#170;'],
		['&ordm;', '&#186;'],
		['&dagger;', '&#8224;'],
		['&Dagger;', '&#8225;'],
		['&Agrave;', '&#192;'],
		['&Aacute;', '&#193;'],
		['&Acirc;', '&#194;'],
		['&Atilde;', '&#195;'],
		['&Auml;', '&#196;'],
		['&Aring;', '&#197;'],
		['&AElig;', '&#198;'],
		['&Ccedil;', '&#199;'],
		['&Egrave;', '&#200;'],
		['&Eacute;', '&#201;'],
		['&Ecirc;', '&#202;'],
		['&Euml;', '&#203;'],
		['&Igrave;', '&#204;'],
		['&Iacute;', '&#205;'],
		['&Icirc;', '&#206;'],
		['&Iuml;', '&#207;'],
		['&ETH;', '&#208;'],
		['&Ntilde;', '&#209;'],
		['&Ograve;', '&#210;'],
		['&Oacute;', '&#211;'],
		['&Ocirc;', '&#212;'],
		['&Otilde;', '&#213;'],
		['&Ouml;', '&#214;'],
		['&Oslash;', '&#216;'],
		['&OElig;', '&#338;'],
		['&Scaron;', '&#352;'],
		['&Ugrave;', '&#217;'],
		['&Uacute;', '&#218;'],
		['&Ucirc;', '&#219;'],
		['&Uuml;', '&#220;'],
		['&Yacute;', '&#221;'],
		['&Yuml;', '&#376;'],
		['&THORN;', '&#222;'],
		['&agrave;', '&#224;'],
		['&aacute;', '&#225;'],
		['&acirc;', '&#226;'],
		['&atilde;', '&#227;'],
		['&auml;', '&#228;'],
		['&aring;', '&#229;'],
		['&aelig;', '&#230;'],
		['&ccedil;', '&#231;'],
		['&egrave;', '&#232;'],
		['&eacute;', '&#233;'],
		['&ecirc;', '&#234;'],
		['&euml;', '&#235;'],
		['&igrave;', '&#236;'],
		['&iacute;', '&#237;'],
		['&icirc;', '&#238;'],
		['&iuml;', '&#239;'],
		['&eth;', '&#240;'],
		['&ntilde;', '&#241;'],
		['&ograve;', '&#242;'],
		['&oacute;', '&#243;'],
		['&ocirc;', '&#244;'],
		['&otilde;', '&#245;'],
		['&ouml;', '&#246;'],
		['&oslash;', '&#248;'],
		['&oelig;', '&#339;'],
		['&scaron;', '&#353;'],
		['&ugrave;', '&#249;'],
		['&uacute;', '&#250;'],
		['&ucirc;', '&#251;'],
		['&uuml;', '&#252;'],
		['&yacute;', '&#253;'],
		['&thorn;', '&#254;'],
		['&yuml;', '&#255;'],
		['&Alpha;', '&#913;'],
		['&Beta;', '&#914;'],
		['&Gamma;', '&#915;'],
		['&Delta;', '&#916;'],
		['&Epsilon;', '&#917;'],
		['&Zeta;', '&#918;'],
		['&Eta;', '&#919;'],
		['&Theta;', '&#920;'],
		['&Iota;', '&#921;'],
		['&Kappa;', '&#922;'],
		['&Lambda;', '&#923;'],
		['&Mu;', '&#924;'],
		['&Nu;', '&#925;'],
		['&Xi;', '&#926;'],
		['&Omicron;', '&#927;'],
		['&Pi;', '&#928;'],
		['&Rho;', '&#929;'],
		['&Sigma;', '&#931;'],
		['&Tau;', '&#932;'],
		['&Upsilon;', '&#933;'],
		['&Phi;', '&#934;'],
		['&Chi;', '&#935;'],
		['&Psi;', '&#936;'],
		['&Omega;', '&#937;'],
		['&alpha;', '&#945;'],
		['&beta;', '&#946;'],
		['&gamma;', '&#947;'],
		['&delta;', '&#948;'],
		['&epsilon;', '&#949;'],
		['&zeta;', '&#950;'],
		['&eta;', '&#951;'],
		['&theta;', '&#952;'],
		['&iota;', '&#953;'],
		['&kappa;', '&#954;'],
		['&lambda;', '&#955;'],
		['&mu;', '&#956;'],
		['&nu;', '&#957;'],
		['&xi;', '&#958;'],
		['&omicron;', '&#959;'],
		['&pi;', '&#960;'],
		['&rho;', '&#961;'],
		['&sigmaf;', '&#962;'],
		['&sigma;', '&#963;'],
		['&tau;', '&#964;'],
		['&upsilon;', '&#965;'],
		['&phi;', '&#966;'],
		['&chi;', '&#967;'],
		['&psi;', '&#968;'],
		['&omega;', '&#969;'],
		['&alefsym;', '&#8501;'],
		['&piv;', '&#982;'],
		['&real;', '&#8476;'],
		['&thetasym;', '&#977;'],
		['&upsih;', '&#978;'],
		['&weierp;', '&#8472;'],
		['&image;', '&#8465;'],
		['&larr;', '&#8592;'],
		['&uarr;', '&#8593;'],
		['&rarr;', '&#8594;'],
		['&darr;', '&#8595;'],
		['&harr;', '&#8596;'],
		['&crarr;', '&#8629;'],
		['&lArr;', '&#8656;'],
		['&uArr;', '&#8657;'],
		['&rArr;', '&#8658;'],
		['&dArr;', '&#8659;'],
		['&hArr;', '&#8660;'],
		['&there4;', '&#8756;'],
		['&sub;', '&#8834;'],
		['&sup;', '&#8835;'],
		['&nsub;', '&#8836;'],
		['&sube;', '&#8838;'],
		['&supe;', '&#8839;'],
		['&oplus;', '&#8853;'],
		['&otimes;', '&#8855;'],
		['&perp;', '&#8869;'],
		['&sdot;', '&#8901;'],
		['&lceil;', '&#8968;'],
		['&rceil;', '&#8969;'],
		['&lfloor;', '&#8970;'],
		['&rfloor;', '&#8971;'],
		['&lang;', '&#9001;'],
		['&rang;', '&#9002;'],
		['&loz;', '&#9674;'],
		['&spades;', '&#9824;'],
		['&clubs;', '&#9827;'],
		['&hearts;', '&#9829;'],
		['&diams;', '&#9830;']
	]
};

Moo.UI.Editor.CharacterDialog = function(editor) {
	var html = 'character <select class="char">';
	var chars = Moo.UI.Editor.Actions.Settings.charmap.chars;
	for (var i=0, len=chars.length; i<len; i++) {
		html += '<option data-code="' + chars[i][0] + '">' + chars[i][1] + '</option>';
	}
	html += '</select>'
		+ '<button class="dialog-button dialog-ok-button">OK</button>'
		+ '<button class="dialog-button dialog-cancel-button">Cancel</button>';
	return new Moo.UI.Editor.Dialog(html, {
		'class': 'editor-prompt-dialog',
		onClick: function(e) {
			if (e.target.tagName.toLowerCase() == 'button') e.preventDefault();
			var button = document.id(e.target);
			if (button.hasClass('dialog-cancel-button')) {
				this.close();
			} else if (button.hasClass('dialog-ok-button')) {
				this.close();
				var sel = button.getPrevious('select.char');
				var div = new Element('div').set('html', sel.options[sel.selectedIndex].getProperty('data-code').trim());
				editor.selection.insertContent(div.get('html'));
			}
		}
	});
};

Moo.UI.Editor.Actions.extend({
	
	charmap: {
		title: 'Insert custom character',
		dialogs: {
			prompt: function(editor) {
				return Moo.UI.Editor.CharacterDialog(editor);
			}
		},
		command: function() {
			this.dialogs.charmap.prompt.open();
		},
		events: {
			toggleView: function() {
				if (this.mode == 'textarea') {
					var s = this.textarea.get('value');
					// when switching from iframe to textarea, we need to convert special symbols to html entities
					Moo.UI.Editor.Actions.Settings.charmap.chars.each(function(e) {
						if (!['&amp;', '&gt;', '&lt;', '&quot;', '&nbsp;'].contains(e[0])) {
							var r = new RegExp(String.fromCharCode(parseInt(e[1].replace('&#', '').replace(';', ''))), 'g');
							s = s.replace(r, e[0]);
						}
					}, this);
					this.textarea.set('value', s);
				}
			}
		}
	}
	
});
/*
---

script: Moo.UI.Editor.Pagebreak.js

description: Extends Moo.UI.Editor with pagebreak plugin

license: MIT-style license

authors:
- Ryan Mitchell

requires:
# - Moo.UI.Editor
# - Moo.UI.Editor
# - Moo.UI.Editor.Actions

provides: [Moo.UI.Editor.Actions.pagebreak]

usage: |
  Add the following tags in your html
  <link rel="stylesheet" href="Moo.UI.Editor.css">
  <link rel="stylesheet" href="Moo.UI.Editor.Pagebreak.css">
  <script src="mootools.js"></script>
  <script src="Moo.UI.Editor.js"></script>
  <script src="Moo.UI.Editor.Pagebreak.js"></script>

  <script>
  window.addEvent('domready', function() {
    var editor = $('textarea-1').mooEditable({
      actions: 'bold italic underline strikethrough | pagebreak | toggleview',
      externalCSS: '../../Assets/Moo.UI.Editor/Editable.css'
    });
  });
  </script>

...
*/

Moo.UI.Editor.Actions.Settings.pagebreak = {
	imageFile: '../../Assets/Moo.UI.Editor/Other/pagebreak.gif'
};

Moo.UI.Editor.Actions.extend({
	
	pagebreak: {
		title: 'Page break',
		command: function() {
			this.selection.insertContent('<img class="editor-visual-aid editor-pagebreak" />');
		},
		events: {
			beforeToggleView: function() { // code to run when switching from iframe to textarea
				if (this.mode == 'iframe') {
					var s = this.getContent().replace(/<img([^>]*)class="editor-visual-aid editor-pagebreak"([^>]*)>/gi, '<!-- page break -->');
					this.setContent(s);
				} else {
					var s = this.textarea.get('value').replace(/<!-- page break -->/gi, '<img class="editor-visual-aid editor-pagebreak" />');
					this.textarea.set('value', s);
				}
			},
			render: function() {
				this.options.moreCSS = 'img.editor-pagebreak { display:block; width:100%; height:16px; background: url('
					+ Moo.UI.Editor.Actions.Settings.pagebreak.imageFile + ') repeat-x; }'
					+ this.options.moreCSS;
			}
		}
	}
		
});

