/*
Script: MooEditable.js
	Class for creating a WYSIWYG editor, for contentEditable-capable browsers.

License:
	MIT-style license.

Copyright:
	Copyright (c) 2007-2009 [Lim Chee Aun](http://cheeaun.com).
	
Build: %build%

Credits:
	- Code inspired by Stefan's work [Safari Supports Content Editing!](http://www.xs4all.nl/~hhijdra/stefan/ContentEditable.html) from [safari gets contentEditable](http://walkah.net/blog/walkah/safari-gets-contenteditable)
	- Main reference from Peter-Paul Koch's [execCommand compatibility](http://www.quirksmode.org/dom/execCommand.html)
	- Some ideas and code inspired by [TinyMCE](http://tinymce.moxiecode.com/)
	- Some functions inspired by Inviz's [Most tiny wysiwyg you ever seen](http://forum.mootools.net/viewtopic.php?id=746), [mooWyg (Most tiny WYSIWYG 2.0)](http://forum.mootools.net/viewtopic.php?id=5740)
	- Some regex from Cameron Adams's [widgEditor](http://widgeditor.googlecode.com/)
	- Some code from Juan M Martinez's [jwysiwyg](http://jwysiwyg.googlecode.com/)
	- Some reference from MoxieForge's [PunyMCE](http://punymce.googlecode.com/)
	- IE support referring Robert Bredlau's [Rich Text Editing](http://www.rbredlau.com/drupal/node/6)
	- Tango icons from the [Tango Desktop Project](http://tango.freedesktop.org/)
	- Additional Tango icons from Jimmacs' [Tango OpenOffice](http://www.gnome-look.org/content/show.php/Tango+OpenOffice?content=54799)
*/

var MooEditable = new Class({

	Implements: [Events, Options],

	options: {
		toolbar: true,
		cleanup: true,
		paragraphise: true,
		xhtml : true,
		semantics : true,
		actions: 'bold italic underline strikethrough | insertunorderedlist insertorderedlist indent outdent | undo redo | createlink unlink | urlimage | toggleview',
		handleSubmit: true,
		handleLabel: true,
		baseCSS: 'html{ height: 100%; cursor: text }\
			body{ font-family: sans-serif; border: 0; }',
		extraCSS: '',
		externalCSS: '',
		html: '<html>\
			<head>\
			<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">\
			<style>{BASECSS} {EXTRACSS}</style>\
			{EXTERNALCSS}\
			</head>\
			<body>{CONTENT}</body>\
			</html>'
	},

	initialize: function(el, options){
		this.setOptions(options);
		this.textarea = document.id(el);
		this.textarea.store('MooEditable', this);
		this.actions = this.options.actions.clean().split(' ');
		this.keys = {};
		this.dialogs = {};
		this.actions.each(function(action){
			var act = MooEditable.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();
	},
	
	toElement: function(){
		return this.textarea;
	},
	
	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 + '-mooeditable-container' : null,
			'class': 'mooeditable-container',
			styles: {
				width: dimensions.x
			}
		});

		// Override all textarea styles
		this.textarea.addClass('mooeditable-textarea').setStyle('height', dimensions.y);
		
		// Build the iframe
		this.iframe = new IFrame({
			'class': 'mooeditable-iframe',
			styles: {
				height: dimensions.y
			}
		});
		
		this.toolbar = new MooEditable.UI.Toolbar({
			onItemAction: function(){
				var args = $splat(arguments);
				var item = args[0];
				self.action(item.name, args);
			}
		});
		this.attach();
		
		// 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);
	},

	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.extraCSS,
			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 keyboard shortcuts
		this.doc.addEvents({
			mouseup: this.editorMouseUp.bind(this),
			mousedown: this.editorMouseDown.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)
		});
		this.textarea.addEvent('keypress', this.textarea.retrieve('mooeditable: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);
		}

		this.selection = new MooEditable.Selection(this.win);
		
		this.fireEvent('attach', this);
		
		return this;
	},
	
	detach: function(){
		this.saveContent();
		this.textarea.setStyle('display', '').removeClass('mooeditable-textarea').inject(this.container, 'before');
		this.textarea.removeEvent('keypress', this.textarea.retrieve('mooeditable:textareaKeyListener'));
		this.container.dispose();
		this.fireEvent('detach', this);
		return this;
	},
	
	editorMouseUp: function(e){
		if (this.editorDisabled){
			e.stop();
			return;
		}
		
		if (this.options.toolbar) this.checkStates();
		
		this.fireEvent('editorMouseUp', e);
	},
	
	editorMouseDown: function(e){
		if (this.editorDisabled){
			e.stop();
			return;
		}
		
		this.fireEvent('editorMouseDown', e);
	},
	
	editorContextMenu: function(e){
		if (this.editorDisabled){
			e.stop();
			return;
		}
		
		this.fireEvent('editorContextMenu', e);
	},
	
	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);
	},
	
	editorDoubleClick: function(e){
		this.fireEvent('editorDoubleClick', e);
	},
	
	editorKeyPress: function(e){
		if (this.editorDisabled){
			e.stop();
			return;
		}
		
		this.keyListener(e);
		
		this.fireEvent('editorKeyPress', e);
	},
	
	editorKeyUp: function(e){
		if (this.editorDisabled){
			e.stop();
			return;
		}
		
		if (this.options.toolbar) this.checkStates();
		
		this.fireEvent('editorKeyUp', e);
	},
	
	editorKeyDown: function(e){
		if (this.editorDisabled){
			e.stop();
			return;
		}
		
		if (e.key == 'enter'){
			if (this.options.paragraphise && !e.shift){
				if (Browser.Engine.gecko || Browser.Engine.webkit){
					var node = this.selection.getNode();
					var blockEls = /^(H[1-6]|P|DIV|ADDRESS|PRE|FORM|TABLE|LI|OL|UL|TD|CAPTION|BLOCKQUOTE|CENTER|DL|DT|DD)$/;
					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 (node.get('tag') != 'li'){
						if (r){
							this.selection.insertContent('<br>');
							this.selection.collapse(false);
						}
					}
					e.preventDefault();
				}
			}
		}
		
		this.fireEvent('editorKeyDown', e);
	},
	
	keyListener: function(e){
		if (!e.control || !this.keys[e.key]) return;
		e.preventDefault();
		var item = this.toolbar.getItem(this.keys[e.key]);
		item.action(e);
	},

	focus: function(){
		// needs the delay to get focus working
		(function(){ 
			(this.mode == 'iframe' ? this.win : this.textarea).focus();
			this.fireEvent('focus', this);
		}).bind(this).delay(10);
		return this;
	},

	action: function(command, args){
		var action = MooEditable.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();
		}
	},

	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;
	},

	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();
		return this;
	},

	getContent: function(){
		return this.cleanup(this.doc.body.get('html'));
	},

	setContent: function(newContent){
		this.doc.body.set('html', newContent);
		return this;
	},

	saveContent: function(){
		if (this.mode == 'iframe') this.textarea.set('value', this.getContent());
		return this;
	},

	checkStates: function(){
		this.actions.each(function(action){
			var item = this.toolbar.getItem(action);
			if (!item) return;
			item.deactivate();

			var states = MooEditable.Actions[action]['states'];
			if (!states) return;
			
			var el = this.selection.getNode();
			if (!el) return;
			
			// custom checkState
			if ($type(states) == 'function'){
				states.attempt(el, item);
				return;
			}
			
			if (states.tags){
				do {
					if ($type(el) != 'element') break;
					var tag = el.tagName.toLowerCase();
					if (states.tags.contains(tag)){
						item.activate(tag);
						break;
					}
				}
				while (el = el.parentNode);
			}

			if (states.css){
				var blockEls = /^(H[1-6]|P|DIV|ADDRESS|PRE|FORM|TABLE|LI|OL|UL|TD|CAPTION|BLOCKQUOTE|CENTER|DL|DT|DD)$/;
				do {
					if ($type(el) != 'element') break;
					var found = false;
					for (var prop in states.css){
						var css = states.css[prop];
						if (document.id(el).getStyle(prop).contains(css)){
							item.activate(css);
							found = true;
						}
					}
					if (found || el.tagName.test(blockEls)) break;
				}
				while (el = el.parentNode);
			}
		}.bind(this));
	},

	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>');

			// 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;
			});

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

			//make img tags xhtml compatable
			//           if (this.options.xhtml){
			//                source = source.replace(/(<(?:img|input)[^/>]*)>/g, '$1 />');
			//           }

			//remove double <p> tags and empty <p> tags
			source = source.replace(/<p>(?:\s*)<p>/g, '<p>');
			source = source.replace(/<\/p>\s*<\/p>/g, '</p>');
			source = source.replace(/<p>\W*<\/p>/g, '');

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

		return source;
	}

});

MooEditable.Selection = new Class({

	initialize: function(win){
		this.win = win;
	},

	getSelection: function(){
		this.win.focus();
		return (this.win.getSelection) ? this.win.getSelection() : this.win.document.selection;
	},

	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();
		}
	},

	setRange: function(range){
		if (range.select){
			$try(function(){
				range.select();
			});
		} else {
			var s = this.getSelection();
			if (s.addRange){
				s.removeAllRanges();
				s.addRange(range);
			}
		}
	},

	selectNode: function(node, collapse){
		var r = this.getRange();
		var s = this.getSelection();

		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;
	},

	isCollapsed: function(){
		var r = this.getRange();
		if (r.item) return false;
		return r.boundingWidth == 0 || this.getSelection().isCollapsed;
	},

	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();
		}
	},

	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;
	},

	getText : function(){
		var r = this.getRange();
		var s = this.getSelection();

		return this.isCollapsed() ? '' : r.text || s.toString();
	},

	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());
	},

	insertContent: function(content){
		var r = this.getRange();

		if (r.insertNode){
			r.deleteContents();
			r.insertNode(r.createContextualFragment(content));
		} else {
			// Handle text and control range
			(r.pasteHTML) ? r.pasteHTML(content) : r.item(0).outerHTML = content;
		}
	}

});

MooEditable.UI = {};

MooEditable.UI.Toolbar= new Class({

	Implements: [Events, Options],

	options: {
		/*
		onItemAction: $empty,
		*/
		'class': ''
	},
    
	initialize: function(options){
		this.setOptions(options);
		this.el = new Element('div', {'class': 'mooeditable-ui-toolbar ' + this.options['class']});
		this.items = {};
		this.content = null;
	},
	
	toElement: function(){
		return this.el;
	},
	
	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;
	},
	
	addItem: function(action){
		var self = this;
		var act = MooEditable.Actions[action];
		if (!act) return;
		var type = act.type || 'button';
		var options = act.options || {};
		var item = new MooEditable.UI[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;
	},
	
	getItem: function(action){
		return this.items[action];
	},
	
	addSeparator: function(){
		return new Element('span', {'class': 'toolbar-separator'}).inject(this.el);
	},
	
	itemAction: function(){
		this.fireEvent('itemAction', arguments);
	},

	disable: function(except){
		$each(this.items, function(item){
			(item.name == except) ? item.activate() : item.deactivate().disable();
		});
		return this;
	},

	enable: function(){
		$each(this.items, function(item){
			item.enable();
		});
		return this;
	},
	
	show: function(){
		this.el.setStyle('display', '');
		return this;
	},
	
	hide: function(){
		this.el.setStyle('display', 'none');
		return this;
	}
	
});

MooEditable.UI.Button = new Class({

	Implements: [Events, Options],

	options: {
		/*
		onAction: $empty,
		*/
		title: '',
		name: '',
		text: 'Button',
		'class': '',
		shortcut: '',
		mode: 'icon'
	},

	initialize: function(options){
		this.setOptions(options);
		this.name = this.options.name;
		this.render();
	},
	
	toElement: function(){
		return this.el;
	},
	
	render: function(){
		var self = this;
		var shortcut = (this.options.shortcut) ? ' ( Ctrl+' + this.options.shortcut.toUpperCase() + ' )' : '';
		var text = this.options.title || name;
		var title = text + shortcut;
		this.el = new Element('button', {
			'class': 'mooeditable-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('mooeditable-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;
	},
	
	click: function(e){
		e.preventDefault();
		if (this.disabled) return;
		this.action(e);
	},
	
	action: function(){
		this.fireEvent('action', [this].concat($A(arguments)));
	},
	
	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;
	},
	
	disable: function(){
		if (this.disabled) return;
		this.disabled = true;
		this.el.addClass('disabled').set({
			disabled: true,
			opacity: 0.4
		});
		return this;
	},
	
	activate: function(){
		if (this.disabled) return;
		this.active = true;
		this.el.addClass('onActive');
		return this;
	},
	
	deactivate: function(){
		this.active = false;
		this.el.removeClass('onActive');
		return this;
	}
	
});

MooEditable.UI.Dialog = new Class({

	Implements: [Events, Options],

	options:{
		/*
		onOpen: $empty,
		onClose: $empty,
		*/
		'class': '',
		contentClass: ''
	},

	initialize: function(html, options){
		this.setOptions(options);
		this.html = html;
		
		var self = this;
		this.el = new Element('div', {
			'class': 'mooeditable-ui-dialog ' + self.options['class'],
			html: '<div class="dialog-content ' + self.options.contentClass + '">' + html + '</div>',
			styles: {
				'display': 'none'
			},
			events: {
				click: self.click.bind(self)
			}
		});
	},
	
	toElement: function(){
		return this.el;
	},
	
	click: function(){
		this.fireEvent('click', arguments);
		return this;
	},
	
	open: function(){
		this.el.setStyle('display', '');
		this.fireEvent('open', this);
		return this;
	},
	
	close: function(){
		this.el.setStyle('display', 'none');
		this.fireEvent('close', this);
		return this;
	}

});

MooEditable.UI.AlertDialog = function(alertText){
	if (!alertText) return;
	var html = alertText + ' <button class="dialog-ok-button">OK</button>';
	return new MooEditable.UI.Dialog(html, {
		'class': 'mooeditable-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();
		}
	});
};

MooEditable.UI.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 MooEditable.UI.Dialog(html, {
		'class': 'mooeditable-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);
			}
		}
	});
};

MooEditable.Actions = new Hash({

	bold: {
		title: 'Bold',
		options: {
			shortcut: 'b'
		},
		states: {
			tags: ['b', 'strong'],
			css: {'font-weight': 'bold'}
		}
	},
	
	italic: {
		title: 'Italic',
		options: {
			shortcut: 'i'
		},
		states: {
			tags: ['i', 'em'],
			css: {'font-style': 'italic'}
		}
	},
	
	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: MooEditable.UI.AlertDialog.pass('Please select the text you wish to hyperlink.'),
			prompt: function(editor){
				return MooEditable.UI.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 url = /^(https?|ftp|rmtp|mms):\/\/(([A-Z0-9][A-Z0-9_-]*)(\.[A-Z0-9][A-Z0-9_-]*)+)(:(\d+))?\/?/i;
				var prompt = this.dialogs.createlink.prompt;
				if (url.test(text)) prompt.el.getElement('.mooeditable-dialog-input').set('value', text);
				prompt.open();
			}
		}
	},

	urlimage: {
		title: 'Add Image',
		options: {
			shortcut: 'm'
		},
		dialogs: {
			prompt: function(editor){
				return MooEditable.UI.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();
		}
	}

});

Element.Properties.mooeditable = {

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

	get: function(options){
		if (options || !this.retrieve('mooeditable')){
			if (options || !this.retrieve('mooeditable:options')) this.set('mooeditable', options);
			this.store('mooeditable', new MooEditable(this, this.retrieve('mooeditable:options')));
		}
		return this.retrieve('mooeditable');
	}

};

Element.implement({

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

});/*
Script: MooEditable.Group.js
	A MooEditable extension for having multiple MooEditable instances on a page controlled by one toolbar.

License:
	MIT-style license.
*/

MooEditable.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 MooEditable.UI.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 MooEditable.Group.Item(textarea, this, $merge({toolbar: false}, this.options, options));
	}
	
});


MooEditable.Group.Item = new Class({

	Extends: MooEditable,

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

});/*
Script: MooEditable.Extras.js
	Extends MooEditable to include more (simple) toolbar buttons.

License:
	MIT-style license.
*/

MooEditable.Actions.extend({

	formatBlock: {
		title: 'Block Formatting',
		type: 'menu-list',
		options: {
			list: [
				{text: 'Paragraph', value: 'p'},
				{text: 'Heading 1', value: 'h1'},
				{text: 'Heading 2', value: 'h2'},
				{text: 'Heading 3', value: 'h3'}
			]
		},
		states: {
			tags: ['p', 'h1', 'h2', 'h3']
		},
		command: function(menulist, name){
			var argument = '<' + name + '>';
			this.execute('formatBlock', false, argument);
			this.focus();
		}
	},
	
	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'}
		}
	}

});
/*
Script: MooEditable.UI.ButtonOverlay.js
	UI Class to create a button element with a popup overlay.

License:
	MIT-style license.

Copyright:
	Copyright (c) 2007-2009 [Lim Chee Aun](http://cheeaun.com).
*/

MooEditable.UI.ButtonOverlay = new Class({

	Extends: MooEditable.UI.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('mooeditable-ui-buttonOverlay');
		this.renderOverlay(this.options.overlayHTML);
	},
	
	renderOverlay: function(html){
		var self = this;
		this.overlay = new Element('div', {
			'class': 'mooeditable-ui-button-overlay ' + self.name + '-overlay ' + self.options.overlayClass,
			html: '<div class="overlay-content ' + self.options.overlayContentClass + '">' + html + '</div>',
			styles: {
				display: 'none',
				position: 'absolute',
				width: self.options.overlaySize.x,
				height: self.options.overlaySize.y
			},
			events: {
				click: self.clickOverlay.bind(self)
			}
		}).inject(document.body).store('MooEditable.UI.ButtonOverlay', this);
		this.overlayVisible = false;
		window.addEvent('click', function(e){
			var el = e.target;
			if (el == self.el) return;
			if (Element.hasClass(el, self.name + '-overlay')) return;
			if (Element.getParents(el, '.' + self.name + '-overlay').length) return;
			self.closeOverlay();
		});
	},
	
	openOverlay: function(){
		if (this.overlayVisible) return;
		this.overlay.setStyle('display', '');
		this.overlayVisible = true;
		this.activate();
		this.fireEvent('openOverlay', this);
		return this;
	},
	
	closeOverlay: function(){
		if (!this.overlayVisible) return;
		this.overlay.setStyle('display', 'none');
		this.overlayVisible = false;
		this.deactivate();
		this.fireEvent('closeOverlay', this);
		return this;
	},
	
	clickOverlay: function(e){
		e.preventDefault();
		this.action(e);
		this.closeOverlay();
	},
	
	click: function(e){
		e.preventDefault();
		if (this.disabled) return;
		if (this.overlayVisible){
			this.closeOverlay();
			return;
		} else {
			var coords = this.el.getCoordinates();
			this.overlay.setStyles({
				top: coords.bottom,
				left: coords.left
			});
			this.openOverlay();
		}
	}
	
});/*
Script: MooEditable.UI.MenuList.js
	UI Class to create a menu list (select) element.

License:
	MIT-style license.

Copyright:
	Copyright (c) 2007-2009 [Lim Chee Aun](http://cheeaun.com).
*/

MooEditable.UI.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}">{text}</option>'.substitute(item);
		});
		this.el = new Element('select', {
			'class': self.options['class'],
			title: self.options.title,
			html: html,
			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;
	}
	
});/*
---

script: moogressBar.js
version: 0.5.1
description: with moogressBar you can easily create a progress bar powered by mooTools
license: MIT-style
authors:
- Christopher Beloch
- Arian Stolwijk (code improvements 0.2 -> 0.3)

requires: 
  core:1.2.4: '*'

provides: [Moogressbar]

...
*/

var MoogressBar = new Class({

	Implements: [Options, Events],
	
	options: {
		bgImage: 'blue.gif',  // What is the background-image?
		percentage: 0,  // Start at which percentage?
		height: 10,  // Height of the bar
		hide: true, // Hide the bar on 100%?
		fx: { // The effects for the scroll, set to null or false if you don't want this effect
			unit: '%',
			duration: 'normal',
			property: 'width'
		} /*,
		onChange: $empty,
		onFinish: $empty
		*/
	},
	
	initialize: function(parent,options){
		this.setOptions(options);
		this.parent = document.id(parent)
			.setStyle('z-index',999);
		
		// Set the current percentage
		this.current = this.options.percentage;
		
		// Draw bar
		this.bar = new Element('div', {
			'styles': {
				display: 'block',
				width: this.options.percentage + '%',
				height: this.options.height,
				'background-image': 'url(' + this.options.bgImage + ')'/*,
				// Border Radius deactivated, because Firefox is causing drawing problems
				'border-radius': '5px',
				'-webkit-border-radius': '5px',
				'-moz-border-radius': '5px'*/
			}
		}).inject(parent);
		
		// Will it be Animated?
		if(this.options.fx)
			this.fx = new Fx.Tween(this.bar, this.options.fx);
	},
	
	// function to modify the percentage status
	setPercentage: function(percentage){
		
		if(this.fx){
			// Fire the events when the fx is complete
			this.fx.addEvent('complete',function(){
				if(percentage >= 100){
					this.fireEvent('finish');
					
					// hide bar
					if(this.options.hide){
						this.parent.tween('opacity', 0).tween('width', 0).tween('height', 0);
						this.fx.set('opacity', 0);
					}
				}
				this.fireEvent('change',percentage);
			}.bind(this));
		}else{
			// Fire the events immediately when there's no fx
			this.fireEvent('change',percentage);
			if(percentage >= 100){
				this.fireEvent('finish');
				
				if(this.options.hide){
					this.parent.setStyle('display', 'none');
				}
			}
		}

		// Change the percentage bar
		if(this.fx) {
			this.fx.cancel().start(this.bar.getStyle('width').toInt(), percentage);
		} else {
			this.bar.setStyle('width', percentage + '%');
		}

		// Change the current percentage
		this.current = percentage;
	},
	
	getPercentage: function(){
		return this.current;
	},
	
	toElement: function(){
		return this.parent;
	},

	increasePercentage: function(percentage) {
		this.setPercentage(this.current + percentage);
	}
});
/**
 * Roar - Notifications
 *
 * Inspired by Growl
 *
 * @version		1.0.1
 *
 * @license		MIT-style license
 * @author		Harald Kirschner <mail [at] digitarald.de>
 * @copyright	Author
 */

var Roar = new Class({

	Implements: [Options, Events, Chain],

	options: {
		duration: 3000,
		position: 'upperLeft',
		container: null,
		bodyFx: null,
		itemFx: null,
		margin: {x: 10, y: 10},
		offset: 10,
		className: 'roar',
		onShow: $empty,
		onHide: $empty,
		onRender: $empty
	},

	initialize: function(options) {
		this.setOptions(options);
		this.items = [];
		this.container = document.id(this.options.container) || document;
	},

	alert: function(title, message, options) {
		var params = Array.link(arguments, {title: String.type, message: String.type, options: Object.type});
		var items = [new Element('h3', {'html': $pick(params.title, '')})];
		if (params.message) items.push(new Element('p', {'html': params.message}));
		return this.inject(items, params.options);
	},

	inject: function(elements, options) {
		if (!this.body) this.render();
		options = options || {};

		var offset = [-this.options.offset, 0];
		var last = this.items.getLast();
		if (last) {
			offset[0] = last.retrieve('roar:offset');
			offset[1] = offset[0] + last.offsetHeight + this.options.offset;
		}
		var to = {'opacity': 1};
		to[this.align.y] = offset;

		var item = new Element('div', {
			'class': this.options.className,
			'opacity': 0
		}).adopt(
			new Element('div', {
				'class': 'roar-bg',
				'opacity': 0.7
			}),
			elements
		);

		item.setStyle(this.align.x, 0).store('roar:offset', offset[1]).set('morph', $merge({
			unit: 'px',
			link: 'cancel',
			onStart: Chain.prototype.clearChain,
			transition: Fx.Transitions.Back.easeOut
		}, this.options.itemFx));

		var remove = this.remove.create({
			bind: this,
			arguments: [item],
			delay: 10
		});
		this.items.push(item.addEvent('click', remove));

		if (this.options.duration) {
			var over = false;
			var trigger = (function() {
				trigger = null;
				if (!over) remove();
			}).delay(this.options.duration);
			item.addEvents({
				mouseover: function() {
					over = true;
				},
				mouseout: function() {
					over = false;
					if (!trigger) remove();
				}
			});
		}
		item.inject(this.body).morph(to);
		return this.fireEvent('onShow', [item, this.items.length]);
	},

	remove: function(item) {
		var index = this.items.indexOf(item);
		if (index == -1) return this;
		this.items.splice(index, 1);
		item.removeEvents();
		var to = {opacity: 0};
		to[this.align.y] = item.getStyle(this.align.y).toInt() - item.offsetHeight - this.options.offset;
		item.morph(to).get('morph').chain(item.destroy.bind(item));
		return this.fireEvent('onHide', [item, this.items.length]).callChain(item);
	},

	empty: function() {
		while (this.items.length) this.remove(this.items[0]);
		return this;
	},

	render: function() {
		this.position = this.options.position;
		if ($type(this.position) == 'string') {
			var position = {x: 'center', y: 'center'};
			this.align = {x: 'left', y: 'top'};
			if ((/left|west/i).test(this.position)) position.x = 'left';
			else if ((/right|east/i).test(this.position)) this.align.x = position.x = 'right';
			if ((/upper|top|north/i).test(this.position)) position.y = 'top';
			else if ((/bottom|lower|south/i).test(this.position)) this.align.y = position.y = 'bottom';
			this.position = position;
		}
		this.body = new Element('div', {'class': 'roar-body'}).inject(document.body);
		if (Browser.Engine.trident4) this.body.addClass('roar-body-ugly');
		this.moveTo = this.body.setStyles.bind(this.body);
		this.reposition();
		if (this.options.bodyFx) {
			var morph = new Fx.Morph(this.body, $merge({
				unit: 'px',
				chain: 'cancel',
				transition: Fx.Transitions.Circ.easeOut
			}, this.options.bodyFx));
			this.moveTo = morph.start.bind(morph);
		}
		var repos = this.reposition.bind(this);
		window.addEvents({
			scroll: repos,
			resize: repos
		});
		this.fireEvent('onRender', this.body);
	},

	reposition: function() {
		var max = document.getCoordinates(), scroll = document.getScroll(), margin = this.options.margin;
		max.left += scroll.x;
		max.right += scroll.x;
		max.top += scroll.y;
		max.bottom += scroll.y;
		var rel = ($type(this.container) == 'element') ? this.container.getCoordinates() : max;
		this.moveTo({
			left: (this.position.x == 'right')
				? (Math.min(rel.right, max.right) - margin.x)
				: (Math.max(rel.left, max.left) + margin.x),
			top: (this.position.y == 'bottom')
				? (Math.min(rel.bottom, max.bottom) - margin.y)
				: (Math.max(rel.top, max.top) + margin.y)
		});
	}

});/*! Sly v1.0rc2 <http://sly.digitarald.com> - (C) 2009 Harald Kirschner <http://digitarald.de> - Open source under MIT License */

/**
 * Credits
 * 
 * Sly's code and pattern are inspired by several open source developers.
 * 
 * Valerio Proietti & MooTools contributors
 *  - Idea of modular combinator and pseudo filters
 *  - Code for several pseudo filters
 *  - Slickspeed benchmark framework
 * Steven Levithan
 *  - Improved Sly.parse expression
 * Diego Perini
 *  - Research on querySelectorAll and browser quirks
 *  - Patches for Sly.parse expression
 *  - Combined tests from jQuery and Prototype
 * Thomas Aylott & Slick contributors
 *   - Idea of using regular expressions in attribute filter.
 * John Resig & jQuery/Sizzle contributors
 *  - Browser feature/quirks detection
 *  - Additional pseudo filters
 *  - Extensive Unit Tests, (c) 2008 John Resig, JÃ¶rn Zaefferer, MIT/GPL dual license
 * Sam Stephenson & Prototype contributors
 *  - Extensive Unit Tests, (c) 2005-2008 Sam Stephenson, MIT-style license
 * Alan Kang & JSSpec contributors
 *  - JSSpec BDD framework
 * 
 * Kudos to every single one of them for supporting the open web.
 */

var Sly = (function() {

var cache = {};

/**
 * Sly::constructor
 * 
 * Acts also as shortcut for Sly::search if context argument is given.
 */
var Sly = function(text, context, results, options) {
	// normalise
	text = (typeof(text) == 'string') ? text.replace(/^\s+|\s+$/, '') : '';
	
	var cls = cache[text] || (cache[text] = new Sly.initialize(text));
	return (context == null) ? cls : cls.search(context, results, options);
};

Sly.initialize = function(text) {
	this.text = text;
};

var proto = Sly.initialize.prototype = Sly.prototype;


/**
 * Sly.implement
 */
Sly.implement = function(key, properties) {
	for (var prop in properties) Sly[key][prop] = properties[prop];
};


/**
 * Sly.support
 *
 * Filled with experiment results.
 */
var support = Sly.support = {};

// Checks similar to NWMatcher, Sizzle
(function() {
	
	// Our guinea pig
	var testee = document.createElement('div'), id = (new Date()).getTime();
	testee.innerHTML = '<a name="' + id + '" class="â‚¬ b"></a>';
	testee.appendChild(document.createComment(''));
	
	// IE returns comment nodes for getElementsByTagName('*')
	support.byTagAddsComments = (testee.getElementsByTagName('*').length > 1);
	
	// Safari can't handle uppercase or unicode characters when in quirks mode.
	support.hasQsa = !!(testee.querySelectorAll && testee.querySelectorAll('.â‚¬').length);
	
	support.hasByClass = (function() {
		if (!testee.getElementsByClassName || !testee.getElementsByClassName('b').length) return false;
		testee.firstChild.className = 'c';
		return (testee.getElementsByClassName('c').length == 1);
	})();
	
	var root = document.documentElement;
	root.insertBefore(testee, root.firstChild);
	
	// IE returns named nodes for getElementById(name)
	support.byIdAddsName = !!(document.getElementById(id));
	
	root.removeChild(testee);
	
})();


var locateFast = function() {
	return true;
};

/**
 * Sly::search
 */
proto.search = function(context, results, options) {
	options = options || {};
	
	var iterate, i, item;

	if (!context) {
		context = document;
	} else if (context.nodeType != 1 && context.nodeType != 9) {
		if (typeof(context) == 'string') {
			context = Sly.search(context);
			iterate = true;
		} else if (Object.prototype.toString.call(context) == '[object Array]' || (typeof(context.length) == 'number' && context.item)) { // simple isArray
			var filtered = [];
			for (i = 0; (item = context[i]); i++) {
				if (item.nodeType == 1 || item.nodeType == 9) filtered.push(item);
			}
			iterate = (filtered.length > 1);
			context = (iterate) ? filtered : (filtered[0] || document);
		}
	}
	
	var mixed, // results need to be sorted, comma
		combined, // found nodes from one iteration process
		nodes, // context nodes from one iteration process
		all = {}, // unique ids for overall result
		state = {}; // matchers temporary state
	var current = all; // unique ids for one iteration process

	// unifiers
	var getUid = Sly.getUid;
	var locateCurrent = function(node) {
		var uid = getUid(node);
		return (current[uid]) ? null : (current[uid] = true);
	};
	
	if (results && results.length) { // fills unique ids, does not alter the given results
		for (i = 0; (item = results[i]); i++) locateCurrent(item);
	}

	if (support.hasQsa && !iterate && context.nodeType == 9 && !(/\[/).test(this.text)) {
		try {
			var query = context.querySelectorAll(this.text);
		} catch(e) {}
		if (query) {
			if (!results) return Sly.toArray(query);
			for (i = 0; (item = query[i]); i++) {
				if (locateCurrent(item)) results.push(item);
			}
			if (!options.unordered) results.sort(Sly.compare);
			return results;
		}
	}

	var parsed = this.parse();
	if (!parsed.length) return [];

	for (var i = 0, selector; (selector = parsed[i]); i++) {

		var locate = locateCurrent;

		if (selector.first) {
			if (!results) locate = locateFast;
			else mixed = true;
			if (iterate) nodes = context;
			else if (selector.combinator) nodes = [context]; // allows combinators before selectors
		}

		if (selector.last && results) {
			current = all;
			combined = results;
		} else {
			// default stack
			current = {};
			combined = [];
		}

		if (!selector.combinator && !iterate) {
			// without prepended combinator
			combined = selector.combine(combined, context, selector, state, locate, !(combined.length));
		} else {
			// with prepended combinators
			for (var k = 0, l = nodes.length; k < l; k++) {
				combined = selector.combine(combined, nodes[k], selector, state, locate);
			}
		}

		if (selector.last) {
			if (combined.length) results = combined;
		} else {
			nodes = combined;
		}
	}

	if (!options.unordered && mixed && results) results.sort(Sly.compare);

	return results || [];
};

/**
 * Sly::find
 */
proto.find = function(context, results, options) {
	return this.search(context, results, options)[0];
};


/**
 * Sly::match
 */
proto.match = function(node, parent) {
	var parsed = this.parse();
	if (parsed.length == 1) return !!(this.parse()[0].match(node, {}));
	if (!parent) {
		parent = node;
		while (parent.parentNode) parent = parent.parentNode
	}
	var found = this.search(parent), i = found.length;
	if (!i--) return false;
	while (i--) {
		if (found[i] == node) return true;
	}
	return false;
};


/**
 * Sly::filter
 */
proto.filter = function(nodes) {
	var results = [], match = this.parse()[0].match;
	for (var i = 0, node; (node = nodes[i]); i++) {
		if (match(node)) results.push(node);
	}
	return results;
};


/**
 * Sly.recompile()
 */
var pattern;

Sly.recompile = function() {

	var key, combList = [','], operList = ['!'];

	for (key in combinators) {
		if (key != ' ') {
			combList[(key.length > 1) ? 'unshift' : 'push'](Sly.escapeRegExp(key));
		}
	}
	for (key in operators) operList.push(key);

	/**
		The regexp is a group of every possible selector part including combinators.
		"|" separates the possible selectors.

		Capturing parentheses:
		1 - Combinator (only requires to allow multiple-character combinators)
		2 - Attribute name
		3 - Attribute operator
		4, 5, 6 - The value
		7 - Pseudo name
		8, 9, 10 - The value
	 */

	pattern = new RegExp(
		// A tagname
		'[\\w\\u00a1-\\uFFFF][\\w\\u00a1-\\uFFFF-]*|' +

		// An id or the classname
		'[#.](?:[\\w\\u00a1-\\uFFFF-]|\\\\:|\\\\.)+|' +

		// Whitespace (descendant combinator)
		'[ \\t\\r\\n\\f](?=[\\w\\u00a1-\\uFFFF*#.[:])|' +

		// Other combinators and the comma
		'[ \\t\\r\\n\\f]*(' + combList.join('|') + ')[ \\t\\r\\n\\f]*|' +

		// An attribute, with the various and optional value formats ([name], [name=value], [name="value"], [name='value']
		'\\[([\\w\\u00a1-\\uFFFF-]+)[ \\t\\r\\n\\f]*(?:([' + operList.join('') + ']?=)[ \\t\\r\\n\\f]*(?:"([^"]*)"|\'([^\']*)\'|([^\\]]*)))?]|' +

		// A pseudo-class, with various formats
		':([-\\w\\u00a1-\\uFFFF]+)(?:\\((?:"([^"]*)"|\'([^\']*)\'|([^)]*))\\))?|' +

		// The universial selector, not process
		'\\*|(.+)', 'g'
	);
};


// I prefer it outside, not sure if this is faster
var create = function(combinator) {
	return {
		ident: [],
		classes: [],
		attributes: [],
		pseudos: [],
		combinator: combinator
	};
};

var blank = function($0) {
	return $0;
};

/**
 * Sly::parse
 *
 * Returns an array with one object for every selector:
 *
 * {
 *   tag: (String) Tagname (defaults to null for universal *)
 *   id: (String) Id
 *   classes: (Array) Classnames
 *   attributes: (Array) Attribute objects with "name", "operator" and "value"
 *   pseudos: (Array) Pseudo objects with "name" and "value"
 *   operator: (Char) The prepended operator (not comma)
 *   first: (Boolean) true if it is the first selector or the first after a comma
 *   last: (Boolean) true if it is the last selector or the last before a comma
 *   ident: (Array) All parsed matches, can be used as cache identifier.
 * }
 */
proto.parse = function(plain) {
	var save = (plain) ? 'plain' : 'parsed';
	if (this[save]) return this[save];
	
	var text = this.text;
	var compute = (plain) ? blank : this.compute;

	var parsed = [], current = create(null);
	current.first = true;

	var refresh = function(combinator) {
		parsed.push(compute(current));
		current = create(combinator);
	};

	pattern.lastIndex = 0; // to fix some weird behavior
	var match, $0;
	
	while ((match = pattern.exec(text))) {
		
		if (match[11]) {
			if (Sly.verbose) throw SyntaxError('Syntax error, "' + $0 + '" unexpected at #' + pattern.lastIndex + ' in "' + text + '"');
			return (this[save] = []);
		}
		
		$0 = match[0];
		
		switch ($0.charAt(0)) {
			case '.':
				current.classes.push($0.slice(1).replace(/\\/g, ''));
				break;
			case '#':
				current.id = $0.slice(1).replace(/\\/g, '');
				break;
			case '[':
				current.attributes.push({
					name: match[2],
					operator: match[3] || null,
					value: match[4] || match[5] || match[6] || null
				});
				break;
			case ':':
				current.pseudos.push({
					name: match[7],
					value: match[8] || match[9] || match[10] || null
				});
				break;
			case ' ': case '\t': case '\r': case '\n': case '\f':
				match[1] = match[1] || ' ';
			default:
				var combinator = match[1];
				if (combinator) {
					if (combinator == ',') {
						current.last = true;
						refresh(null);
						current.first = true;
						continue;
					}
					if (current.first && !current.ident.length) current.combinator = combinator;
					else refresh(combinator);
				} else {
					if ($0 != '*') current.tag = $0;
				}
		}
		current.ident.push($0);
	}

	current.last = true;
	parsed.push(compute(current));

	return (this[save] = parsed);
};


// chains two given functions

function chain(prepend, append, aux, unshift) {
	return (prepend) ? ((unshift) ? function(node, state) {
		return append(node, aux, state) && prepend(node, state);
	} : function(node, state) {
		return prepend(node, state) && append(node, aux, state);
	}) : function(node, state) {
		return append(node, aux, state);
	};
	// fn.$slyIndex = (prepend) ? (prepend.$slyIndex + 1) : 0;
};


// prepared match comperators, probably needs namespacing
var empty = function() {
	return true;
};

var matchId = function(node, id) {
	return (node.id == id);
};

var matchTag = function(node, tag) {
	return (node.nodeName.toUpperCase() == tag);
};

var prepareClass = function(name) {
	return (new RegExp('(?:^|[ \\t\\r\\n\\f])' + name + '(?:$|[ \\t\\r\\n\\f])'));
};

var matchClass = function(node, expr) {
	return node.className && expr.test(node.className);
};

var prepareAttribute = function(attr) {
	attr.getter = Sly.lookupAttribute(attr.name) || Sly.getAttribute;
	if (!attr.operator || !attr.value) return attr;
	var parser = operators[attr.operator];
	if (parser) { // @todo: Allow functions, not only regex
		attr.escaped = Sly.escapeRegExp(attr.value);
		attr.pattern = new RegExp(parser(attr.value, attr.escaped, attr));
	}
	return attr;
};

var matchAttribute = function(node, attr) {
	var read = attr.getter(node, attr.name);
	switch (attr.operator) {
		case null: return read;
		case '=': return (read == attr.value);
		case '!=': return (read != attr.value);
	}
	if (!read && attr.value) return false;
	return attr.pattern.test(read);
};


/**
 * Sly::compute
 *
 * Attaches the following methods to the selector object:
 *
 * {
 *   search: Uses the most convinient properties (id, tag and/or class) of the selector as search.
 *   matchAux: If search does not contain all selector properties, this method matches an element against the rest.
 *   match: Matches an element against all properties.
 *   simple: Set when matchAux is not needed.
 *   combine: The callback for the combinator
 * }
 */
proto.compute = function(selector) {

	var i, item, match, search, matchSearch, tagged,
		tag = selector.tag,
		id = selector.id,
		classes = selector.classes;

	var nodeName = (tag) ? tag.toUpperCase() : null;

	if (id) {
		tagged = true;

		matchSearch = chain(null, matchId, id);

		search = function(context) {
			if (context.getElementById) {
				var el = context.getElementById(id);
				return (el
					&& (!nodeName || el.nodeName.toUpperCase() == nodeName)
					&& (!support.getIdAdds || el.id == id))
						? [el]
						: [];
			}

			var query = context.getElementsByTagName(tag || '*');
			for (var j = 0, node; (node = query[j]); j++) {
				if (node.id == id) return [node];
			}
			return [];
		};
	}

	if (classes.length > 0) {

		if (!search && support.hasByClass) {

			for (i = 0; (item = classes[i]); i++) {
				matchSearch = chain(matchSearch, matchClass, prepareClass(item));
			}

			var joined = classes.join(' ');
			search = function(context) {
				return context.getElementsByClassName(joined);
			};

		} else if (!search && classes.length == 1) { // optimised for typical .one-class-only

			tagged = true;

			var expr = prepareClass(classes[0]);
			matchSearch = chain(matchSearch, matchClass, expr);

			search = function(context) {
				var query = context.getElementsByTagName(tag || '*');
				var found = [];
				for (var i = 0, node; (node = query[i]); i++) {
					if (node.className && expr.test(node.className)) found.push(node);
				}
				return found;
			};

		} else {

			for (i = 0; (item = classes[i]); i++) {
				match = chain(match, matchClass, prepareClass(item));
			}

		}
	}

	if (tag) {

		if (!search) {
			matchSearch = chain(matchSearch, matchTag, nodeName);

			search = function(context) {
				return context.getElementsByTagName(tag);
			};
		} else if (!tagged) { // search does not filter by tag yet
			match = chain(match, matchTag, nodeName);
		}

	} else if (!search) { // default engine

		search = function(context) {
			var query = context.getElementsByTagName('*');
			if (!support.byTagAddsComments) return query;
			var found = [];
			for (var i = 0, node; (node = query[i]); i++) {
				if (node.nodeType === 1) found.push(node);
			}
			return found;
		};

	}

	for (i = 0; (item = selector.pseudos[i]); i++) {

		if (item.name == 'not') { // optimised :not(), fast as possible
			var not = Sly(item.value);
			match = chain(match, function(node, not) {
				return !not.match(node);
			}, (not.parse().length == 1) ? not.parsed[0] : not);
		} else {
			var parser = pseudos[item.name];
			if (parser) match = chain(match, parser, item.value);
		}

	}

	for (i = 0; (item = selector.attributes[i]); i++) {
		match = chain(match, matchAttribute, prepareAttribute(item));
	}

	if ((selector.simple = !(match))) {
		selector.matchAux = empty;
	} else {
		selector.matchAux = match;
		matchSearch = chain(matchSearch, match);
	}

	selector.match = matchSearch || empty;

	selector.combine = Sly.combinators[selector.combinator || ' '];

	selector.search = search;

	return selector;
};

// Combinators/Pseudos partly from MooTools 1.2-pre, (c) 2006-2009 Valerio Proietti, MIT License

/**
 * Combinators
 */
var combinators = Sly.combinators = {

	' ': function(combined, context, selector, state, locate, fast) {
		var nodes = selector.search(context);
		if (fast && selector.simple) return Sly.toArray(nodes);
		for (var i = 0, node, aux = selector.matchAux; (node = nodes[i]); i++) {
			if (locate(node) && aux(node, state)) combined.push(node);
		}
		return combined;
	},

	'>': function(combined, context, selector, state, locate) {
		var nodes = selector.search(context);
		for (var i = 0, node; (node = nodes[i]); i++) {
			if (node.parentNode == context && locate(node) && selector.matchAux(node, state)) combined.push(node);
		}
		return combined;
	},

	'+': function(combined, context, selector, state, locate) {
		while ((context = context.nextSibling)) {
			if (context.nodeType == 1) {
				if (locate(context) && selector.match(context, state)) combined.push(context);
				break;
			}

		}
		return combined;
	},

	'~': function(combined, context, selector, state, locate) {
		while ((context = context.nextSibling)) {
			if (context.nodeType == 1) {
				if (!locate(context)) break;
				if (selector.match(context, state)) combined.push(context);
			}
		}
		return combined;
	}

};


/**
 * Pseudo-Classes
 */
var pseudos = Sly.pseudos = {

	// w3c pseudo classes

	'first-child': function(node) {
		return pseudos.index(node, 0);
	},

	'last-child': function(node) {
		while ((node = node.nextSibling)) {
			if (node.nodeType === 1) return false;
		}
		return true;
	},

	'only-child': function(node) {
		var prev = node;
		while ((prev = prev.previousSibling)) {
			if (prev.nodeType === 1) return false;
		}
		var next = node;
		while ((next = next.nextSibling)) {
			if (next.nodeType === 1) return false;
		}
		return true;
	},

	'nth-child': function(node, value, state) {
		var parsed = Sly.parseNth(value || 'n');
		if (parsed.special != 'n') return pseudos[parsed.special](node, parsed.a, state);
		state = state || {}; // just to be sure
		state.positions = state.positions || {};
		var uid = Sly.getUid(node) ;
		if (!state.positions[uid]) {
			var count = 0;
			while ((node = node.previousSibling)) {
				if (node.nodeType != 1) continue;
				count++;
				var position = state.positions[Sly.getUid(node)];
				if (position != undefined) {
					count = position + count;
					break;
				}
			}
			state.positions[uid] = count;
		}
		return (state.positions[uid] % parsed.a == parsed.b);
	},

	'empty': function(node) {
		return !(node.innerText || node.textContent || '').length;
	},

	'contains': function(node, text) {
		return (node.innerText || node.textContent || '').indexOf(text) != -1;
	},

	'index': function(node, index) {
		var count = 1;
		while ((node = node.previousSibling)) {
			if (node.nodeType == 1 && ++count > index) return false;
		}
		return (count == index);
	},

	'even': function(node, value, state) {
		return pseudos['nth-child'](node, '2n+1', state);
	},

	'odd': function(node, value, state) {
		return pseudos['nth-child'](node, '2n', state);
	}

};

pseudos.first = pseudos['first-child'];
pseudos.last = pseudos['last-child'];
pseudos.nth = pseudos['nth-child'];
pseudos.eq = pseudos.index;


/**
 * Attribute operators
 */
var operators = Sly.operators = {

	'*=': function(value, escaped) {
		return escaped;
	},

	'^=': function(value, escaped) {
		return '^' + escaped;
	},

	'$=': function(value, escaped) {
		return value + '$';
	},

	'~=': function(value, escaped) {
		return '(?:^|[ \\t\\r\\n\\f])' + escaped + '(?:$|[ \\t\\r\\n\\f])';
	},

	'|=': function(value, escaped) {
		return '(?:^|\\|)' + escaped + '(?:$|\\|)';
	}

};


// public, overridable

/**
 * Sly.getAttribute & Sly.lookupAttribute
 * 
 * @todo add more translations
 */
var translate = {
	'class': 'className'
}

Sly.lookupAttribute = function(name) {
	var prop = translate[name];
	if (prop) {
		return function(node) {
			return node[prop];
		}
	}
	var flag = /^(?:src|href|action)$/.test(name) ? 2 : 0;
	return function(node) {
		return node.getAttribute(name, flag);
	}
};

Sly.getAttribute = function(node, name) {
	return node.getAttribute(name);
};

/**
 * Sly.toArray
 */
var toArray = Array.slice || function(nodes) {
	return Array.prototype.slice.call(nodes);
};

try {
	toArray(document.documentElement.childNodes);
} catch (e) {
	toArray = function(nodes) {
		if (nodes instanceof Array) return nodes;
		var i = nodes.length, results = new Array(i);
		while (i--) results[i] = nodes[i];
		return results;
	};
}

Sly.toArray = toArray;

Sly.compare = (document.compareDocumentPosition) ? function (a, b) {
	return (3 - (a.compareDocumentPosition(b) & 6));
} : function (a, b) {
	return (a.sourceIndex - b.sourceIndex);
};

/**
 * Sly.getUid
 */
var nextUid = 1;

Sly.getUid = (window.ActiveXObject) ? function(node) {
	return (node.$slyUid || (node.$slyUid = {id: nextUid++})).id;
} : function(node) {
	return node.$slyUid || (node.$slyUid = nextUid++);
};


var nthCache = {};

Sly.parseNth = function(value) {
	if (nthCache[value]) return nthCache[value];

	var parsed = value.match(/^([+-]?\d*)?([a-z]+)?([+-]?\d*)?$/);
	if (!parsed) return false;

	var a = parseInt(parsed[1], 10), b = (parseInt(parsed[3], 10) || 0) - 1;

	if ((a = (isNaN(a)) ? 1 : a)) {
		while (b < 1) b += a;
		while (b >= a) b -= a;
	}
	switch (parsed[2]) {
		case 'n': parsed = {a: a, b: b, special: 'n'}; break;
		case 'odd': parsed = {a: 2, b: 0, special: 'n'}; break;
		case 'even': parsed = {a: 2, b: 1, special: 'n'}; break;
		case 'first': parsed = {a: 0, special: 'index'}; break;
		case 'last': parsed = {special: 'last-child'}; break;
		case 'only': parsed = {special: 'only-child'}; break;
		default: parsed = {a: (a) ? (a - 1) : b, special: 'index'};
	}

	return (nthCache[value] = parsed);
};


Sly.escapeRegExp = function(text) {
	return text.replace(/[-.*+?^${}()|[\]\/\\]/g, '\\$&');
};


// generic accessors

Sly.generise = function(name) {
	Sly[name] = function(text) {
		var cls = Sly(text);
		return cls[name].apply(cls, Array.prototype.slice.call(arguments, 1));
	}
};

var generics = ['parse', 'search', 'find', 'match', 'filter'];
for (var i = 0; generics[i]; i++) Sly.generise(generics[i]);


// compile pattern for the first time

Sly.recompile();

// FIN

return Sly;

})();
/*! Sly v1.0rc2 <http://sly.digitarald.com> - (C) 2009 Harald Kirschner <http://digitarald.de> - Open source under MIT License */

 Sly.implement('combinators', {

	// Custom combinators, from MooTools Slick.js

	// Returns all matched parent nodes
	'<': function(combined, context, selector, state, locate) {
		while ((context = context.parentNode) && context.nodeType !== 9) {
	    if (locate(context) && selector.match(context, state)) combined.push(context);
	  }
		return combined;
	},

	// Returns the first matched descendant children
	'^': function(combined, context, selector, state, locate) {
	  if ((context = context.firstChild)) {
	    if (node.nodeType === 1 && locate(context) && selector.match(context, state)) combined.push(context);
	    else combined = Sly.combinators['+'](combined, context, selector, context, state);
	  }
		return combined;
	},

	// Returns all matched next slibings
	'++': function(combined, context, selector, state, locate) {
		while ((context = context.nextSibling)) {
			if (context.nodeType === 1 && locate(context) && this.match(context, state)) combined.push(context);
		}
		return combined;
	},

	// Returns all matched previous slibings
	'--': function(combined, context, selector, state, locate) {
		while ((context = context.previousSibling)) {
			if (context.nodeType === 1 && locate(context) && this.match(context, state)) combined.push(context);
		}
		return combined;
	}

	/*
	// Returns all matched slibings
	'Â±': function(combined, context, selector, state, locate) {
		return Sly.combinators['--'].call(Sly.combinators['++'].call(combined, context, state, uniques), context, state, uniques);
	}
	*/

});



Sly.implement('pseudos', {

	// All the exhilarating w3 ideas go here for the sake of completeness.
	// http://www.w3.org/TR/css3-selectors/#pseudo-classes

	// missing: # :nth-of-type(), :nth-last-of-type(), :first-of-type, :last-of-type, :only-of-type pseudo-class

	// http://www.w3.org/TR/css3-selectors/#root-pseudo
	'root': function(node) {
		return (node.parentNode == node.ownerDocument);
	},

	// http://www.w3.org/TR/css3-selectors/#target-pseudo
	'target': function(node) {
		var hash = location.hash;
		return (node.id && hash && node.id == hash.slice(1));
	},

	// http://www.w3.org/TR/css3-selectors/#only-child-pseudo
	'only-child': function(node, value, state) {
		return (Sly.pseudos['first-child'](node, null, state) && Sly.pseudos['last-child'](node, null, state));
	},
	
	// Custom pseudos, like jQuery filter

	// Matches all elements that are hidden.
	'hidden': function(node) {
		return (!node.offsetWidth && !node.offsetHeight);
	},

	// Matches all elements that are visible.
	'visible': function(node) {
		return (node.offsetWidth || node.offsetHeight);
	},

	// Matches elements which contain at least one element that matches the specified selector.
	'has': function(node, argument) {
		return Sly.find(argument, node);
	},

	// Matches all elements that are disabled.
	'disabled': function(node) {
		return (node.disabled == true);
	},

	// Matches all elements that are enabled.
	'enabled': function(node) {
		return (node.disabled == false && node.type != 'hidden');
	},

	// Matches all elements that are enabled.
	'selected': function(node) {
		return (node.selected != false);
	},

	// Matches all elements that are checked.
	'checked': function(node) {
		return (node.checked == true || node.selected == true);
	},

	// More or less useful from jq
	'input': function(node) {
		return (node.type);
	},

	'radio': function(node) {
		return (node.type == 'radio');
	},

	'checkbox': function(node) {
		return (node.type == 'checkbox');
	},

	'text': function(node) {
		return (node.type == 'text');
	},

	'header': function(node) {
		return ((/^h\d$/i).test(node.tagName));
	}

	// ... be creative and add yours ;)
	
});

Sly.implement('operators', {

	// Matches the attribute value against the given regexp, flags are not possible yet
	'/=': function(value, escaped) {
		return value;
	}

});

// Always call recompile after using implement!
Sly.recompile();
/*!
 * Sizzle CSS Selector Engine - v1.0
 *  Copyright 2009, The Dojo Foundation
 *  Released under the MIT, BSD, and GPL Licenses.
 *  More information: http://sizzlejs.com/
 */
(function(){

var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
	done = 0,
	toString = Object.prototype.toString,
	hasDuplicate = false,
	baseHasDuplicate = true;

// Here we check if the JavaScript engine is using some sort of
// optimization where it does not always call our comparision
// function. If that is the case, discard the hasDuplicate value.
//   Thus far that includes Google Chrome.
[0, 0].sort(function(){
	baseHasDuplicate = false;
	return 0;
});

var Sizzle = function(selector, context, results, seed) {
	results = results || [];
	context = context || document;

	var origContext = context;

	if ( context.nodeType !== 1 && context.nodeType !== 9 ) {
		return [];
	}
	
	if ( !selector || typeof selector !== "string" ) {
		return results;
	}

	var parts = [], m, set, checkSet, extra, prune = true, contextXML = Sizzle.isXML(context),
		soFar = selector, ret, cur, pop, i;
	
	// Reset the position of the chunker regexp (start from head)
	do {
		chunker.exec("");
		m = chunker.exec(soFar);

		if ( m ) {
			soFar = m[3];
		
			parts.push( m[1] );
		
			if ( m[2] ) {
				extra = m[3];
				break;
			}
		}
	} while ( m );

	if ( parts.length > 1 && origPOS.exec( selector ) ) {
		if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
			set = posProcess( parts[0] + parts[1], context );
		} else {
			set = Expr.relative[ parts[0] ] ?
				[ context ] :
				Sizzle( parts.shift(), context );

			while ( parts.length ) {
				selector = parts.shift();

				if ( Expr.relative[ selector ] ) {
					selector += parts.shift();
				}
				
				set = posProcess( selector, set );
			}
		}
	} else {
		// Take a shortcut and set the context if the root selector is an ID
		// (but not if it'll be faster if the inner selector is an ID)
		if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML &&
				Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) {
			ret = Sizzle.find( parts.shift(), context, contextXML );
			context = ret.expr ? Sizzle.filter( ret.expr, ret.set )[0] : ret.set[0];
		}

		if ( context ) {
			ret = seed ?
				{ expr: parts.pop(), set: makeArray(seed) } :
				Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML );
			set = ret.expr ? Sizzle.filter( ret.expr, ret.set ) : ret.set;

			if ( parts.length > 0 ) {
				checkSet = makeArray(set);
			} else {
				prune = false;
			}

			while ( parts.length ) {
				cur = parts.pop();
				pop = cur;

				if ( !Expr.relative[ cur ] ) {
					cur = "";
				} else {
					pop = parts.pop();
				}

				if ( pop == null ) {
					pop = context;
				}

				Expr.relative[ cur ]( checkSet, pop, contextXML );
			}
		} else {
			checkSet = parts = [];
		}
	}

	if ( !checkSet ) {
		checkSet = set;
	}

	if ( !checkSet ) {
		Sizzle.error( cur || selector );
	}

	if ( toString.call(checkSet) === "[object Array]" ) {
		if ( !prune ) {
			results.push.apply( results, checkSet );
		} else if ( context && context.nodeType === 1 ) {
			for ( i = 0; checkSet[i] != null; i++ ) {
				if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) {
					results.push( set[i] );
				}
			}
		} else {
			for ( i = 0; checkSet[i] != null; i++ ) {
				if ( checkSet[i] && checkSet[i].nodeType === 1 ) {
					results.push( set[i] );
				}
			}
		}
	} else {
		makeArray( checkSet, results );
	}

	if ( extra ) {
		Sizzle( extra, origContext, results, seed );
		Sizzle.uniqueSort( results );
	}

	return results;
};

Sizzle.uniqueSort = function(results){
	if ( sortOrder ) {
		hasDuplicate = baseHasDuplicate;
		results.sort(sortOrder);

		if ( hasDuplicate ) {
			for ( var i = 1; i < results.length; i++ ) {
				if ( results[i] === results[i-1] ) {
					results.splice(i--, 1);
				}
			}
		}
	}

	return results;
};

Sizzle.matches = function(expr, set){
	return Sizzle(expr, null, null, set);
};

Sizzle.find = function(expr, context, isXML){
	var set;

	if ( !expr ) {
		return [];
	}

	for ( var i = 0, l = Expr.order.length; i < l; i++ ) {
		var type = Expr.order[i], match;
		
		if ( (match = Expr.leftMatch[ type ].exec( expr )) ) {
			var left = match[1];
			match.splice(1,1);

			if ( left.substr( left.length - 1 ) !== "\\" ) {
				match[1] = (match[1] || "").replace(/\\/g, "");
				set = Expr.find[ type ]( match, context, isXML );
				if ( set != null ) {
					expr = expr.replace( Expr.match[ type ], "" );
					break;
				}
			}
		}
	}

	if ( !set ) {
		set = context.getElementsByTagName("*");
	}

	return {set: set, expr: expr};
};

Sizzle.filter = function(expr, set, inplace, not){
	var old = expr, result = [], curLoop = set, match, anyFound,
		isXMLFilter = set && set[0] && Sizzle.isXML(set[0]);

	while ( expr && set.length ) {
		for ( var type in Expr.filter ) {
			if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) {
				var filter = Expr.filter[ type ], found, item, left = match[1];
				anyFound = false;

				match.splice(1,1);

				if ( left.substr( left.length - 1 ) === "\\" ) {
					continue;
				}

				if ( curLoop === result ) {
					result = [];
				}

				if ( Expr.preFilter[ type ] ) {
					match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );

					if ( !match ) {
						anyFound = found = true;
					} else if ( match === true ) {
						continue;
					}
				}

				if ( match ) {
					for ( var i = 0; (item = curLoop[i]) != null; i++ ) {
						if ( item ) {
							found = filter( item, match, i, curLoop );
							var pass = not ^ !!found;

							if ( inplace && found != null ) {
								if ( pass ) {
									anyFound = true;
								} else {
									curLoop[i] = false;
								}
							} else if ( pass ) {
								result.push( item );
								anyFound = true;
							}
						}
					}
				}

				if ( found !== undefined ) {
					if ( !inplace ) {
						curLoop = result;
					}

					expr = expr.replace( Expr.match[ type ], "" );

					if ( !anyFound ) {
						return [];
					}

					break;
				}
			}
		}

		// Improper expression
		if ( expr === old ) {
			if ( anyFound == null ) {
				Sizzle.error( expr );
			} else {
				break;
			}
		}

		old = expr;
	}

	return curLoop;
};

Sizzle.error = function( msg ) {
	throw "Syntax error, unrecognized expression: " + msg;
};

var Expr = Sizzle.selectors = {
	order: [ "ID", "NAME", "TAG" ],
	match: {
		ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
		CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
		NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,
		ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,
		TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,
		CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+\-]*)\))?/,
		POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,
		PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/
	},
	leftMatch: {},
	attrMap: {
		"class": "className",
		"for": "htmlFor"
	},
	attrHandle: {
		href: function(elem){
			return elem.getAttribute("href");
		}
	},
	relative: {
		"+": function(checkSet, part){
			var isPartStr = typeof part === "string",
				isTag = isPartStr && !/\W/.test(part),
				isPartStrNotTag = isPartStr && !isTag;

			if ( isTag ) {
				part = part.toLowerCase();
			}

			for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) {
				if ( (elem = checkSet[i]) ) {
					while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {}

					checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ?
						elem || false :
						elem === part;
				}
			}

			if ( isPartStrNotTag ) {
				Sizzle.filter( part, checkSet, true );
			}
		},
		">": function(checkSet, part){
			var isPartStr = typeof part === "string",
				elem, i = 0, l = checkSet.length;

			if ( isPartStr && !/\W/.test(part) ) {
				part = part.toLowerCase();

				for ( ; i < l; i++ ) {
					elem = checkSet[i];
					if ( elem ) {
						var parent = elem.parentNode;
						checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false;
					}
				}
			} else {
				for ( ; i < l; i++ ) {
					elem = checkSet[i];
					if ( elem ) {
						checkSet[i] = isPartStr ?
							elem.parentNode :
							elem.parentNode === part;
					}
				}

				if ( isPartStr ) {
					Sizzle.filter( part, checkSet, true );
				}
			}
		},
		"": function(checkSet, part, isXML){
			var doneName = done++, checkFn = dirCheck, nodeCheck;

			if ( typeof part === "string" && !/\W/.test(part) ) {
				part = part.toLowerCase();
				nodeCheck = part;
				checkFn = dirNodeCheck;
			}

			checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML);
		},
		"~": function(checkSet, part, isXML){
			var doneName = done++, checkFn = dirCheck, nodeCheck;

			if ( typeof part === "string" && !/\W/.test(part) ) {
				part = part.toLowerCase();
				nodeCheck = part;
				checkFn = dirNodeCheck;
			}

			checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML);
		}
	},
	find: {
		ID: function(match, context, isXML){
			if ( typeof context.getElementById !== "undefined" && !isXML ) {
				var m = context.getElementById(match[1]);
				return m ? [m] : [];
			}
		},
		NAME: function(match, context){
			if ( typeof context.getElementsByName !== "undefined" ) {
				var ret = [], results = context.getElementsByName(match[1]);

				for ( var i = 0, l = results.length; i < l; i++ ) {
					if ( results[i].getAttribute("name") === match[1] ) {
						ret.push( results[i] );
					}
				}

				return ret.length === 0 ? null : ret;
			}
		},
		TAG: function(match, context){
			return context.getElementsByTagName(match[1]);
		}
	},
	preFilter: {
		CLASS: function(match, curLoop, inplace, result, not, isXML){
			match = " " + match[1].replace(/\\/g, "") + " ";

			if ( isXML ) {
				return match;
			}

			for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) {
				if ( elem ) {
					if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n]/g, " ").indexOf(match) >= 0) ) {
						if ( !inplace ) {
							result.push( elem );
						}
					} else if ( inplace ) {
						curLoop[i] = false;
					}
				}
			}

			return false;
		},
		ID: function(match){
			return match[1].replace(/\\/g, "");
		},
		TAG: function(match, curLoop){
			return match[1].toLowerCase();
		},
		CHILD: function(match){
			if ( match[1] === "nth" ) {
				// parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'
				var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec(
					match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" ||
					!/\D/.test( match[2] ) && "0n+" + match[2] || match[2]);

				// calculate the numbers (first)n+(last) including if they are negative
				match[2] = (test[1] + (test[2] || 1)) - 0;
				match[3] = test[3] - 0;
			}

			// TODO: Move to normal caching system
			match[0] = done++;

			return match;
		},
		ATTR: function(match, curLoop, inplace, result, not, isXML){
			var name = match[1].replace(/\\/g, "");
			
			if ( !isXML && Expr.attrMap[name] ) {
				match[1] = Expr.attrMap[name];
			}

			if ( match[2] === "~=" ) {
				match[4] = " " + match[4] + " ";
			}

			return match;
		},
		PSEUDO: function(match, curLoop, inplace, result, not){
			if ( match[1] === "not" ) {
				// If we're dealing with a complex expression, or a simple one
				if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) {
					match[3] = Sizzle(match[3], null, null, curLoop);
				} else {
					var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not);
					if ( !inplace ) {
						result.push.apply( result, ret );
					}
					return false;
				}
			} else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) {
				return true;
			}
			
			return match;
		},
		POS: function(match){
			match.unshift( true );
			return match;
		}
	},
	filters: {
		enabled: function(elem){
			return elem.disabled === false && elem.type !== "hidden";
		},
		disabled: function(elem){
			return elem.disabled === true;
		},
		checked: function(elem){
			return elem.checked === true;
		},
		selected: function(elem){
			// Accessing this property makes selected-by-default
			// options in Safari work properly
			elem.parentNode.selectedIndex;
			return elem.selected === true;
		},
		parent: function(elem){
			return !!elem.firstChild;
		},
		empty: function(elem){
			return !elem.firstChild;
		},
		has: function(elem, i, match){
			return !!Sizzle( match[3], elem ).length;
		},
		header: function(elem){
			return (/h\d/i).test( elem.nodeName );
		},
		text: function(elem){
			return "text" === elem.type;
		},
		radio: function(elem){
			return "radio" === elem.type;
		},
		checkbox: function(elem){
			return "checkbox" === elem.type;
		},
		file: function(elem){
			return "file" === elem.type;
		},
		password: function(elem){
			return "password" === elem.type;
		},
		submit: function(elem){
			return "submit" === elem.type;
		},
		image: function(elem){
			return "image" === elem.type;
		},
		reset: function(elem){
			return "reset" === elem.type;
		},
		button: function(elem){
			return "button" === elem.type || elem.nodeName.toLowerCase() === "button";
		},
		input: function(elem){
			return (/input|select|textarea|button/i).test(elem.nodeName);
		}
	},
	setFilters: {
		first: function(elem, i){
			return i === 0;
		},
		last: function(elem, i, match, array){
			return i === array.length - 1;
		},
		even: function(elem, i){
			return i % 2 === 0;
		},
		odd: function(elem, i){
			return i % 2 === 1;
		},
		lt: function(elem, i, match){
			return i < match[3] - 0;
		},
		gt: function(elem, i, match){
			return i > match[3] - 0;
		},
		nth: function(elem, i, match){
			return match[3] - 0 === i;
		},
		eq: function(elem, i, match){
			return match[3] - 0 === i;
		}
	},
	filter: {
		PSEUDO: function(elem, match, i, array){
			var name = match[1], filter = Expr.filters[ name ];

			if ( filter ) {
				return filter( elem, i, match, array );
			} else if ( name === "contains" ) {
				return (elem.textContent || elem.innerText || Sizzle.getText([ elem ]) || "").indexOf(match[3]) >= 0;
			} else if ( name === "not" ) {
				var not = match[3];

				for ( var j = 0, l = not.length; j < l; j++ ) {
					if ( not[j] === elem ) {
						return false;
					}
				}

				return true;
			} else {
				Sizzle.error( "Syntax error, unrecognized expression: " + name );
			}
		},
		CHILD: function(elem, match){
			var type = match[1], node = elem;
			switch (type) {
				case 'only':
				case 'first':
					while ( (node = node.previousSibling) )	 {
						if ( node.nodeType === 1 ) { 
							return false; 
						}
					}
					if ( type === "first" ) { 
						return true; 
					}
					node = elem;
				case 'last':
					while ( (node = node.nextSibling) )	 {
						if ( node.nodeType === 1 ) { 
							return false; 
						}
					}
					return true;
				case 'nth':
					var first = match[2], last = match[3];

					if ( first === 1 && last === 0 ) {
						return true;
					}
					
					var doneName = match[0],
						parent = elem.parentNode;
	
					if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) {
						var count = 0;
						for ( node = parent.firstChild; node; node = node.nextSibling ) {
							if ( node.nodeType === 1 ) {
								node.nodeIndex = ++count;
							}
						} 
						parent.sizcache = doneName;
					}
					
					var diff = elem.nodeIndex - last;
					if ( first === 0 ) {
						return diff === 0;
					} else {
						return ( diff % first === 0 && diff / first >= 0 );
					}
			}
		},
		ID: function(elem, match){
			return elem.nodeType === 1 && elem.getAttribute("id") === match;
		},
		TAG: function(elem, match){
			return (match === "*" && elem.nodeType === 1) || elem.nodeName.toLowerCase() === match;
		},
		CLASS: function(elem, match){
			return (" " + (elem.className || elem.getAttribute("class")) + " ")
				.indexOf( match ) > -1;
		},
		ATTR: function(elem, match){
			var name = match[1],
				result = Expr.attrHandle[ name ] ?
					Expr.attrHandle[ name ]( elem ) :
					elem[ name ] != null ?
						elem[ name ] :
						elem.getAttribute( name ),
				value = result + "",
				type = match[2],
				check = match[4];

			return result == null ?
				type === "!=" :
				type === "=" ?
				value === check :
				type === "*=" ?
				value.indexOf(check) >= 0 :
				type === "~=" ?
				(" " + value + " ").indexOf(check) >= 0 :
				!check ?
				value && result !== false :
				type === "!=" ?
				value !== check :
				type === "^=" ?
				value.indexOf(check) === 0 :
				type === "$=" ?
				value.substr(value.length - check.length) === check :
				type === "|=" ?
				value === check || value.substr(0, check.length + 1) === check + "-" :
				false;
		},
		POS: function(elem, match, i, array){
			var name = match[2], filter = Expr.setFilters[ name ];

			if ( filter ) {
				return filter( elem, i, match, array );
			}
		}
	}
};

var origPOS = Expr.match.POS,
	fescape = function(all, num){
		return "\\" + (num - 0 + 1);
	};

for ( var type in Expr.match ) {
	Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) );
	Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) );
}

var makeArray = function(array, results) {
	array = Array.prototype.slice.call( array, 0 );

	if ( results ) {
		results.push.apply( results, array );
		return results;
	}
	
	return array;
};

// Perform a simple check to determine if the browser is capable of
// converting a NodeList to an array using builtin methods.
// Also verifies that the returned array holds DOM nodes
// (which is not the case in the Blackberry browser)
try {
	Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType;

// Provide a fallback method if it does not work
} catch(e){
	makeArray = function(array, results) {
		var ret = results || [], i = 0;

		if ( toString.call(array) === "[object Array]" ) {
			Array.prototype.push.apply( ret, array );
		} else {
			if ( typeof array.length === "number" ) {
				for ( var l = array.length; i < l; i++ ) {
					ret.push( array[i] );
				}
			} else {
				for ( ; array[i]; i++ ) {
					ret.push( array[i] );
				}
			}
		}

		return ret;
	};
}

var sortOrder;

if ( document.documentElement.compareDocumentPosition ) {
	sortOrder = function( a, b ) {
		if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) {
			if ( a == b ) {
				hasDuplicate = true;
			}
			return a.compareDocumentPosition ? -1 : 1;
		}

		var ret = a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1;
		if ( ret === 0 ) {
			hasDuplicate = true;
		}
		return ret;
	};
} else if ( "sourceIndex" in document.documentElement ) {
	sortOrder = function( a, b ) {
		if ( !a.sourceIndex || !b.sourceIndex ) {
			if ( a == b ) {
				hasDuplicate = true;
			}
			return a.sourceIndex ? -1 : 1;
		}

		var ret = a.sourceIndex - b.sourceIndex;
		if ( ret === 0 ) {
			hasDuplicate = true;
		}
		return ret;
	};
} else if ( document.createRange ) {
	sortOrder = function( a, b ) {
		if ( !a.ownerDocument || !b.ownerDocument ) {
			if ( a == b ) {
				hasDuplicate = true;
			}
			return a.ownerDocument ? -1 : 1;
		}

		var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange();
		aRange.setStart(a, 0);
		aRange.setEnd(a, 0);
		bRange.setStart(b, 0);
		bRange.setEnd(b, 0);
		var ret = aRange.compareBoundaryPoints(Range.START_TO_END, bRange);
		if ( ret === 0 ) {
			hasDuplicate = true;
		}
		return ret;
	};
}

// Utility function for retreiving the text value of an array of DOM nodes
Sizzle.getText = function( elems ) {
	var ret = "", elem;

	for ( var i = 0; elems[i]; i++ ) {
		elem = elems[i];

		// Get the text from text nodes and CDATA nodes
		if ( elem.nodeType === 3 || elem.nodeType === 4 ) {
			ret += elem.nodeValue;

		// Traverse everything else, except comment nodes
		} else if ( elem.nodeType !== 8 ) {
			ret += Sizzle.getText( elem.childNodes );
		}
	}

	return ret;
};

// Check to see if the browser returns elements by name when
// querying by getElementById (and provide a workaround)
(function(){
	// We're going to inject a fake input element with a specified name
	var form = document.createElement("div"),
		id = "script" + (new Date()).getTime();
	form.innerHTML = "<a name='" + id + "'/>";

	// Inject it into the root element, check its status, and remove it quickly
	var root = document.documentElement;
	root.insertBefore( form, root.firstChild );

	// The workaround has to do additional checks after a getElementById
	// Which slows things down for other browsers (hence the branching)
	if ( document.getElementById( id ) ) {
		Expr.find.ID = function(match, context, isXML){
			if ( typeof context.getElementById !== "undefined" && !isXML ) {
				var m = context.getElementById(match[1]);
				return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : [];
			}
		};

		Expr.filter.ID = function(elem, match){
			var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");
			return elem.nodeType === 1 && node && node.nodeValue === match;
		};
	}

	root.removeChild( form );
	root = form = null; // release memory in IE
})();

(function(){
	// Check to see if the browser returns only elements
	// when doing getElementsByTagName("*")

	// Create a fake element
	var div = document.createElement("div");
	div.appendChild( document.createComment("") );

	// Make sure no comments are found
	if ( div.getElementsByTagName("*").length > 0 ) {
		Expr.find.TAG = function(match, context){
			var results = context.getElementsByTagName(match[1]);

			// Filter out possible comments
			if ( match[1] === "*" ) {
				var tmp = [];

				for ( var i = 0; results[i]; i++ ) {
					if ( results[i].nodeType === 1 ) {
						tmp.push( results[i] );
					}
				}

				results = tmp;
			}

			return results;
		};
	}

	// Check to see if an attribute returns normalized href attributes
	div.innerHTML = "<a href='#'></a>";
	if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" &&
			div.firstChild.getAttribute("href") !== "#" ) {
		Expr.attrHandle.href = function(elem){
			return elem.getAttribute("href", 2);
		};
	}

	div = null; // release memory in IE
})();

if ( document.querySelectorAll ) {
	(function(){
		var oldSizzle = Sizzle, div = document.createElement("div");
		div.innerHTML = "<p class='TEST'></p>";

		// Safari can't handle uppercase or unicode characters when
		// in quirks mode.
		if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) {
			return;
		}
	
		Sizzle = function(query, context, extra, seed){
			context = context || document;

			// Only use querySelectorAll on non-XML documents
			// (ID selectors don't work in non-HTML documents)
			if ( !seed && context.nodeType === 9 && !Sizzle.isXML(context) ) {
				try {
					return makeArray( context.querySelectorAll(query), extra );
				} catch(e){}
			}
		
			return oldSizzle(query, context, extra, seed);
		};

		for ( var prop in oldSizzle ) {
			Sizzle[ prop ] = oldSizzle[ prop ];
		}

		div = null; // release memory in IE
	})();
}

(function(){
	var div = document.createElement("div");

	div.innerHTML = "<div class='test e'></div><div class='test'></div>";

	// Opera can't find a second classname (in 9.6)
	// Also, make sure that getElementsByClassName actually exists
	if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) {
		return;
	}

	// Safari caches class attributes, doesn't catch changes (in 3.2)
	div.lastChild.className = "e";

	if ( div.getElementsByClassName("e").length === 1 ) {
		return;
	}
	
	Expr.order.splice(1, 0, "CLASS");
	Expr.find.CLASS = function(match, context, isXML) {
		if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) {
			return context.getElementsByClassName(match[1]);
		}
	};

	div = null; // release memory in IE
})();

function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
	for ( var i = 0, l = checkSet.length; i < l; i++ ) {
		var elem = checkSet[i];
		if ( elem ) {
			elem = elem[dir];
			var match = false;

			while ( elem ) {
				if ( elem.sizcache === doneName ) {
					match = checkSet[elem.sizset];
					break;
				}

				if ( elem.nodeType === 1 && !isXML ){
					elem.sizcache = doneName;
					elem.sizset = i;
				}

				if ( elem.nodeName.toLowerCase() === cur ) {
					match = elem;
					break;
				}

				elem = elem[dir];
			}

			checkSet[i] = match;
		}
	}
}

function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
	for ( var i = 0, l = checkSet.length; i < l; i++ ) {
		var elem = checkSet[i];
		if ( elem ) {
			elem = elem[dir];
			var match = false;

			while ( elem ) {
				if ( elem.sizcache === doneName ) {
					match = checkSet[elem.sizset];
					break;
				}

				if ( elem.nodeType === 1 ) {
					if ( !isXML ) {
						elem.sizcache = doneName;
						elem.sizset = i;
					}
					if ( typeof cur !== "string" ) {
						if ( elem === cur ) {
							match = true;
							break;
						}

					} else if ( Sizzle.filter( cur, [elem] ).length > 0 ) {
						match = elem;
						break;
					}
				}

				elem = elem[dir];
			}

			checkSet[i] = match;
		}
	}
}

Sizzle.contains = document.compareDocumentPosition ? function(a, b){
	return !!(a.compareDocumentPosition(b) & 16);
} : function(a, b){
	return a !== b && (a.contains ? a.contains(b) : true);
};

Sizzle.isXML = function(elem){
	// documentElement is verified for cases where it doesn't yet exist
	// (such as loading iframes in IE - #4833) 
	var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement;
	return documentElement ? documentElement.nodeName !== "HTML" : false;
};

var posProcess = function(selector, context){
	var tmpSet = [], later = "", match,
		root = context.nodeType ? [context] : context;

	// Position selectors must be done after the filter
	// And so must :not(positional) so we move all PSEUDOs to the end
	while ( (match = Expr.match.PSEUDO.exec( selector )) ) {
		later += match[0];
		selector = selector.replace( Expr.match.PSEUDO, "" );
	}

	selector = Expr.relative[selector] ? selector + "*" : selector;

	for ( var i = 0, l = root.length; i < l; i++ ) {
		Sizzle( selector, root[i], tmpSet );
	}

	return Sizzle.filter( later, tmpSet );
};

// EXPOSE

window.Sizzle = Sizzle;

})();/* Clientcide Copyright (c) 2006-2009, http://www.clientcide.com/wiki/cnet-libraries#license*/

//Contents: Clientcide, Class.ToElement, dbug, StyleWriter, StickyWin, StickyWin.Fx, StickyWin.Drag, StickyWin.Modal, StickyWin.UI, StickyWin.Ajax, StickyWin.Alert, StickyWin.Confirm, StickyWin.Prompt, StickyWin.UI.Pointy, StickyWin.PointyTip, Tips.Pointy

//This lib: http://www.clientcide.com/js/build.php?excludeLibs[]=mootools-core&excludeLibs[]=mootools-more&require[]=StickyWin&require[]=StickyWin.Fx&require[]=StickyWin.Drag&require[]=StickyWin.Modal&require[]=StickyWin.Ajax&require[]=StickyWin.Alert&require[]=StickyWin.Confirm&require[]=StickyWin.Prompt&require[]=StickyWin.UI.Pointy&require[]=StickyWin.PointyTip&require[]=Tips.Pointy&compression=jsmin


var Clientcide={version:'%build%',assetLocation:"http://github.com/anutron/clientcide/raw/master/Assets",setAssetLocation:function(baseHref){Clientcide.assetLocation=baseHref;if(Clientcide.preloaded)Clientcide.preLoadCss();},preLoadCss:function(){if(window.StickyWin&&StickyWin.ui)StickyWin.ui();if(window.StickyWin&&StickyWin.pointy)StickyWin.pointy();Clientcide.preloaded=true;return true;},preloaded:false};(function(){if(!window.addEvent)return;var preload=function(){if(window.dbug)dbug.log('preloading clientcide css');if(!Clientcide.preloaded)Clientcide.preLoadCss();};window.addEvent('domready',preload);window.addEvent('load',preload);})();setCNETAssetBaseHref=Clientcide.setAssetLocation;
Class.ToElement=new Class({toElement:function(){return this.element;}});var ToElement=Class.ToElement;
var dbug={logged:[],timers:{},firebug:false,enabled:false,log:function(){dbug.logged.push(arguments);},nolog:function(msg){dbug.logged.push(arguments);},time:function(name){dbug.timers[name]=new Date().getTime();},timeEnd:function(name){if(dbug.timers[name]){var end=new Date().getTime()-dbug.timers[name];dbug.timers[name]=false;dbug.log('%s: %s',name,end);}else dbug.log('no such timer: %s',name);},enable:function(silent){var con=window.firebug?firebug.d.console.cmd:window.console;if((!!window.console&&!!window.console.warn)||window.firebug){try{dbug.enabled=true;dbug.log=function(){try{(con.debug||con.log).apply(con,arguments);}catch(e){console.log(Array.slice(arguments));}};dbug.time=function(){con.time.apply(con,arguments);};dbug.timeEnd=function(){con.timeEnd.apply(con,arguments);};if(!silent)dbug.log('enabling dbug');for(var i=0;i<dbug.logged.length;i++){dbug.log.apply(con,dbug.logged[i]);}
dbug.logged=[];}catch(e){dbug.enable.delay(400);}}},disable:function(){if(dbug.firebug)dbug.enabled=false;dbug.log=dbug.nolog;dbug.time=function(){};dbug.timeEnd=function(){};},cookie:function(set){var value=document.cookie.match('(?:^|;)\\s*jsdebug=([^;]*)');var debugCookie=value?unescape(value[1]):false;if((!$defined(set)&&debugCookie!='true')||($defined(set)&&set)){dbug.enable();dbug.log('setting debugging cookie');var date=new Date();date.setTime(date.getTime()+(24*60*60*1000));document.cookie='jsdebug=true;expires='+date.toGMTString()+';path=/;';}else dbug.disableCookie();},disableCookie:function(){dbug.log('disabling debugging cookie');document.cookie='jsdebug=false;path=/;';}};(function(){var fb=!!window.console||!!window.firebug;var con=window.firebug?window.firebug.d.console.cmd:window.console;var debugMethods=['debug','info','warn','error','assert','dir','dirxml'];var otherMethods=['trace','group','groupEnd','profile','profileEnd','count'];function set(methodList,defaultFunction){for(var i=0;i<methodList.length;i++){dbug[methodList[i]]=(fb&&con[methodList[i]])?con[methodList[i]]:defaultFunction;}};set(debugMethods,dbug.log);set(otherMethods,function(){});})();if((!!window.console&&!!window.console.warn)||window.firebug){dbug.firebug=true;var value=document.cookie.match('(?:^|;)\\s*jsdebug=([^;]*)');var debugCookie=value?unescape(value[1]):false;if(window.location.href.indexOf("jsdebug=true")>0||debugCookie=='true')dbug.enable();if(debugCookie=='true')dbug.log('debugging cookie enabled');if(window.location.href.indexOf("jsdebugCookie=true")>0){dbug.cookie();if(!dbug.enabled)dbug.enable();}
if(window.location.href.indexOf("jsdebugCookie=false")>0)dbug.disableCookie();}
var StyleWriter=new Class({createStyle:function(css,id){window.addEvent('domready',function(){try{if(document.id(id)&&id)return;var style=new Element('style',{id:id||''}).inject($$('head')[0]);if(Browser.Engine.trident)style.styleSheet.cssText=css;else style.set('text',css);}catch(e){dbug.log('error: %s',e);}}.bind(this));}});
var StickyWin=new Class({Binds:['destroy','hide','togglepin','esc'],Implements:[Options,Events,StyleWriter,Class.ToElement],options:{closeClassName:'closeSticky',pinClassName:'pinSticky',content:'',zIndex:10000,className:'',width:false,height:false,timeout:-1,allowMultipleByClass:true,allowMultiple:true,showNow:true,useIframeShim:true,iframeShimSelector:'',destroyOnClose:false,closeOnClickOut:false,closeOnEsc:false,getWindowManager:function(){return StickyWin.WM;}},css:'.SWclearfix:after {content: "."; display: block; height: 0; clear: both; visibility: hidden;}'+'.SWclearfix {display: inline-table;} * html .SWclearfix {height: 1%;} .SWclearfix {display: block;}',initialize:function(options){this.options.inject=this.options.inject||{target:document.body,where:'bottom'};this.setOptions(options);this.windowManager=this.options.getWindowManager();this.id=this.options.id||'StickyWin_'+new Date().getTime();this.makeWindow();if(this.windowManager)this.windowManager.add(this);if(this.options.content)this.setContent(this.options.content);if(this.options.timeout>0){this.addEvent('onDisplay',function(){this.hide.delay(this.options.timeout,this);}.bind(this));}
this.createStyle(this.css,'StickyWinClearFix');if(this.options.closeOnClickOut||this.options.closeOnEsc)this.attach();if(this.options.destroyOnClose)this.addEvent('close',this.destroy);if(this.options.showNow)this.show();},attach:function(attach){var method=$pick(attach,true)?'addEvents':'removeEvents';var events={};if(this.options.closeOnClickOut)events.click=this.esc;if(this.options.closeOnEsc)events.keyup=this.esc;document[method](events);},esc:function(e){if(e.key=="esc")this.hide();if(e.type=="click"&&this.element!=e.target&&!this.element.hasChild(e.target))this.hide();},makeWindow:function(){this.destroyOthers();if(!document.id(this.id)){this.win=new Element('div',{id:this.id}).addClass(this.options.className).addClass('StickyWinInstance').addClass('SWclearfix').setStyles({display:'none',position:'absolute',zIndex:this.options.zIndex}).inject(this.options.inject.target,this.options.inject.where).store('StickyWin',this);}else this.win=document.id(this.id);this.element=this.win;if(this.options.width&&$type(this.options.width.toInt())=="number")this.win.setStyle('width',this.options.width.toInt());if(this.options.height&&$type(this.options.height.toInt())=="number")this.win.setStyle('height',this.options.height.toInt());return this;},show:function(suppressEvent){this.showWin();if(!suppressEvent)this.fireEvent('display');if(this.options.useIframeShim)this.showIframeShim();this.visible=true;return this;},showWin:function(){if(this.windowManager)this.windowManager.focus(this);if(!this.positioned)this.position();this.win.show();},hide:function(suppressEvent){if($type(suppressEvent)=="event"||!suppressEvent)this.fireEvent('close');this.hideWin();if(this.options.useIframeShim)this.hideIframeShim();this.visible=false;return this;},hideWin:function(){this.win.setStyle('display','none');},destroyOthers:function(){if(!this.options.allowMultipleByClass||!this.options.allowMultiple){$$('div.StickyWinInstance').each(function(sw){if(!this.options.allowMultiple||(!this.options.allowMultipleByClass&&sw.hasClass(this.options.className)))
sw.retrieve('StickyWin').destroy();},this);}},setContent:function(html){if(this.win.getChildren().length>0)this.win.empty();if($type(html)=="string")this.win.set('html',html);else if(document.id(html))this.win.adopt(html);this.win.getElements('.'+this.options.closeClassName).each(function(el){el.addEvent('click',this.hide);},this);this.win.getElements('.'+this.options.pinClassName).each(function(el){el.addEvent('click',this.togglepin);},this);return this;},position:function(options){this.positioned=true;this.setOptions(options);this.win.position({allowNegative:$pick(this.options.allowNegative,this.options.relativeTo!=document.body),relativeTo:this.options.relativeTo,position:this.options.position,offset:this.options.offset,edge:this.options.edge});if(this.shim)this.shim.position();return this;},pin:function(pin){if(!this.win.pin){dbug.log('you must include element.pin.js!');return this;}
this.pinned=$pick(pin,true);this.win.pin(pin);return this;},unpin:function(){return this.pin(false);},togglepin:function(){return this.pin(!this.pinned);},makeIframeShim:function(){if(!this.shim){var el=(this.options.iframeShimSelector)?this.win.getElement(this.options.iframeShimSelector):this.win;this.shim=new IframeShim(el,{display:false,name:'StickyWinShim'});}},showIframeShim:function(){if(this.options.useIframeShim){this.makeIframeShim();this.shim.show();}},hideIframeShim:function(){if(this.shim)this.shim.hide();},destroy:function(){if(this.windowManager)this.windowManager.remove(this);if(this.win)this.win.destroy();if(this.options.useIframeShim&&this.shim)this.shim.destroy();if(document.id('modalOverlay'))document.id('modalOverlay').destroy();this.fireEvent('destroy');}});StickyWin.Stacker=new Class({Implements:[Options,Events],Binds:['click'],instances:[],options:{zIndexBase:9000},initialize:function(options){this.setOptions(options);this.zIndex=this.options.zIndex;},add:function(sw){this.instances.include(sw);document.id(sw).addEvent('mousedown',this.click);},click:function(e){this.instances.each(function(sw){var el=document.id(sw);if(el==e.target||el.hasChild(document.id(e.target)))this.focus(sw);},this);},focus:function(instance){if(this.focused==instance)return;this.focused=instance;if(instance)this.instances.erase(instance).push(instance);this.instances.each(function(current,i){document.id(current).setStyle('z-index',this.options.zIndexBase+i);},this);this.focused=instance;},remove:function(sw){this.instances.erase(sw);document.id(sw).removeEvent('click',this.click);}});StickyWin.WM=new StickyWin.Stacker();
StickyWin=Class.refactor(StickyWin,{options:{fade:true,fadeDuration:150},hideWin:function(){if(this.options.fade)this.fade(0);else this.previous();},showWin:function(){if(this.options.fade)this.fade(1);else this.previous();},hide:function(){this.previous(this.options.fade);},show:function(){this.previous(this.options.fade);},fade:function(to){if(!this.fadeFx){this.win.setStyles({opacity:0,display:'block'});var opts={property:'opacity',duration:this.options.fadeDuration};if(this.options.fadeTransition)opts.transition=this.options.fadeTransition;this.fadeFx=new Fx.Tween(this.win,opts);}
if(to>0){this.win.setStyle('display','block');this.position();}
this.fadeFx.clearChain();this.fadeFx.start(to).chain(function(){if(to==0){this.win.setStyle('display','none');this.fireEvent('onClose');}else{this.fireEvent('onDisplay');}}.bind(this));return this;}});StickyWin.Fx=StickyWin;
StickyWin=Class.refactor(StickyWin,{options:{draggable:false,dragOptions:{},dragHandleSelector:'.dragHandle',resizable:false,resizeOptions:{},resizeHandleSelector:''},setContent:function(){this.previous.apply(this,arguments);if(this.options.draggable)this.makeDraggable();if(this.options.resizable)this.makeResizable();return this;},makeDraggable:function(){var toggled=this.toggleVisible(true);if(this.options.useIframeShim){this.makeIframeShim();var onComplete=(this.options.dragOptions.onComplete||$empty);this.options.dragOptions.onComplete=function(){onComplete();this.shim.position();}.bind(this);}
if(this.options.dragHandleSelector){var handle=this.win.getElement(this.options.dragHandleSelector);if(handle){handle.setStyle('cursor','move');this.options.dragOptions.handle=handle;}}
this.win.makeDraggable(this.options.dragOptions);if(toggled)this.toggleVisible(false);},makeResizable:function(){var toggled=this.toggleVisible(true);if(this.options.useIframeShim){this.makeIframeShim();var onComplete=(this.options.resizeOptions.onComplete||$empty);this.options.resizeOptions.onComplete=function(){onComplete();this.shim.position();}.bind(this);}
if(this.options.resizeHandleSelector){var handle=this.win.getElement(this.options.resizeHandleSelector);if(handle)this.options.resizeOptions.handle=this.win.getElement(this.options.resizeHandleSelector);}
this.win.makeResizable(this.options.resizeOptions);if(toggled)this.toggleVisible(false);},toggleVisible:function(show){if(!this.visible&&Browser.Engine.webkit&&$pick(show,true)){this.win.setStyles({display:'block',opacity:0});return true;}else if(!$pick(show,false)){this.win.setStyles({display:'none',opacity:1});return false;}
return false;}});StickyWin.Fx=StickyWin;
StickyWin.Modal=new Class({Extends:StickyWin,options:{modalize:true,maskOptions:{style:{'background-color':'#333',opacity:0.8}},hideOnClick:true,getWindowManager:function(){return StickyWin.ModalWM;}},initialize:function(options){this.options.maskTarget=this.options.maskTarget||document.body;this.setOptions(options);this.mask=new Mask(this.options.maskTarget,this.options.maskOptions).addEvent('click',function(){if(this.options.hideOnClick)this.hide();}.bind(this));this.parent(options);},show:function(showModal){if($pick(showModal,this.options.modalize))this.mask.show();this.parent();},hide:function(hideModal){if($pick(hideModal,true))this.mask.hide();this.parent();}});StickyWin.ModalWM=new StickyWin.Stacker({zIndexBase:11000});if(StickyWin.Fx)StickyWin.Fx.Modal=StickyWin.Modal;
StickyWin.UI=new Class({Implements:[Options,Class.ToElement,StyleWriter],options:{width:300,css:"div.DefaultStickyWin {font-family:verdana; font-size:11px; line-height: 13px;position: relative;}"+"div.DefaultStickyWin div.top{-moz-user-select: none;-khtml-user-select: none;}"+"div.DefaultStickyWin div.top_ul{background:url({%baseHref%}full.png) top left no-repeat; height:30px; width:15px; float:left}"+"div.DefaultStickyWin div.top_ur{position:relative; left:0px !important; left:-4px; background:url({%baseHref%}full.png) top right !important; height:30px; margin:0px 0px 0px 15px !important; margin-right:-4px; padding:0px}"+"div.DefaultStickyWin h1.caption{clear: none !important; margin:0px !important; overflow: hidden; padding:0 !important; font-weight:bold; color:#555; font-size:14px !important; position:relative; top:8px !important; left:5px !important; float: left; height: 22px !important;}"+"div.DefaultStickyWin div.middle, div.DefaultStickyWin div.closeBody {background:url({%baseHref%}body.png) top left repeat-y; margin:0px 20px 0px 0px !important; margin-bottom: -3px; position: relative; top: 0px !important; top: -3px;}"+"div.DefaultStickyWin div.body{background:url({%baseHref%}body.png) top right repeat-y; padding:8px 23px 8px 0px !important; margin-left:5px !important; position:relative; right:-20px !important; z-index: 1;}"+"div.DefaultStickyWin div.bottom{clear:both;}"+"div.DefaultStickyWin div.bottom_ll{background:url({%baseHref%}full.png) bottom left no-repeat; width:15px; height:15px; float:left}"+"div.DefaultStickyWin div.bottom_lr{background:url({%baseHref%}full.png) bottom right; position:relative; left:0px !important; left:-4px; margin:0px 0px 0px 15px !important; margin-right:-4px; height:15px}"+"div.DefaultStickyWin div.closeButtons{text-align: center; background:url({%baseHref%}body.png) top right repeat-y; padding: 4px 30px 8px 0px; margin-left:5px; position:relative; right:-20px}"+"div.DefaultStickyWin a.button:hover{background:url({%baseHref%}big_button_over.gif) repeat-x}"+"div.DefaultStickyWin a.button {background:url({%baseHref%}big_button.gif) repeat-x; margin: 2px 8px 2px 8px; padding: 2px 12px; cursor:pointer; border: 1px solid #999 !important; text-decoration:none; color: #000 !important;}"+"div.DefaultStickyWin div.closeButton{width:13px; height:13px; background:url({%baseHref%}closebtn.gif) no-repeat; position: absolute; right: 0px; margin:10px 15px 0px 0px !important; cursor:pointer;top:0px}"+"div.DefaultStickyWin div.dragHandle { width: 11px; height: 25px; position: relative; top: 5px; left: -3px; cursor: move; background: url({%baseHref%}drag_corner.gif); float: left;}",cornerHandle:false,cssClass:'',buttons:[],cssId:'defaultStickyWinStyle',cssClassName:'DefaultStickyWin',closeButton:true},initialize:function(){var args=this.getArgs(arguments);this.setOptions(args.options);this.legacy();var css=this.options.css.substitute({baseHref:this.options.baseHref||Clientcide.assetLocation+'/stickyWinHTML/'},/\\?\{%([^}]+)%\}/g);if(Browser.Engine.trident4)css=css.replace(/png/g,'gif');this.createStyle(css,this.options.cssId);this.build();if(args.caption||args.body)this.setContent(args.caption,args.body);},getArgs:function(){return StickyWin.UI.getArgs.apply(this,arguments);},legacy:function(){var opt=this.options;if(opt.confirmTxt)opt.buttons.push({text:opt.confirmTxt,onClick:opt.onConfirm||$empty});if(opt.closeTxt)opt.buttons.push({text:opt.closeTxt,onClick:opt.onClose||$empty});},build:function(){var opt=this.options;var container=new Element('div',{'class':opt.cssClassName});if(opt.width)container.setStyle('width',opt.width);this.element=container;this.element.store('StickyWinUI',this);if(opt.cssClass)container.addClass(opt.cssClass);var bodyDiv=new Element('div').addClass('body');this.body=bodyDiv;var top_ur=new Element('div').addClass('top_ur');this.top_ur=top_ur;this.top=new Element('div').addClass('top').adopt(new Element('div').addClass('top_ul')).adopt(top_ur);container.adopt(this.top);if(opt.cornerHandle)new Element('div').addClass('dragHandle').inject(top_ur,'top');container.adopt(new Element('div').addClass('middle').adopt(bodyDiv));if(opt.buttons.length>0){var closeButtons=new Element('div').addClass('closeButtons');opt.buttons.each(function(button){if(button.properties&&button.properties.className){button.properties['class']=button.properties.className;delete button.properties.className;}
var properties=$merge({'class':'closeSticky'},button.properties);new Element('a').addEvent('click',button.onClick||$empty).appendText(button.text).inject(closeButtons).set(properties).addClass('button');});container.adopt(new Element('div').addClass('closeBody').adopt(closeButtons));}
container.adopt(new Element('div').addClass('bottom').adopt(new Element('div').addClass('bottom_ll')).adopt(new Element('div').addClass('bottom_lr')));if(this.options.closeButton)container.adopt(new Element('div').addClass('closeButton').addClass('closeSticky'));return this;},setCaption:function(caption){this.caption=caption;if(!this.h1){this.makeCaption(caption);}else{if(document.id(caption))this.h1.adopt(caption);else this.h1.set('html',caption);}
return this;},makeCaption:function(caption){if(!caption)return this.destroyCaption();var opt=this.options;this.h1=new Element('h1').addClass('caption');if(opt.width)this.h1.setStyle('width',(opt.width-(opt.cornerHandle?55:40)-(opt.closeButton?10:0)));this.setCaption(caption);this.top_ur.adopt(this.h1);if(!this.options.cornerHandle)this.h1.addClass('dragHandle');return this;},destroyCaption:function(){if(this.h1){this.h1.destroy();this.h1=null;}
return this;},setContent:function(){var args=this.getArgs.apply(this,arguments);var caption=args.caption;var body=args.body;this.setCaption(caption);if(document.id(body))this.body.empty().adopt(body);else this.body.set('html',body);return this;}});StickyWin.UI.getArgs=function(){var input=$type(arguments[0])=="arguments"?arguments[0]:arguments;if(Browser.Engine.presto&&1===input.length)input=input[0];var cap=input[0],bod=input[1];var args=Array.link(input,{options:Object.type});if(input.length==3||(!args.options&&input.length==2)){args.caption=cap;args.body=bod;}else if(($type(bod)=='object'||!bod)&&cap&&$type(cap)!='object'){args.body=cap;}
return args;};StickyWin.ui=function(caption,body,options){return document.id(new StickyWin.UI(caption,body,options));};
(function(){var SWA=function(extend){return{Extends:extend,options:{url:'',showNow:false,requestOptions:{method:'get',evalScripts:true},wrapWithUi:false,caption:'',uiOptions:{},handleResponse:function(response){var responseScript="";this.Request.response.text.stripScripts(function(script){responseScript+=script;});if(this.options.wrapWithUi)response=StickyWin.ui(this.options.caption,response,this.options.uiOptions);this.setContent(response);this.show();if(this.evalScripts)$exec(responseScript);this.fireEvent('update');}},initialize:function(options){var showNow;if(options&&options.showNow){showNow=true;options.showNow=false;}
this.parent(options);this.evalScripts=this.options.requestOptions.evalScripts;this.options.requestOptions.evalScripts=false;this.createRequest();if(showNow)this.update();},createRequest:function(){this.Request=new Request(this.options.requestOptions).addEvent('onSuccess',this.options.handleResponse.bind(this));},update:function(url,options){this.Request.setOptions(options).send({url:url||this.options.url});return this;}};};try{StickyWin.Ajax=new Class(SWA(StickyWin));}catch(e){}
try{StickyWin.Modal.Ajax=new Class(SWA(StickyWin.Modal));}catch(e){}})();
StickyWin.Alert=new Class({Implements:Options,Extends:StickyWin.Modal,options:{destroyOnClose:true,modalOptions:{modalStyle:{zIndex:11000}},zIndex:110001,uiOptions:{width:250,buttons:[{text:'Ok'}]},getWindowManager:$empty},initialize:function(caption,message,options){this.message=message;this.caption=caption;this.setOptions(options);this.setOptions({content:this.build()});this.parent(options);},makeMessage:function(){return new Element('p',{'class':'errorMsg SWclearfix',styles:{margin:0,minHeight:10},html:this.message});},build:function(){return StickyWin.ui(this.caption,this.makeMessage(),this.options.uiOptions);}});StickyWin.Error=new Class({Extends:StickyWin.Alert,makeMessage:function(){var message=this.parent();new Element('img',{src:(this.options.baseHref||Clientcide.assetLocation+'/simple.error.popup')+'/icon_problems_sm.gif','class':'bang clearfix',styles:{'float':'left',width:30,height:30,margin:'3px 5px 5px 0px'}}).inject(message,'top');return message;}});StickyWin.alert=function(caption,message,options){if($type(options)=="string")options={baseHref:options};return new StickyWin.Alert(caption,message,options);};StickyWin.error=function(caption,message,options){return new StickyWin.Error(caption,message,options);};
StickyWin.Confirm=new Class({Extends:StickyWin.Alert,options:{uiOptions:{width:250}},build:function(callback){this.setOptions({uiOptions:{buttons:[{text:'Cancel'},{text:'Ok',onClick:callback||function(){this.fireEvent('confirm');}.bind(this)}]}});return this.parent();}});StickyWin.confirm=function(caption,message,callback,options){return new StickyWin.Confirm(caption,message,options).addEvent('confirm',callback);};
StickyWin.Prompt=new Class({Extends:StickyWin.Confirm,options:{defaultValue:''},initialize:function(message,header,options){this.addEvent('display',function(){this.input.select();}.bind(this));this.parent.apply(this,arguments);},makeMessage:function(){this.input=new Element('input',{value:this.options.defaultValue,type:'text',id:'foo',styles:{width:'100%'},events:{keyup:function(e){if(e.key=='enter'){this.fireEvent('confirm',this.input.get('value'));this.hide();}}.bind(this)}});return new Element('div').adopt(this.parent()).adopt(this.input);},build:function(){return this.parent(function(){this.fireEvent('confirm',this.input.get('value'));}.bind(this));}});StickyWin.prompt=function(caption,message,callback,options){return new StickyWin.Prompt(caption,message,options).addEvent('confirm',callback);};
StickyWin.UI.Pointy=new Class({Extends:StickyWin.UI,options:{theme:'dark',themes:{dark:{bgColor:'#333',fgColor:'#ddd',imgset:'dark'},light:{bgColor:'#ccc',fgColor:'#333',imgset:'light'}},css:"div.DefaultPointyTip {vertical-align: auto; position: relative;}"+"div.DefaultPointyTip * {text-align:left !important}"+"div.DefaultPointyTip .pointyWrapper div.body{background: {%bgColor%}; color: {%fgColor%}; left: 0px; right: 0px !important;padding:  0px 10px !important;margin-left: 0px !important;font-family: verdana;font-size: 11px;line-height: 13px;position: relative;}"+"div.DefaultPointyTip .pointyWrapper div.top {position: relative;height: 25px; overflow: visible;}"+"div.DefaultPointyTip .pointyWrapper div.top_ul{background: url({%baseHref%}{%imgset%}_back.png) top left no-repeat;width: 8px;height: 25px; position: absolute; left: 0px;}"+"div.DefaultPointyTip .pointyWrapper div.top_ur{background: url({%baseHref%}{%imgset%}_back.png) top right !important;margin: 0 0 0 8px !important;height: 25px;position: relative;left: 0px !important;padding: 0;}"+"div.DefaultPointyTip .pointyWrapper h1.caption{color: {%fgColor%};left: 0px !important;top: 4px !important;clear: none !important;overflow: hidden;font-weight: 700;font-size: 12px !important;position: relative;float: left;height: 22px !important;margin: 0 !important;padding: 0 !important;}"+"div.DefaultPointyTip .pointyWrapper div.middle, div.DefaultPointyTip .pointyWrapper div.closeBody{background:  {%bgColor%};margin: 0 0px 0 0 !important;position: relative;top: 0 !important;}"+"div.DefaultPointyTip .pointyWrapper div.bottom {clear: both; width: 100% !important; background: none; height: 6px} "+"div.DefaultPointyTip .pointyWrapper div.bottom_ll{font-size:1; background: url({%baseHref%}{%imgset%}_back.png) bottom left no-repeat;width: 6px;height: 6px;position: absolute; left: 0px;}"+"div.DefaultPointyTip .pointyWrapper div.bottom_lr{font-size:1; background: url({%baseHref%}{%imgset%}_back.png) bottom right;height: 6px;margin: 0 0 0 6px !important;position: relative;left: 0 !important;}"+"div.DefaultPointyTip .pointyWrapper div.noCaption{ height: 6px; overflow: hidden}"+"div.DefaultPointyTip .pointyWrapper div.closeButton{width:13px; height:13px; background:url({%baseHref%}{%imgset%}_x.png) no-repeat; position: absolute; right: 0px; margin:0px !important; cursor:pointer; z-index: 1; top: 4px;}"+"div.DefaultPointyTip .pointyWrapper div.pointyDivot {background: url({%divot%}) no-repeat;}",divot:'{%baseHref%}{%imgset%}_divot.png',divotSize:22,direction:12,cssId:'defaultPointyTipStyle',cssClassName:'DefaultPointyTip'},initialize:function(){var args=this.getArgs(arguments);this.setOptions(args.options);$extend(this.options,this.options.themes[this.options.theme]);this.options.baseHref=this.options.baseHref||Clientcide.assetLocation+'/PointyTip/';this.options.divot=this.options.divot.substitute(this.options,/\\?\{%([^}]+)%\}/g);if(Browser.Engine.trident4)this.options.divot=this.options.divot.replace(/png/g,'gif');this.options.css=this.options.css.substitute(this.options,/\\?\{%([^}]+)%\}/g);if(args.options&&args.options.theme){while(!this.id){var id=$random(0,999999999);if(!StickyWin.UI.Pointy[id]){StickyWin.UI.Pointy[id]=this;this.id=id;}}
this.options.css=this.options.css.replace(/div\.DefaultPointyTip/g,"div#pointy_"+this.id);this.options.cssId="pointyTipStyle_"+this.id;}
if($type(this.options.direction)=='string'){var map={left:9,right:3,up:12,down:6};this.options.direction=map[this.options.direction];}
this.parent(args.caption,args.body,this.options);if(this.id)document.id(this).set('id',"pointy_"+this.id);},build:function(){this.parent();var opt=this.options;this.pointyWrapper=new Element('div',{'class':'pointyWrapper'}).inject(document.id(this));document.id(this).getChildren().each(function(el){if(el!=this.pointyWrapper)this.pointyWrapper.grab(el);},this);var w=opt.divotSize;var h=w;var left=(opt.width-opt.divotSize)/2;var orient=function(){switch(opt.direction){case 12:case 1:case 11:return{height:h/2};case 5:case 6:case 7:return{height:h/2,backgroundPosition:'0 -'+h/2+'px'};case 8:case 9:case 10:return{width:w/2};case 2:case 3:case 4:return{width:w/2,backgroundPosition:'100%'};};};this.pointer=new Element('div',{styles:$extend({width:w,height:h,overflow:'hidden'},orient()),'class':'pointyDivot pointy_'+opt.direction}).inject(this.pointyWrapper);},expose:function(){if(document.id(this).getStyle('display')!='none'&&document.id(document.body).hasChild(document.id(this)))return $empty;document.id(this).setStyles({visibility:'hidden',position:'absolute'});var dispose;if(!document.body.hasChild(document.id(this))){document.id(this).inject(document.body);dispose=true;}
return(function(){if(dispose)document.id(this).dispose();document.id(this).setStyles({visibility:'visible',position:'relative'});}).bind(this);},positionPointer:function(options){if(!this.pointer)return;var opt=options||this.options;var pos;var d=opt.direction;switch(d){case 12:case 1:case 11:pos={edge:{x:'center',y:'bottom'},position:{x:d==12?'center':d==1?'right':'left',y:'top'},offset:{x:(d==12?0:d==1?-1:1)*opt.divotSize,y:1}};break;case 2:case 3:case 4:pos={edge:{x:'left',y:'center'},position:{x:'right',y:d==3?'center':d==2?'top':'bottom'},offset:{x:-1,y:(d==3?0:d==4?-1:1)*opt.divotSize}};break;case 5:case 6:case 7:pos={edge:{x:'center',y:'top'},position:{x:d==6?'center':d==5?'right':'left',y:'bottom'},offset:{x:(d==6?0:d==5?-1:1)*opt.divotSize,y:-1}};break;case 8:case 9:case 10:pos={edge:{x:'right',y:'center'},position:{x:'left',y:d==9?'center':d==10?'top':'bottom'},offset:{x:1,y:(d==9?0:d==8?-1:1)*opt.divotSize}};break;};var putItBack=this.expose();this.pointer.position($extend({relativeTo:this.pointyWrapper},pos,options));putItBack();},setContent:function(a1,a2){this.parent(a1,a2);this.top[this.h1?'removeClass':'addClass']('noCaption');if(Browser.Engine.trident4)document.id(this).getElements('.bottom_ll, .bottom_lr').setStyle('font-size',1);if(this.options.closeButton)this.body.setStyle('margin-right',6);this.positionPointer();return this;},makeCaption:function(caption){this.parent(caption);if(this.options.width&&this.h1)this.h1.setStyle('width',(this.options.width-(this.options.closeButton?25:15)));}});StickyWin.UI.pointy=function(caption,body,options){return document.id(new StickyWin.UI.Pointy(caption,body,options));};StickyWin.ui.pointy=StickyWin.UI.pointy;
StickyWin.PointyTip=new Class({Extends:StickyWin,options:{point:"left",pointyOptions:{}},initialize:function(){var args=this.getArgs(arguments);this.setOptions(args.options);var popts=this.options.pointyOptions;var d=popts.direction;if(!d){var map={left:9,right:3,up:12,down:6};d=map[this.options.point];if(!d)d=this.options.point;popts.direction=d;}
if(!popts.width)popts.width=this.options.width;this.pointy=new StickyWin.UI.Pointy(args.caption,args.body,popts);this.options.content=null;this.setOptions(args.options,this.getPositionSettings());this.parent(this.options);this.win.empty().adopt(document.id(this.pointy));this.attachHandlers(this.win);if(this.options.showNow)this.position();},getArgs:function(){return StickyWin.UI.getArgs.apply(this,arguments);},getPositionSettings:function(){var s=this.pointy.options.divotSize;var d=this.options.point;var offset=this.options.offset||{};switch(d){case"left":case 8:case 9:case 10:return{edge:{x:'left',y:d==10?'top':d==8?'bottom':'center'},position:{x:'right',y:'center'},offset:{x:s+(offset.x||0),y:offset.y||0}};case"right":case 2:case 3:case 4:return{edge:{x:'right',y:(d==2?'top':d==4?'bottom':'center')+(offset.y||0)},position:{x:'left',y:'center'},offset:{x:-s+(offset.x||0),y:offset.y||0}};case"up":case 11:case 12:case 1:return{edge:{x:d==11?'left':d==1?'right':'center',y:'top'},position:{x:'center',y:'bottom'},offset:{y:s+(offset.y||0),x:(d==11?-s:d==1?s:0)+(offset.x||0)}};case"down":case 5:case 6:case 7:return{edge:{x:(d==7?'left':d==5?'right':'center')+(offset.x||0),y:'bottom'},position:{x:'center',y:'top'},offset:{y:-s+(offset.y||0),x:(d==7?-s:d==5?s:0)+(offset.x||0)}};};},setContent:function(){var args=this.getArgs(arguments);this.pointy.setContent(args.caption,args.body);[this.pointy.h1,this.pointy.body].each(this.attachHandlers,this);if(this.visible)this.position();return this;},showWin:function(){this.parent();this.pointy.positionPointer();},position:function(options){this.parent(options);this.pointy.positionPointer();},attachHandlers:function(content){if(!content)return;content.getElements('.'+this.options.closeClassName).addEvent('click',function(){this.hide();}.bind(this));content.getElements('.'+this.options.pinClassName).addEvent('click',function(){this.togglepin();}.bind(this));}});
Tips.Pointy=new Class({Extends:Tips,options:{onShow:function(tip,stickyWin){stickyWin.show();},onHide:function(tip,stickyWin){stickyWin.hide();},pointyTipOptions:{point:11,width:150,pointyOptions:{closeButton:false}}},initialize:function(){var params=Array.link(arguments,{options:Object.type,elements:$defined});this.setOptions(params.options);this.tip=new StickyWin.PointyTip($extend(this.options.pointyTipOptions,{showNow:false}));if(this.options.className)document.id(this.tip).addClass(this.options.className);if(params.elements)this.attach(params.elements);},elementEnter:function(event,element){var title=element.retrieve('tip:title');var text=element.retrieve('tip:text');this.tip.setContent(title,text);this.timer=$clear(this.timer);this.timer=this.show.delay(this.options.showDelay,this);this.position(element);},elementLeave:function(event){$clear(this.timer);this.timer=this.hide.delay(this.options.hideDelay,this);},elementMove:function(event){return;},position:function(element){this.tip.setOptions({relativeTo:element});this.tip.position();},show:function(){this.fireEvent('show',[document.id(this.tip),this.tip]);},hide:function(){this.fireEvent('hide',[document.id(this.tip),this.tip]);}});var Moo = {};
Moo.Animable = {};
Moo.Animation = {};
Moo.Form = {};
Moo.Form.Request = {};
Moo.Request = {};
Moo.Selector = {};
Moo.UI = {};
Moo.UI.Navigation = {};
Moo.UI.List = {};
Moo.UI.Window = {};
Moo.Command = {};// +---------------------------------------------------------------------------+
// | 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 Lemieux BÃ©dard (info@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 Lemieux BÃ©dard (info@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 Lemieux BÃ©dard (info@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 Lemieux BÃ©dard (info@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.                          |
// +---------------------------------------------------------------------------+

Native.implement([Element, Document], {
	/**
	 * Return all the element that match a given selector using the Sly engine.
	 * @param string The selector string.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.1.0
	 */
/*
	getElements: function(tags, nocash) {
		return Sly(tags).search(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.                          |
// +---------------------------------------------------------------------------+

Element.implement
({	
	/**
	 * 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_' + property, null);
		this.store('property_' + 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_' + 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');
	},
	
	/**
	 * Return the anchor in an url.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */	
	getLinkAnchor: function() {
		var href = this.getProperty('href');
		if (href == null) href = this.getDisposedHrefProperty();
		if (href) {
			var regex = new RegExp('\#([0-9A-Za-z]+)$');
			var match = regex.exec(href);
			if (match) {
				return match[1];
			}
		}
		return null;
	},
	
	/**
	 * 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('style_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('style_display');
		if (display && display != 'none') this.store('style_display', display);
		this.setStyle('display', 'none');
	}
});// +---------------------------------------------------------------------------+
// | 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, '');
	},

	toCharacters: 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];
	},
	
	shorten: function(at, animate) {},
	expand: function(options) {}
});

function str_repeat(i, m) {
	for (var o = []; m > 0; o[--m] = i);
	return(o.join(""));
}

function sprintf() {
	var i = 0, a, f = arguments[i++], o = [], m, p, c, x, s = '';
	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;
			}
			if (/[def]/.test(m[7])) {
				s = (a >= 0 ? (m[2] ? '+' : '') : '-');
				a = Math.abs(a);
			}
			c = m[3] ? m[3] == '0' ? '0' : m[3].charAt(1) : ' ';
			x = m[5] - String(a).length - s.length;
			p = m[5] ? str_repeat(c, x) : '';
			o.push(s + (m[4] ? a + p : p + a));
		}
		else {
			throw("Huh ?!");
		}
		f = f.substring(m[0].length);
	}
	return o.join("");
}// +---------------------------------------------------------------------------+
// | 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.Singleton = new Class
({
	initialize: function(definition, options) {
		var singleton = new Class(definition);
		return new singleton(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.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('::');
				this.getElements(pair[0]).each(function(elem) {
					if (pair.length == 1) return rule(elem);
					elem.addEvent(pair[1], rule.pass(elem));
				});
			}, this);
		}		
	},

	/**
	 * Return the elements that were matched by a rule.
	 * @param string The selector.
	 * @return array The elements.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */
	getElements: function(rule) {	
		return $$(rule);
	}
});
// +---------------------------------------------------------------------------+
// | 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.implement({

	/**
	 * Return the elements that were matched by a rule.
	 * @param string The selector.
	 * @return array The elements.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.1.0
	 */
	getElements: function(rule) {	
		return Moo.Selector.ExecutionContainer.hasContainer() 
			?  Moo.Selector.ExecutionContainer.getContainer().getElement(rule)
			:  $$(rule);
	}
});

/**
 * Moo.Selector.ExecutionContainer is a class that containt the root element
 * used by all the future selectors.
 * @package Selector
 * @author Ross Lawley
 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
 * @copyright Ross Lawley
 * @since 2.1.0
 * @version 2.1.0
 */
Moo.Selector.ExecutionContainer = new Class.Singleton
({
	/**
	 * @var object The execution container.
	 */
	container: null,

	/**
	 * Set the container.
	 * @param object The container.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */
	setContainer: function(container) {
		this.container = container;
	},
	
	/**
	 * Return the container.
	 * @return object The container.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */	
	getContainer: function() {
		return this.container;
	},
	
	/**
	 * Indicate whether or not a container has been set.
	 * @return object The container.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */	
	hasContainer: function() {
		return this.container != 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.Request.Element
 * This class handles a form by sending the form contents using a json request. Just
 * a note, if you're loading a specific element, you might want to turn the evalScripts
 * option off. If you're using selectors, they won't be executed until the response
 * is added back to the document.
 * @package Request
 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
 * @copyright Lemieux BÃ©dard (info@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.
	 */
	elementId: 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(elementId) {	
		this.elementId = elementId;
		new Request.HTML({
			url: this.url, 
			method: this.options.method, 
			evalScripts: this.options.evalScripts,
			headers: {'Request-For': 'partial'},
			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) {
		var response = responseHtml.match(/<body[^>]*>([\s\S]*?)<\/body>/i);
		response = (response) ? response[1] : responseHtml;	
		response = new Element('div').set('html', response);		
		// if an element id has been specified we strip the element
		// from the response we return it
		if (this.elementId) {
			var r = response.getElement('*[id=' + this.elementId + ']');
			if (r) {
				this.fireEvent('complete', [r, responseHtml, responseJavaScript, true]);
				return;
			}
		} 
		// at this point there is either no selectors or the selector used returned
		// nothing in particular
		this.fireEvent('complete', [response, responseHtml, responseJavaScript, false]);			
	}
});// +---------------------------------------------------------------------------+
// | This file is part of the Mootools Addon project.                          |
// | Copyright (C) Original Authors                                            |
// |                                                                           |
// | For the full copyright and license information, please view the LICENSE   |
// | file that was distributed with this source code.                          |
// +---------------------------------------------------------------------------+ 
 
/**
 * A workaround for IE issues in mootools 1.2.1
 * Recreates FX.Scroll() but utilises 1.2.0's getPosition/getOffset routines.
 */
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  = document.id(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 = document.id(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.                          |
// +---------------------------------------------------------------------------+

/**
 * Create a rich text editor.
 * @package UI
 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
 * @copyright Lemieux BÃ©dard (info@lemieuxbedard.com)
 * @since 2.0.1
 * @version 2.0.1
 */
Moo.UI.Editor = new Class
({	
	/**
	 * @extends 
	 */
	Extends: MooEditable	
});

Element.implement({
	editor: function(options) {
		return this.mooEditable(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 navigation item.
 * @package UI
 * @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.Item = new Class
({		
	/**
	 * @implements Events Options.
	 */
	Implements: [Events, Options, Class.Occlude],

	/**
	 * @property moo.ui.navigation.tab
	 */
	property: 'moo.ui.navigation.item',

	/**
	 * @var object The options.
	 */
	options: {
		onEmphasize: $empty,
		onNormalize: $empty,
		emphasizeClass: 'alt',
		normalizeClass: 'cur'
	},
	
	/**
	 * @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 = document.id(element);
		if (this.occlude()) return this.occluded;
		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.emphasizeClass);
			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.normalizeClass);
			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.emphasizeClass);
	}	
});// +---------------------------------------------------------------------------+
// | 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.Singleton
({
	/**
	 * @extends Roar.
	 */
	Extends: Roar,
	
	/**
	 * Initialize the notifier.
	 * @param object A reference to this object.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.1
	 */	
	initialize: function() {
		this.roar = new Roar({
			position: 'upperRight'
		});
		return this;
	},
	
	/**
	 * Show a notification.
	 * @param string The message title.
	 * @param string The message content.
	 * @param object The options.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.1
	 */
	show: function(title, message, options) {
		this.roar.alert(title, message, options);
	}
});

/**
 Basic CSS

.roar-body {
	position: absolute;
	font: 12px/14px "Lucida Grande", Arial, Helvetica, Verdana, sans-serif;
	color: #fff;
	text-align: left;
	z-index: 999;
}

.roar {
	position: bsolute;
	width: 300px;
	cursor: pointer;
}

.roar-bg {
	position: absolute;
	z-index: 1000;
	width: 100%;
	height: 100%;
	left: 0;
	top: 0;
	background-color: #000;
	-moz-border-radius: 10px;
	-webkit-border-radius: 5px;
	-webkit-box-shadow: 0 0 5px rgba(0, 0, 0, 0.5);
}

.roar-body-ugly .roar {
	background-color: 333;
}

.roar-body-ugly .roar-bg {
	display: none;
}

.roar h3 {
	position: relative;
	padding: 15px 10px 0;
	margin: 0;
	border: 0;
	font-size: 13px;
	color: #fff;
	z-index: 1002;
}

.roar p {
	position: relative;
	padding: 10px 10px 15px;
	margin: 0;
	font-size: 12px;
	color: #fff;
	z-index: 1002;
}

 */// +---------------------------------------------------------------------------+
// | 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 Lemieux BÃ©dard (info@lemieuxbedard.com)
 * @since 2.0.1
 * @version 2.0.1
 */
Moo.UI.Progress = new Class
({	
	/**
	 * @extends 
	 */
	Extends: MoogressBar,
	
	/**
	 * Set the progressbar percentage.
	 * @param int The value.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0	 
	 */
	setValue: function(value) {
		this.setPercentage(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.                          |
// +---------------------------------------------------------------------------+

/**
 * Creates a div within the page with the specified contents at the location 
 * relative to the element you specify; basically an in-page popup maker.
 * @package UI
 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
 * @copyright Lemieux BÃ©dard (info@lemieuxbedard.com)
 * @since 2.0.1
 * @version 2.0.1
 */
Moo.UI.Window = new Class
({
	/**
	 * @extends StickyWin
	 */
	Extends: StickyWin
});// +---------------------------------------------------------------------------+
// | 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.                          |
// +---------------------------------------------------------------------------+

/**
 * Creates a div within the page with the specified contents at the location 
 * relative to the element you specify; basically an in-page popup maker.
 * @package UI
 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
 * @copyright Lemieux BÃ©dard (info@lemieuxbedard.com)
 * @since 2.0.1
 * @version 2.0.1
 */
Moo.UI.Window.Modal = new Class
({
	/**
	 * @extends StickyWin
	 */
	Extends: StickyWin.Modal
});// +---------------------------------------------------------------------------+
// | 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.                          |
// +---------------------------------------------------------------------------+

/**
 * Creates a div within the page with the specified contents at the location 
 * relative to the element you specify; basically an in-page popup maker.
 * @package UI
 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
 * @copyright Lemieux BÃ©dard (info@lemieuxbedard.com)
 * @since 2.0.1
 * @version 2.0.1
 */
Moo.UI.Window.Ajax = new Class({ Extends: StickyWin.Ajax });
Moo.UI.Window.Modal.Ajax = new Class({ Extends: StickyWin.Modal.Ajax });

/*
---

script: StickyWin.Ajaxjs

descript: Adds ajax functionality to all the StickyWin classes.

license: MIT-Style License

requires:
- Core:1.2.4/Request
- /StickyWin
- /StickyWin.UI

provides:
- StickyWin.Ajax
- StickyWin.Modal.Ajax

...
*/
(function(){
	var SWA = function(extend){
		return {
			Extends: extend,
			options: {
				//onUpdate: $empty,
				url: '',
				showNow: false,
				requestOptions: {
					method: 'get',
					evalScripts: true
				},
				wrapWithUi: false, 
				caption: '',
				uiOptions:{},
				handleResponse: function(response){
					var responseScript = "";
					this.Request.response.text.stripScripts(function(script){	responseScript += script; });
					if (this.options.wrapWithUi) response = StickyWin.ui(this.options.caption, response, this.options.uiOptions);
					this.setContent(response);
					this.show();
					if (this.evalScripts) $exec(responseScript);
					this.fireEvent('update');
				}
			},
			initialize: function(options){
				var showNow;
				if (options && options.showNow) {
					showNow = true;
					options.showNow = false;
				}
				this.parent(options);
				this.evalScripts = this.options.requestOptions.evalScripts;
				this.options.requestOptions.evalScripts = false;
				this.createRequest();
				if (showNow) this.update();
			},
			createRequest: function(){
				this.Request = new Request(this.options.requestOptions).addEvent('onSuccess',
					this.options.handleResponse.bind(this));
			},
			update: function(url, options){
				this.Request.setOptions(options).send({url: url||this.options.url});
				return this;
			}
		};
	};
	try {	Moo.UI.Window.Tip.Ajax = new Class(SWA(Moo.UI.Window.Tip)); } catch(e){}
})();
// +---------------------------------------------------------------------------+
// | 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.                          |
// +---------------------------------------------------------------------------+

/**
 * Creates a div within the page with the specified contents at the location 
 * relative to the element you specify; basically an in-page popup maker.
 * @package UI
 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
 * @copyright Lemieux BÃ©dard (info@lemieuxbedard.com)
 * @since 2.0.1
 * @version 2.0.1
 */
Moo.UI.Window.Tip = new Class
({ 
	/**
	 * @extends StickyWin.PointyTip
	 */
	Extends: StickyWin.PointyTip
});
// +---------------------------------------------------------------------------+
// | 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.                          |
// +---------------------------------------------------------------------------+

/**
 * Filter elements from a list.
 * 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.UI.List.Filter = new Class
({
	/**
	 * @implements Events, Options.
	 */
	Implements: [Events, Options, Class.Occlude],

	/**
	 * @property Moo.UI.Tag.Browse.
	 */
	property: 'Moo.UI.List.Filter',
	
	/**
	 * @var object The root element.
	 */ 
	element: null,
	
	/**
	 * @var object The child elements.
	 */
	elements: null,
	
	/**
	 * The item selector.
	 */
	path: null,

	/**
	 * @var object The options.
	 */
	options: {
		onMiss: $empty,
		onMatch: $empty,
		missClass: 'miss',
		matchClass: 'match'
	},

	/**
	 * Initialize the text filter.
	 * @param object The root element of the searchable tree.
	 * @param string The path to search.
	 * @param object The options.
	 * @author Jean-Philippe DÃ©ry (jean-philippe.dery@lemieuxbedard.com)
 	 * @since 2.0.0
	 */
	initialize: function(root, path, options) {
		this.setOptions(options);
		this.element  = document.id(root);		
		this.elements = document.id(root).getElements(path);
		this.path = path;
		if (this.occlude()) return this.occluded();
		return this;
	},
	
	/**
	 * 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(search) {
		this.clear();
		if (search.length > 0) {
			search = search.trim();
			this.elements = this.element.getElements(this.path);
			this.elements.each(function(element) {
				var match = this.match(search, element);
				if (match) {
					this.fireEvent('match', element);
					element.addClass(this.options.matchClass);
				} else {
					this.fireEvent('miss', element);
					element.addClass(this.options.missClass);
				}
			}, this);
		}
	},
	
	/**
	 * Indicate whether or not the element contains the search string.
	 * @param string The search string.
	 * @param object The element to search inside.
	 * @return bool Whether or not the element contains the string.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */
	match: function(search, element) {
		var val = search.escapeRegExp()
		var reg = new RegExp(val, 'mig');
		var txt = element.get('text');
		return txt ? txt.match(reg) : false;
	},
	
	/**
	 * 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() {
		this.element.getElements('.' + this.options.missClass).removeClass(this.options.missClass);
		this.element.getElements('.' + this.options.matchClass).removeClass(this.options.matchClass);
	}
});// +---------------------------------------------------------------------------+
// | 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.                          |
// +---------------------------------------------------------------------------+

/**
 * Filter elements from a list.
 * 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.1.0
 * @version 2.1.0
 */
Moo.UI.List.Filter.Bindable = new Class
({
	/**
	 * @extends Moo.UI.List.Filter
	 */
	Extends: Moo.UI.List.Filter,
	
	/**
	 * @var object The textbox to bind to.
	 */
	textbox: null,
	
	/**
	 * @var int The previous textbox length.
	 */
	textboxLength: 0,
	
	/**
	 * @var object The interval to search within.
	 */
	interval : null,
	
	/**
	 * Bind the filter with a textbox.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.1.0
	 */
	bind: function(textbox) {
		this.textbox = document.id(textbox);
		this.textbox.addEvents({
			keyup: this.onValueChange.bind(this),
			keydown: this.onValueChange.bind(this)
		});
		return this;
	},
	
	/**
	 * This event happens when the value of the textbox change.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.1.0
	 */	
	onValueChange: function(e) {
		if (this.testboxLength != this.textbox.value.length) {
			this.textboxLength = this.textbox.value.length;
			$clear(this.interval);
			if (this.textboxLength > 1) {
				this.interval = this.filter.delay(300, this, this.textbox.value);
			} else {
				this.clear();
			}			
		}
	}
});// +---------------------------------------------------------------------------+
// | 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,
		onCancel: $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;
		}
	},
	
	/**
	 * 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();
	},
	
	/**
	 * Cancel the request.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */	
	cancel: function()Â {
		if (this.loading == true) {
			this.loading = false;
			this.request.cancel();
			this.fireEvent('cancel');
		}
	},

	/**
	 * 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.Commands = new Class.Singleton
({
	/**
	 * @var array The commands.
	 */
	commands: [],
	
	setCommand: function(name, url) {
		this.commands[name] = url;
	},
	
	getCommand: function(name) {
		return this.hasCommand(name) ? this.commands[name] : null;
	},
	
	hasCommand: function(name) {
		return this.commands[name] != undefined;
	},
	
	retrieve: function(name, options) {
		return this.hasCommand(name) ? new Moo.Command(this.commands[name], options) : 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.Controller = new Class
({
	/**
	 * @var object The view.
	 */
	view: null,
	
	/**
	 * Initialize the view controller.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */
	init: function(view) {
		this.view = view;
	}	
});// +---------------------------------------------------------------------------+
// | 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.Controller.Form = new Class
({
	/**
	 * @extends Moo.Controller
	 */
	Extends: Moo.Controller,
	
	/**
	 * @var object The form.
	 */
	form: null,
	
	/**
	 * Initialize the view controller.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */
	init: function(view, form) {
		this.parent(view);
		form = form ? form : this.view.getElement('form');
		this.form = new Moo.Form.Request.JSON(form, {
			onError: this.onFormSubmitError.bind(this),
			onRequest: this.onFormSubmitRequest.bind(this),
			onSuccess: this.onFormSubmitSuccess.bind(this),
			onComplete: this.onFormSubmitComplete.bind(this),
			onFailure: this.onFormSubmitFailure.bind(this),
			onException: this.onFormSubmitFailure.bind(this)
		});
	},
	
	/**
	 * Those are events associated with the creation of an application.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */
	onFormSubmitRequest: function() {},

	/**
	 * Those are events associated with the creation of an application.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */	
	onFormSubmitSuccess: function() {},
	
	/**
	 * Those are events associated with the creation of an application.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */	
	onFormSubmitComplete: function() {},
	
	/**
	 * Those are events associated with the creation of an application.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */		
	onFormSubmitError: function() {},
	
	/**
	 * This event happens when a failure occurs.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */	
	onFormSubmitFailure: function(xhr) {
		alert('Error processing the 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.                          |
// +---------------------------------------------------------------------------+

/**
 * Handle errors on a form. Errors must be given in json format.
 * @package Forms
 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
 * @copyright Lemieux BÃ©dard (info@lemieuxbedard.com)
 * @since 2.0.0
 * @version 2.0.0
 */
Moo.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.                          |
// +---------------------------------------------------------------------------+

/**
 * Handle a form using ajax.
 * @package Forms
 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
 * @since 2.1.0
 * @version 2.1.0
 */
Moo.Form.Request.JSON = new Class
({
	/**
	 * @extends Moo.Request.JSON
	 */
	Extends: Form.Request,
	
	/**
	 * @var object The options.
	 */
	options: {
		onSuccess: $empty,
		onError: $empty,
		errorHandling: true,
		errorHandler: null,
		resetForm: false,
		reset: false
	},

	/**
	 * Initialize.
	 * @author Original authors.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.1.0
	 */
	initialize: function(form, options) {
		return this.parent(form, null, options);
	},

	/**
	 * Load the request object.
	 * @author Original authors.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.1.0
	 */
	makeRequest: function() {
		if (this.options.resetForm == true) {
			this.options.resetForm = false;
			this.options.reset = true;
		}
		this.request = new Request.JSON($merge({
			emulation: false,
			spinnerTarget: this.element.getElement('input[type=submit]'),
			method: this.element.get('method') || 'post'
		}, this.options.requestOptions)).addEvents({
			request: this.onRequest.bind(this),
			complete: this.onComplete.bind(this),
			failure: this.onFailure.bind(this),
			exception: this.onException.bind(this)
		});
	},

	/**
	 * Event when the form sends it's request.
	 * @return Void.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */
	onRequest: function(e) {
		this.fireEvent('request');
	},

	/**
	 * Event when the form the request is completed.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */	
	onComplete: function(json, text) {
		if (json) {
			this.fireEvent('complete', json);
			switch (json.status) {
				case 'success': 
					this.removeErrors();
					this.fireEvent('success', [json.message, json.code, json]);
					if (this.options.reset) {
						$try(function() { 
							this.element.reset();
						}.bind(this));
					}
					break;
				case 'error':
					this.removeErrors();
					this.displayErrors(json.message);
					this.fireEvent('error', [json.message, json.code, json]);
					break;
				case 'redirect':
					this.removeErrors();
					this.fireEvent('success', [json.message, json.code, json]);
					window.location = json.message;
					break;
			}
		} else {
			this.fireEvent('failure');
		}
	},
	
	/**
	 * 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 + '"]');
	},	
	
	/**
	 * Event when the form the request fails.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */	
	onFailure: function(xhr) {
		this.fireEvent('complete').fireEvent('failure', xhr);
	},	
	
	/**
	 * Event when the form the request thrown an exception.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */	
	onException: function(xhr) {
		this.fireEvent('failure', xhr);
	},
	
	/**
	 * Display the errors.
	 * @param array The error messages.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */
	displayErrors: function(message) {
		if (this.options.errorHandling) {
			this.options.errorHandler = this.options.errorHandler ? this.options.errorHandler : new Moo.Form.Errors(this);
			this.options.errorHandler.handleErrors(message);
		}
	},

	/**
	 * Remove the errors.
	 * @param array The error messages.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.0.0
	 */	
	removeErrors: function() {
		if (this.options.errorHandling) {
			this.options.errorHandler = this.options.errorHandler ? this.options.errorHandler : new Moo.Form.Errors(this);
			this.options.errorHandler.removeErrors();
		}
	}	
});// +---------------------------------------------------------------------------+
// | 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 create a ghost of the draged element and return it to place
 * once dropped.
 * @package Drag
 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
 * @copyright Lemieux Bedard Communication (francois.patry@lemieuxbedard.com)
 * @since 2.1.0
 * @version 2.1.0
 */
Drag.Ghost = new Class
({
	/**
	 * @implements Events, Options.
	 */
	Implements: [Events, Options, Class.Occlude],
	
	/**
	 * @property Drag.Ghost
	 */
	property: 'Drag.Ghost',

	/**
	 * @var object The options.
	 */
	options: {
		onStart: $empty,
		onDrop: $empty,
		dragClassName: 'dragged',
		dragOpacity: 0.7
	},

	/**
	 * @var object The element.
	 */
	element: null,
	
	/**
	 * @var object The ghost.
	 */
	ghost: null,
	
	/**
	 * Initialize.
	 * @param mixed The element to make draggable.
	 * @param object The options.
	 * @author Jean-Philippe DÃ©ry (jean-philippe.dery@lemieuxbedard.com)
 	 * @since 2.1.0
	 */	
	initialize: function(element, options) {
		this.setOptions(options);
		this.element = document.id(element);
		if (this.occlude()) return this.occluded;
		this.element.addEvent('mouseup', this.onElementMouseUp.bind(this));
		this.element.addEvent('mousedown', this.onElementMouseDown.bind(this));
		return this;
	},

	/**
	 * Reset the ghost 
	 * @param object The event.
	 * @return void
	 * @author Jean-Philippe DÃ©ry (jean-philippe.dery@lemieuxbedard.com)
 	 * @since 2.1.0
	 */
	resetGhost: function() {
		new Fx.Morph(this.ghost).start({
			'top': this.element.getCoordinates().top,
			'left': this.element.getCoordinates().left,
			'opacity': 0
		}).chain(function() {
			this.clearGhost();
		}.bind(this));
	},

	/**
	 * The event on a mouse up event.
	 * @param object The event.
	 * @return void
	 * @author Jean-Philippe DÃ©ry (jean-philippe.dery@lemieuxbedard.com)
 	 * @since 2.1.0
	 */
	clearGhost: function() {
		if (this.ghost) {
			this.ghost.dispose();
			this.ghost = null;
		}
	},

	/**
	 * The event on a mouse up event.
	 * @param object The event.
	 * @return void
	 * @author Jean-Philippe DÃ©ry (jean-philippe.dery@lemieuxbedard.com)
 	 * @since 2.1.0
	 */
	onElementMouseUp: function(e) {},
		
	/**
	 * The event on a mouse down event.
	 * @param object The event.
	 * @return void
	 * @author Jean-Philippe DÃ©ry (jean-philippe.dery@lemieuxbedard.com)
 	 * @since 2.1.0
	 */
	onElementMouseDown: function(e) {
		e.stop();
		if (this.ghost == null) {
			this.ghost = this.element.clone();
			this.ghost.removeProperty('style');
			this.ghost.addClass(this.options.dragClassName);
			this.ghost.setStyles(this.element.getCoordinates())
			this.ghost.setStyle('opacity', this.options.dragOpacity);
			this.ghost.setStyle('position', 'absolute');
			this.ghost.store('id', this.element.get('id'));
			this.ghost.store('parent', this.element);
			this.ghost.inject(document.body);
			this.ghost.makeDraggable(
				$merge(this.options, {
					onDrop: this.onDrop.bind(this)
				})
			).start(e);
			this.fireEvent('start', [this.ghost]);
		}
	},
	
	/**
	 * The event when the element is dropped.
	 * @param object The element.
	 * @param object The dropped in element.
	 * @param object The event.
	 * @return void
	 * @author Jean-Philippe DÃ©ry (jean-philippe.dery@lemieuxbedard.com)
 	 * @since 2.1.0
	 */	
	onDrop: function(element, droppable, e) {
		this.fireEvent('drop', [element, droppable, e]);
		if (droppable) this.clearGhost();
		else this.resetGhost();
	}
});

Element.implement({
	makeGhostDraggable: function(options) {
		return new Drag.Ghost(this, options);
	}
});Moo.Administration = {};// +---------------------------------------------------------------------------+
// | 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.                          |
// +---------------------------------------------------------------------------+

/**
 * Manage a series of tags.
 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
 * @copyright Lemieux BÃ©dard (info@lemieuxbedard.com)
 * @since 2.1.0
 * @version 2.1.0
 */
Moo.Administration.Tags = new Class
({
	/**
	 * @implements Events, Options, Class.Occlude
	 */
	Implements: [Events, Options, Class.Occlude],
	
	/**
	 * @property The Class.Occlude property name.
	 */
	property: 'Moo.Administration.Tags',
	
	/**
	 * @var object The tags container.
	 */
	element: null,
	
	/**
	 * @var object The tags.
	 */
	elements: null,
	
	/**
	 * @var object The tips.
	 */
	tips: null,
	
	/**
	 * @var object The options.
	 */
	 options: {
	 	onAdd: $empty,
	 	onRemove: $empty,
	 	onSelect: $empty,
	 	selector: 'li',
	 	checkIdFormat: 'fe-tag-%s',
	 	checkNameFormat: 'tags[%s]',
	 	labelClassName: 'label',
	 	wordsClassName: 'words',
	 	checkClassName: 'check'
	 },
	
	/**
	 * Initialize the tags.
	 * @param object The tags container.
	 * @return object This object
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.1.0
	 */			
	initialize: function(tags, options) {

		// settings
		this.setOptions(options);
		this.element = document.id(tags);
		
		// occlude
		if (this.occlude()) return this.occluded;
		
		// events
		this.elements = this.element.getElements(this.options.selector);
		this.elements.each(
			function(el) {
				this.addTagEvents(el);
				el.store('tip:title', el.getElement('.label').get('text'));
				el.store('tip:text',  el.getElement('.words').get('text').replace('/,/g', ', '));
			},
		this);
		
		// tips
		this.tips = new Tips(this.elements, {
			className: 'tag-tip',
			fixed: true,
			hideDelay: 50,
			showDelay: 50,
			offset: {
				x: 0,
				y: 25
			}
		});
		
		return this;
	},
			
	/**
	 * Create a series of tags.
	 * @param array The tags.
	 * @param stirng The tag class name.
	 * @return object This object
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */
	addTags: function(tags, className) {
		tags.each(function(tag) { this.addTag(tag, className); }, this);
		return this;
	},
	
	/**
	 * Add a tag to the list. 
	 * @param object The tag structure.
	 * @param string The tag class name.
	 * @return object This object.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */			
	addTag: function(tag, className) {
		if (this.hasTag(tag) == false) {
			var t = this.create(tag, className);
			t.inject(this.element);
			t.fade('hide');
			t.fade(1);
			if (this.tips) 
				this.tips.attach(t);
			this.fireEvent('add', [tag, t]);
		}
		return this;
	},
	
	/**
	 * Add events to a tag.
	 * @param object The tag element.
	 * @return object The tag.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */	
	addTagEvents: function(element) {
		element.addEvent('click',		this.onTagClick.bind(this));
		element.addEvent('mouseenter', 	this.onTagMouseEnter.bind(this));
		element.addEvent('mouseleave', 	this.onTagMouseLeave.bind(this));
		return element;
	},
		
	/**
	 * Return a tag.
	 * @param object The tag structure.
	 * @return object The tag.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */
	getTag: function(tag) {
		 var t = this.element.getElement('input[value=' + tag.id + ']');
		 return t ? t.getParent('li') : null;
	},	
	
	/**
	 * Find whether or not a tag exists.
	 * @param object The tag structure.
	 * @return bool The if the tag exists.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */			
	hasTag: function(tag) {
		return this.getTag(tag) != null;
	},	
	
	/**
	 * Remove a given tag from the list.
	 * @param object The tag structure.
	 * @return object This object.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */		
	removeTag: function(tag) {
		var t = this.getTag(tag);
		if (t) {
			new Fx.Tween(t).start('opacity', 0).chain(function() {
				this.fireEvent('remove', [tag, t]);
				if (this.tips) 
					this.tips.detach(t);
				t.dispose();
				t = null;
			});
		}
	},
	
	/**
	 * Remove all the tags.
	 * @return object This object
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */			
	removeTags: function() {
		this.element.getElements('li').dispose();
		return this;
	},
	
	/**
	 * Remove all the computer generated tags.
	 * @return object This object
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */			
	removeTagsWithClassName: function(className) {
		this.element.getElements('li.' + className).dispose();
		return this;
	},

	/**
	 * Create a new container for a tag.
	 * @param int The id.
	 * @param string The tag label.
	 * @param string The tag words.
	 * @param string The tag className.
	 * @return object The tag container.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0			 
	 */
	create: function(tag, className) {
		
		// create
		var el = 
			new Element('li')
				.set('class', className)
				.grab(
					new Element('div')
						.set('class', this.options.labelClassName)
						.set('text', tag.label)
				)
				.grab(
					new Element('div')
						.set('class', this.options.wordsClassName)
						.set('text', tag.words)
				)
				.grab(
					new Element('input')
						.set('class', this.options.checkClassName)
						.set('type', 'checkbox')
						.set('id',   sprintf(this.options.checkIdFormat, tag.id))
						.set('name', sprintf(this.options.checkNameFormat, tag.id))
						.set('value', tag.id)
						.set('checked', 'checked')
				);
				
		// add events
		el = this.addTagEvents(el);

		// prepare for tips
		if (this.tips) {
			el.store('tip:title', el.getElement('.label').get('text'));
			el.store('tip:text',  el.getElement('.words').get('text').replace('/,/g', ', '));
		}

		return el;
	},	
	
	/**
	 * Convert a tag element to a structure.
	 * @param object The element.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */		
	convertElementToStructure: function(element) {
		return {
			id: 	element.get('id') ? element.get('id').toNumber() : element.getElement('input').value,
			label: 	element.getElement('.label').get('text'),
			words: 	element.getElement('.words').get('text')
		};
	},
		
	/**
	 * Tag Click Event.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */			
	onTagClick: function(e) {
		var li = e.target;
		if (li.get('tag') != 'li') li = li.getParent('li');
		var tag = this.convertElementToStructure(li);
		this.removeTag(tag);
		this.fireEvent('select', [tag, li]);
	},
	
	/**
	 * Tag Mouse Enter Event.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */			
	onTagMouseEnter: function(e) {},
	
	/**
	 * Tag Mouse Leave Event.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */			
	onTagMouseLeave: function(e) {}
});// +---------------------------------------------------------------------------+
// | 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.                          |
// +---------------------------------------------------------------------------+

/**
 * Manage a series of tags.
 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
 * @copyright Lemieux BÃ©dard (info@lemieuxbedard.com)
 * @since 2.1.0
 * @version 2.1.0
 */
Moo.Administration.TagBrowser = new Class
({
	/**
	 * @implements Events, Options, Class.Occlude
	 */
	Implements: [Events, Options, Class.Occlude],
	
	/**
	 * @property The Class.Occlude property name.
	 */
	property: 'Moo.Administration.TagBrowser',
	
	/**
	 * @var object The browser.
	 */
	element: null,

	/**
	 * @var object The receiver.
	 */
	receiver: null,
	
	/**
	 * @var object The filter.
	 */
	filter: null,
	
	/**
	 * @var object The tag object.
	 */
	tags: null,
	
	/**
	 * @var object The slider.
	 */
	slider: null,
	
	/**
	 * @var bool The visibility status.
	 */
	sliderVisible: false,
	
	/**
	 * @var object The create tag label.
	 */
	tagLabel: null,

	/**
	 * @var object The create tag world.
	 */
	tagWords: null,
	
	/**
	 * @var object The create tag label.
	 */	
	tagSubmit: null,
	
	/**
	 * @var object The options.
	 */
	options: {
		onShow: $empty,
		onHide: $empty,
		createTagCommand: 'tag.create',
		updateTagCommand: 'tag.update'
	},
	
	/**
	 * @var object The create tag command.
	 */
	createTagCommand: null,
	
	/**
	 * @var object The update tag commnand.
	 */
	updateTagCommand: null,
	
	/**
	 * Initialize the browser.
	 * @param object The browser.
	 * @return object This object
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.1.0
	 */			
	initialize: function(browser, receiver, options) {
		
		// settings
		this.setOptions(options);
		this.element  = document.id(browser);
		this.receiver = receiver;

		// occlude
		if (this.occlude()) return this.occluded;

		this.initializeSlider();
		this.initializeFilter();
		this.initializeTags();
		this.initializeCreateForm();

		return this;
	},
	
	/**
	 * Initialize the slider.
	 * @return object This object
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.1.0
	 */		
	initializeSlider: function() {
		this.sliderVisible = false;
		this.slider = new Fx.Slide(this.element);
		this.slider.hide();		
		return this;
	},
	
	/**
	 * Initalize the filter.
	 * @return object This object
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.1.0
	 */		
	initializeFilter: function() {
		var textbox = this.element.getElement('.filter');
		if (textbox) {
			this.filter = new Moo.UI.List.Filter.Bindable(this.element, 'li');
			this.filter.bind(textbox);
			// new OverText(textbox);		
		}
		return this;
	},	
	
	/**
	 * Initialize the slider.
	 * @return object This object
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.1.0
	 */		
	initializeTags: function() {
		var tags = this.element.getElement('.browse ul');
		if (tags) {
			this.tags = new Moo.Administration.Tags(tags);
			this.tags.addEvent('add',    this.onTagAdd.bind(this));
			this.tags.addEvent('remove', this.onTagRemove.bind(this));
			this.tags.addEvent('select', this.onTagSelect.bind(this));
		}
		return this;
	},

	/**
	 * Initialize the creation form.
	 * @return object This object
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.1.0
	 */	
	initializeCreateForm: function() {
		this.tagLabel = this.element.getElement('.create-tag-label');
		this.tagWords = this.element.getElement('.create-tag-words');
		this.tagSubmit = this.element.getElement('.create-tag-button');
		this.tagSubmit.addEvent('click', this.onCreateButtonClick.bind(this));
		this.createTagCommand = Moo.Commands.retrieve(this.options.createTagCommand, {
			onCancel: this.onCreateTagCancel.bind(this),
			onRequest: this.onCreateTagRequest.bind(this),
			onComplete: this.onCreateTagComplete.bind(this)
		});
		return this;
	},
	
	/**
	 * Either show or hide the browser.
	 * @return object This object
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.1.0
	 */		
	toggle: function() {
		if (this.sliderVisible) this.hide(); 
		else 					this.show();
		return this;
	},
	
	/**
	 * Show or the browser.
	 * @return object This object
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.1.0
	 */		
	show: function() {
		this.sliderVisible = true;
		this.slider.slideIn();
		this.fireEvent('show');
		return this;
	},
	
	/**
	 * Either show or hide the browser.
	 * @return object This object
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.1.0
	 */		
	hide: function() {
		this.sliderVisible = false;
		this.slider.slideOut();
		this.fireEvent('hide');
		return this;		
	},
	
	/**
	 * Enable or disaable the form.
	 * @param bool Whether or not the form is enabled.
	 * @return object This object
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.1.0
	 */
	enableCreateForm: function(enabled) {
		this.tagLabel.enabled = enabled;
		this.tagWords.enabled = enabled;
		this.tagSubmit.enabled = enabled;
		return this;
	},
	
	/**
	 * Submit the create form.
	 * @return object This object
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.1.0
	 */
	submitCreateForm: function() {
		this.createTagCommand.setParameter('tag[label]', this.tagLabel.value);
		this.createTagCommand.setParameter('tag[words]', this.tagWords.value);
		this.createTagCommand.setParameter('confirm', 1);
		this.createTagCommand.executeJson();
	},

	/**
	 * Handle the form once it's been submitted.
	 * @param string The status.
	 * @param object The response.
	 * @return object This object
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.1.0
	 */
	handleCreateForm: function(status, response) {
		if (status == 'success') {
			this.createTag(response);
			this.tagLabel.value = '';
			this.tagWords.value = '';
		} else {
			var string = '';
			response.each(function(message) { string += message.error + "\n"; }, this);
		}
		return this;
	},

	/**
	 * Create a new tag.
	 * @return object This object
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.1.0
	 */
	createTag: function(tag) {
		this.tags.addTag(tag);
		this.receiver.addTag(tag);
	},
	
	/**
	 * Event when a tag is added to the list.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.1.0	 
	 */
	onTagAdd: function(tag, element) {
		/* Nothing Yet */
	},
	
	/**
	 * Event when a tag is removed from the list.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.1.0	 	 
	 */	
	onTagRemove: function(tag, element) {
		/* Nothing Yet */	
	},

	/**
	 * Event when a tag is selected.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.1.0	 	 
	 */	
	onTagSelect: function(tag, element) {
		this.receiver.addTag(tag);
	},
	
	/**
	 * Event when the create button is clicked.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.1.0	 	 
	 */		
	onCreateButtonClick: function(e) {
		this.submitCreateForm();
	},

	/**
	 * Event when the create tag command is requested.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.1.0	 	 
	 */		
	onCreateTagRequest: function(e) {
		this.enableCreateForm(true);
		var fieldset = this.tagSubmit.getParent('fieldset');
		if (fieldset) {
			fieldset.addClass('activity');
		}
	},

	/**
	 * Event when the create tag command is completed.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.1.0	 	 
	 */		
	onCreateTagComplete: function(response) {
		this.enableCreateForm(false);
		var fieldset = this.tagSubmit.getParent('fieldset');
		if (fieldset) {
			fieldset.removeClass('activity');
		}
		this.handleCreateForm(response.status, response.message);		
	},
	
	/**
	 * Event when the create tag command is cancelled.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.1.0	 	 
	 */	
	onCreateTagCancel: function(e) {
		this.enableCreateForm();
		var fieldset = this.tagSubmit.getParent('fieldset');
		if (fieldset) {
			fieldset.removeClass('activity');
		}		
	}	
});// +---------------------------------------------------------------------------+
// | 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.                          |
// +---------------------------------------------------------------------------+

/**
 * Manage a series of tags.
 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
 * @copyright Lemieux BÃ©dard (info@lemieuxbedard.com)
 * @since 2.1.0
 * @version 2.1.0
 */
Moo.Administration.TagAdvisor = new Class
({
	/**
	 * @implements Events, Options, Class.Occlude
	 */
	Implements: [Events, Options, Class.Occlude],
	
	/**
	 * @property The Class.Occlude property name.
	 */
	property: 'Moo.Administration.TagAdvisor',
	
	/**
	 * @var object The browser.
	 */
	element: null,

	/**
	 * @var object The element length.
	 */
	elementLength: 0,

	/**
	 * @var object The receiver.
	 */
	receiver: null,

	/**
	 * @var object The options.
	 */
	options: {
		getTagsCommand: 'tag.get_from_string',
		textboxActivityClassName: 'activity'
	},

	/**
	 * @var object The command.
	 */
	getTagsCommand: null,

	/**
	 * @var objet The interval to run the command.
	 */
	getTagsCommandInterval: null,

	/**
	 * Initialize the browser.
	 * @param object The browser.
	 * @return object This object
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.1.0
	 */			
	initialize: function(textbox, receiver, options) {

		// settings
		this.setOptions(options);
		this.element = document.id(textbox);
		this.receiver = receiver;

		// occlude
		if (this.occlude()) return this.occluded;

		// events
		this.element.addEvent('blur', this.onTextChange.bind(this));
		this.element.addEvent('keyup', this.onTextChange.bind(this));
		this.element.addEvent('keydown', this.onTextChange.bind(this));

		// commands
		this.getTagsCommand = Moo.Commands.retrieve(this.options.getTagsCommand, {
			onCancel: this.onGetTagByStringCancel.bind(this),
			onRequest: this.onGetTagByStringRequest.bind(this),
			onComplete: this.onGetTagByStringComplete.bind(this)
		});
		
		return this;
	},

	/**
	 * Retrieve all the tags that were found on the textbox's value.
	 * @return object A reference to this object.			 
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */
	getTags: function() {
		this.getTagsCommand.cancel();
		this.getTagsCommand.setParameter('value', this.element.value);
		this.getTagsCommand.executeJson();
		return this;
	},
	
	/**
	 * Add all the tags found to the receiver.
	 * @param array The tags.
	 * @return object A reference to this object.			 
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */	
	addTags: function(tags) {
		this.receiver.addTags(tags);
		return this;
	},	
			
	/**
	 * Event when a key is pressed on the textbox.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */
	onTextChange: function(e) {
		var length = this.element.value.length;
		if (length > 0) {
			// check if the length has changed which means
			// only letter keys were pressed
			if (this.elementLength != length) {
				this.elementLength = length;
				if (this.getTagsCommand.isLoading()) 
					this.getTagsCommand.cancel();
				$clear(this.getTagsCommandInterval);
				this.getTagsCommandInterval = this.getTags.delay(500, this);
			}
		} else {
			this.receiver.removeTags();
		}
	},
	
	/**
	 * Event when the command gets cancelled.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */
	onGetTagByStringCancel: function() {
		this.element.removeClass(this.options.textboxActivityClassName);
	},
	
	/**
	 * Event when the command gets requested.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */	
	onGetTagByStringRequest: function() {
		this.element.addClass(this.options.textboxActivityClassName);
	},

	/**
	 * Event when the command is completed.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */
	onGetTagByStringComplete: function(results) {
		this.element.removeClass(this.options.textboxActivityClassName);
		this.addTags(results.response);
	}
});// +---------------------------------------------------------------------------+
// | 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.                          |
// +---------------------------------------------------------------------------+

/**
 * Manage a series of tags.
 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
 * @copyright Lemieux BÃ©dard (info@lemieuxbedard.com)
 * @since 2.1.0
 * @version 2.1.0
 */
Moo.Administration.Answers = new Class
({
	/**
	 * @implements Events, Options, Class.Occlude
	 */
	Implements: [Events, Options, Class.Occlude],	

	/**
	 * @property The Class.Occlude property name.
	 */
	property: 'Moo.Administration.Answers',
	
	/**
	 * @var object Options.
	 */
	options: {
		listItemIdFormat: 'answer-%s'
	},
	
	/**
	 * Initialize the answers.
	 * @param object The tags container.
	 * @return object This object
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.1.0
	 */	
	initialize: function(element, options) {
	
		// settings
		this.setOptions(options);
		this.element = document.id(element);
	
		// occlude
		if (this.occlude()) return this.occluded();
	
		// define the droppable
		this.answerPickList = this.element.getElement('.pick');
		this.answerPoolList = this.element.getElement('.pool');

		// activate the picked answers
		var pickAnswers = this.element.getElements('.pick ul li.answer');
		if (pickAnswers) {
			pickAnswers.each(function(a) {
				a.makeGhostDraggable({
					dragClassName: 'dragged-answer',
					droppables: this.answerPoolList,
					onStart: this.onPickAnswerDragToPoolList.bind(this),
					onDrop: this.onPickAnswerDropToPoolList.bind(this)
				});
				// add the "more" event and minimize it
				this.minimizeAnswer(a, true)
				a.addEvent('mouseenter', function() { this.maximizeAnswer(a); }.bind(this) );
				a.addEvent('mouseleave', function() { this.minimizeAnswer(a); }.bind(this) );
			},this);
		}
		
		// actiave the answers from the pool
		var poolAnswers = this.element.getElements('.pool ul li.answer');
		if (poolAnswers) {
			poolAnswers.each(function(a) {
				a.makeGhostDraggable({
					dragClassName: 'dragged-answer',
					droppables: this.answerPickList,
					onStart: this.onPoolAnswerDragToPickList.bind(this),
					onDrop: this.onPoolAnswerDropToPickList.bind(this)
				});
				// add the "more" event and minimize it
				this.minimizeAnswer(a, true)
				a.addEvent('mouseenter', function() { this.maximizeAnswer(a); }.bind(this) );
				a.addEvent('mouseleave', function() { this.minimizeAnswer(a); }.bind(this) );
			}, this)
		}
		
		// activate the filters
		new Moo.UI.List.Filter.Bindable(this.answerPickList, 'li').bind(this.answerPickList.getElement('.filter'));
		new Moo.UI.List.Filter.Bindable(this.answerPoolList, 'li').bind(this.answerPoolList.getElement('.filter'));
		
		// hide some message
		this.toggleEmptyListMessage();
	},
	
	/**
	 * Minimize the answer.
	 * @return object This object.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.1.0
	 */	
	minimizeAnswer: function(answer, speedy) {
		var minimized = answer.retrieve('minimized', false);
		if (minimized == false) {
			// it's maximised so we make sure it's larger that 
			// the minimize size
			var size = answer.getSize();
			if (size.y <= 100) {
				minimized = false;
			} else {
				minimized = true;
				if (speedy == true) {
					answer.setStyle('overflow', 'hidden');
					answer.setStyle('height', 100);
				} else {
					answer.setStyle('overflow', 'hidden');
					new Fx.Tween(answer, {duration: 150}).start('height', 100);
				}
			}
		}
		answer.store('minimized', minimized);
		return this;
	},
	
	/**
	 * Maximize the answer.
	 * @return object This object.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.1.0
	 */		
	maximizeAnswer: function(answer) {
		var minimized = answer.retrieve('minimized', false);
		if (minimized) new Fx.Tween(answer, {duration: 150}).start('height', [answer.getSize().y, answer.getScrollSize().y]);
		answer.store('minimized', false);	
		return this;
	},
	
	toggleAnswerSize: function(answer) {
		var minimized = answer.retrieve('minimized', false);
		if (minimized) this.maximize(answer);
		else this.minimize(answer);
		return this;
	},
	
	/**
	 * Either show or hide the container that says the list is empty.
	 * @return object This object.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.1.0
	 */
	toggleEmptyListMessage: function() {
		var elements = [];
		elements.push(this.answerPickList);
		elements.push(this.answerPoolList);
		elements.each(function(item) {
			var	li    = item.getElement('.empty').getParent('li');
			var empty = item.getElements('ul li').length == 1;
			if (empty) li.removeClass('dn');
			else li.addClass('dn');
		});
	},	
	
	/**
	 * Add the answer from the pool list to the selected list.
	 * @param object The answer.
	 * @return object This object
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.1.0
	 */		
	addPickAnswerToPoolList: function(answer) {
		var clone = answer.clone()
		var input = clone.getElement('input');
		if (input) input.checked = false;
		answer.dispose();
		clone.removeProperty('style');
		this.minimizeAnswer(clone, true)
		clone.addEvent('mouseenter', function() { this.maximizeAnswer(clone); }.bind(this) );
		clone.addEvent('mouseleave', function() { this.minimizeAnswer(clone); }.bind(this) );		
		clone.makeGhostDraggable({
			dragClassName: 'dragged-answer',
			droppables: this.answerPickList,
			onStart: this.onPoolAnswerDragToPickList.bind(this),
			onDrop: this.onPoolAnswerDropToPickList.bind(this)
		});
		clone.inject(this.answerPoolList.getElement('ul'));
		this.toggleEmptyListMessage();
		return this;
	},
	
	/**
	 * Add the answer from the selected list to the pool list.
	 * @param object The answer.
	 * @return object This object
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.1.0
	 */			
	addPoolAnswerToPickList: function(answer) {
		var clone = answer.clone();
		var input = clone.getElement('input');
		if (input) input.checked = true;
		answer.dispose();
		clone.removeProperty('style');
		this.minimizeAnswer(clone, true);
		clone.addEvent('mouseenter', function() { this.maximizeAnswer(clone); }.bind(this) );
		clone.addEvent('mouseleave', function() { this.minimizeAnswer(clone); }.bind(this) );		
		clone.makeGhostDraggable({
			dragClassName: 'dragged-answer',
			droppables: this.answerPoolList,
			onStart: this.onPickAnswerDragToPoolList.bind(this),
			onDrop: this.onPickAnswerDropToPoolList.bind(this)
		});
		clone.inject(this.answerPickList.getElement('ul'));
		this.toggleEmptyListMessage();
		return this;
	},
	
	/**
	 * TODO
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.1.0
	 */
	createPoolAnswer: function(id, answer) {
	
		// create the element
		var element = 
			new Element('li')
				.set('id', sprintf(this.options.listItemIdFormat, id))
				.grab(
					new Element('div')
						.set('class', 'text')
						.set('text', answer)
				)
				.grab(
					new Element('div')
						.set('class', 'more')
				)
				.grab(
					new Element('input')
						.set('type', 'checkbox')
						.set('class', 'check')
						.set('name', 'answers[]')
						.set('value', id)
				);
								
		// add events
		element.makeGhostDraggable({
			dragClassName: 'dragged-answer',
			droppables: this.answerPickList,
			onStart: this.onPoolAnswerDragToPickList.bind(this),
			onDrop: this.onPoolAnswerDropToPickList.bind(this)
		});	

		this.minimizeAnswer(element, true)
		element.addEvent('mouseenter', function() { this.maximizeAnswer(element); }.bind(this) );
		element.addEvent('mouseleave', function() { this.minimizeAnswer(element); }.bind(this) );

		// inject		
		element.inject(this.answerPoolList.getElement('ul'), 'top');
	},
	
	/**
	 * Emphasize a given list.
	 * @param object The list.
	 * @return object This object.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.1.0
	 */
	emphasizeList: function(list) {
		new Fx.Morph(list).start({'border-color': '#B7BF26'});
		return this;
	},

	/**
	 * Normalize a given list.
	 * @param object The list.
	 * @return object This object.
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 2.1.0
	 */	
	normalizeList: function(list) {
		new Fx.Morph(list).start({'border-color': '#dfdfdf'});
		return this;
	},
		
	/**
	 * This event happens when an object from the available answers gets 
	 * dragged to the question answers list.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */			
	onPickAnswerDragToPoolList: function(element) {
		this.emphasizeList(this.answerPoolList);
	},
	
	/**
	 * This event happens when an object from the available answers gets 
	 * dropped to the question answers list.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */		
	onPickAnswerDropToPoolList: function(element, droppable, e) {
		if (droppable) this.addPickAnswerToPoolList(element.retrieve('parent'));
		this.normalizeList(this.answerPoolList);
	},
	
	/**
	 * This event happens when an object from the available answers gets 
	 * dragged to the question answers list.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */			
	onPoolAnswerDragToPickList: function(element) {
		this.emphasizeList(this.answerPickList);
	},
	
	/**
	 * This event happens when an object from the available answers gets 
	 * dropped to the question answers list.
	 * @return void
	 * @author Jean-Philippe Dery (jean-philippe.dery@lemieuxbedard.com)
	 * @since 1.0.0
	 */		
	onPoolAnswerDropToPickList: function(element, droppable, e) {
		if (droppable) this.addPoolAnswerToPickList(element.retrieve('parent'));
		this.normalizeList(this.answerPickList);
	}
	
});

