﻿/**
 * Autocompleter
 *
 * @version		1.0rc4
 *
 * @license		MIT-style license
 * @author		Harald Kirschner <mail [at] digitarald.de>
 * @copyright	Author
 */


var Autocompleter = {};

Autocompleter.Base = new Class({

	options: {
		minLength: 3,
		markQuery: true,
		inheritWidth: false,
		maxChoices: 10,
		offset: 0,
		injectChoice: null,
		onSelect: Class.empty,
		onShow: Class.empty,
		onHide: Class.empty,
		customTarget: null,
		className: 'autocompleter',
		zIndex: 9999,
		observerOptions: {},
		fxOptions: {},
		overflown: [],
		target: [],
		indice : '',
		menu : []
	},

	initialize: function(el, options) {
		if (this.element && this.element===$(el))return;
		this.setOptions(options);
		this.element = $(el);
		var test = /\w*(_\d{1,2})$/;
		var found = el.match(test);
		if (found && found.length>1)this.options.indice=found[1];
		this.build();
		this.observer = new Observer(this.element, this.prefetch.bind(this), $merge({
			delay: 400
		}, this.options.observerOptions));
		this.value = this.observer.value;
		this.queryValue = null;
	},

	/**
	 * build - Initialize DOM
	 *
	 * Builds the html structure for choices and appends the events to the element.
	 * Override this function to modify the html generation.
	 */
	build: function() {
		if($chk(this.autocomplete))return;
		this.autocomplete = new Element('div', {
			'class': this.options.className,
			styles: {zIndex: 9990}
		}).injectInside(document.body);
		this.list = new Element('div', {
			'class': 'list',
			styles: {zIndex: 9991}
		}).injectInside(this.autocomplete);

		if ($chk($(this.options.customTarget))) {
			this.choices = this.options.customTarget;
		}else {			
			this.choices = new Element('ul', {
				'class': this.options.className,
				styles: {zIndex: this.options.zIndex}
			}).injectInside(this.list);
			this.clear = new Element('div', {
				'class': 'clear'						  
			}).injectInside(this.list);
			
			this.fix = new OverlayFix(this.autocomplete);
		}
		this.fx = this.autocomplete.effect('opacity', $merge({
			wait: false,
			duration: 200
		}, this.options.fxOptions))
			.addEvent('onStart', function() {
				if (this.fx.now) return;
				this.autocomplete.setStyle('display', '');
				this.fix.show();
			}.bind(this))
			.addEvent('onComplete', function() {
				if (this.fx.now) return;
				this.autocomplete.setStyle('display', 'none');
				this.fix.hide();
			}.bind(this)).set(0);
		this.autocomplete.addEvent('click', function(e){new Event(e).stopPropagation();this.element.focus();this.toggleFocus(true);}.bind(this));

		this.element.setProperty('autocomplete', 'off')
			.addEvent('keydown', this.onCommand.bindWithEvent(this))
			.addEvent('mousedown', this.onCommand.bindWithEvent(this, [true]))
			.addEvent('blur', this.hideDelay.bind(this))
			.addEvent('trash', this.destroy.bind(this));
	},
	
	hideDelay: function() {
		this.time2hide = this.toggleFocus.delay(300, this, false);
	},

	destroy: function() {
		this.autocomplete.remove();
	},

	toggleFocus: function(state) {
		this.focussed = state;
		if (!state)this.hideChoices();
		else this.time2hide = $clear(this.time2hide);
	},

	onCommand: function(e, mouse) {
		if (mouse && this.focussed) this.prefetch.delay(200, this);
		if (e.key && !e.shift) switch (e.key) {
		
			case 'enter':
				if (this.selected && this.visible) {
					this.choiceSelect(this.selected);
					e.stop();
				} return;
			case 'up': case 'down':
				if (this.observer.value != (this.value || this.queryValue)) this.prefetch();
				else if (this.queryValue === null) break;
				else if (!this.visible) this.showChoices();
				else {
					this.choiceOver((e.key == 'up')
						? this.selected.getPrevious() || this.choices.getLast()
						: this.selected.getNext() || this.choices.getFirst() );
				}
				e.stop(); return;
			case 'esc': this.hideChoices(); return;
		}
		this.value = false;
	},

	hideChoices: function() {
		if (!this.visible) return;
		this.visible = this.value = false;
		this.observer.clear();
		this.fx.start(0);
		this.fireEvent('onHide', [this.element, this.choices]);
		this.fix.hide();
	},

	showChoices: function() {
		if (this.visible || !this.choices.getFirst()) return;
		this.visible = true;
		this.choices.getChildren().each(function(e,i){
				if(i<this.options.offset || i>=(this.options.offset+this.options.maxChoices))e.setStyle('display','none');
				else e.setStyle('display','');
				if(e.getProperty('id')=='previous'){e.setStyle('display',(this.options.offset>0)?'':'none')}
				if(e.getProperty('id')=='next'){e.setStyle('display',((this.options.offset+this.options.maxChoices)<this.choices.getChildren().length-2)?'':'none')}
			}.bind(this));
		var pos = this.element.getCoordinates(this.options.overflown);
		this.autocomplete.setStyles({
			left: pos.left,
			top: pos.bottom
		});
		if (this.options.inheritWidth) this.autocomplete.setStyle('width', pos.width);
		this.fix.show();
		this.fx.start(1);
		this.choiceOver(this.choices.getFirst());	
		this.fireEvent('onShow', [this.element, this.choices]);		
	},

	prefetch: function() {
	    if (this.element.value != this.queryValue){
		this.options.target.each(function(t,i){
			  if (t!='' && t+this.options.indice != this.element.id){
				if(t.test('div_')){
					$(t+this.options.indice).getElements('input[type=radio]').each(function(r,j){r.removeProperty('checked');})
					}
				else $(t+this.options.indice).setProperty('value','')
				if(i==4)$(this.options.target[i]+this.options.indice).removeProperty('readonly').removeClass('readonly');
			  }
			}.bind(this));
		}		
		if (this.element.value.length < this.options.minLength) this.hideChoices();
		else if (this.element.value == this.queryValue) this.showChoices();
		else this.query();
	},

	choiceOver: function(el) {
		if (this.selected) this.selected.removeClass('autocompleter-selected');
		this.selected = el.addClass('autocompleter-selected');
	},

	choiceSelect: function(el) {
		this.toggleFocus();
		if(el.respValue){
			el.respValue.getElements('span').each(function(span,i){
				if(this.options.target[i] && this.options.target[i]!=''){
					if(this.options.target[i].test('div_')){
						$(this.options.target[i]+this.options.indice).getElements('input[type=radio]').each(function(r,j){
							if(r.value==span.getText()) r.setProperty('checked',true);
						});
					}else{$(this.options.target[i]+this.options.indice).setProperty('value',span.getText());
					       if(this.options.target[i]=='dc_contributor_first')$(this.options.target[i]+this.options.indice).setProperty('readonly','readonly').addClass('readonly');
					}
				}
			}.bind(this));
		}
		this.observer.value = this.element.value;
		this.fireEvent('onSelect', [this.element], 20);
	},

	/**
	 * markQueryValue
	 *
	 * Marks the queried word in the given string with <span class="autocompleter-queried">*</span>
	 * Call this i.e. from your custom parseChoices, same for addChoiceEvents
	 *
	 * @param		{String} Text
	 * @return		{String} Text
	 */
	markQueryValue: function(txt) {
		return (this.options.markQuery && this.queryValue) ? txt.replace(new RegExp('(([ \t\r\n\v\f]||^)' + this.queryValue.escapeRegExp() + ')', 'ig'), '<span class="autocompleter-queried">$1</span>') : txt;
	},

	/**
	 * addChoiceEvents
	 *
	 * Appends the needed event handlers for a choice-entry to the given element.
	 *
	 * @param		{Element} Choice entry
	 * @return		{Element} Choice entry
	 */
	addChoiceEvents: function(el) {
		return el.addEvents({
			mouseover: this.choiceOver.bind(this, [el]),
			mousedown: this.choiceSelect.bind(this, [el])
		});
	}
});

Autocompleter.Base.implement(new Events);
Autocompleter.Base.implement(new Options);


Autocompleter.Ajax = {};

Autocompleter.Ajax.Xhtml = Autocompleter.Base.extend({

	options: {
		postVar: 'value',
		postData: {},
		parseChoices: null,
		ajaxOptions: {},
		onRequest: function(el){el.addClass('loading')},
		onComplete: function(el){el.removeClass('loading')}
	},

	initialize: function(el, url, options) {
		this.parent(el, options);
		this.buildMenu();
		this.ajax = new Ajax(url, $merge({
			autoCancel: true
		}, this.options.ajaxOptions));
		this.ajax.addEvent('onComplete', this.queryResponse.bind(this));
		this.ajax.addEvent('onFailure', this.queryResponse.bind(this, [false]));
	},
	
	buildMenu: function(){
		if(this.options.menu.length>1){  // Onglets
			this.outer = new Element('div', {
				'class': 'outer',
				styles: {zIndex: 9992}
			}).injectInside(this.autocomplete);
			this.menu = new Element('ul').injectInside(this.outer);
			
			for(i=0;i<this.options.menu.length;i++){
				var scope = this.options.menu[i][1];
				var elem = new Element('li');
				var elemLink = new Element('a').addClass('xmenu');
				if(i == 0){
					elem.addClass('selected');
					this.options.postData = $extend( {'scope':scope}, this.options.postData);
					this.menu.scope=scope;
				}
				new Element('span').addClass('xboxcontent').setText(this.options.menu[i][0]).injectInside(elemLink);
				elemLink.addEvent('click', this.toto.bind(this, [scope])).injectInside(elem);
				elem.injectInside(this.menu);
			}
		}
	},

	query: function(){
		var data = $extend({}, this.options.postData);
		data[this.options.postVar] = this.element.value;
		this.fireEvent('onRequest', [this.element, this.ajax]);
		this.ajax.request(data);
	},

	queryResponse: function(resp) {
		this.value = this.queryValue = this.element.value;
		this.selected = false;
		this.options.offset=0;
		this.hideChoices();
		this.fireEvent(resp ? 'onComplete' : 'onFailure', [this.element, this.ajax], 20);
		if (!resp) return;
		var tmp = new Element('div').setHTML(resp);
		this.choices.setHTML(tmp.getFirst().innerHTML).getChildren().each(this.options.parseChoices || this.parseChoices, this);
		var prev = new Element('li', {
				'id': 'previous',
				styles: {'float': 'left'}
			}).setHTML('<span>'+autocompleteI18N.glp('previous')+'</span>');
		this.addChoiceEvents(prev).addEvent('click', function(e){this.options.offset-=this.options.maxChoices;this.element.focus();this.showChoices()}.bind(this)).injectInside(this.choices);

		var next = new Element('li', {
				'id': 'next',
				styles: {'float': 'left'}
			}).setHTML('<span>'+autocompleteI18N.glp('next')+'</span>');
		this.addChoiceEvents(next).addEvent('click', function(e){this.options.offset+=this.options.maxChoices;this.element.focus();this.showChoices()}.bind(this)).injectInside(this.choices);
		this.showChoices();
	},
	
	parseChoices: function(el) {
		var value = el.innerHTML;
		el.inputValue = el.getFirst().innerHTML;
		el.respValue = el.clone();
		this.addChoiceEvents(el).setHTML(this.markQueryValue(value));
	},
	
	toto : function(scope) {
		if (this.options.menu.length==0) return;
		this.element.focus();
		if (this.options.postData.scope==scope){
			this.showChoices();
		}else{
			var selected = 0;
			for(i=0;i<this.options.menu.length;i++){
				if (this.options.menu[i][1]==scope)selected=i;
			}
			this.menu.getChildren().each(function(t,index){t.removeClass('selected');if(index==this){t.addClass('selected')}}.bind(selected));
			this.options.postData.scope=this.options.menu[selected][1];
			this.query();
		}
	},
	
	changeElement:  function(el) {
		if (this.element === $(el)) return;
		this.toggleFocus();
		this.element = $(el);
		this.choices.empty();
		var test = /\w*(_\d{1,2})$/;
		var found = el.match(test);
		if (found && found.length>1)this.options.indice=found[1];
		else this.options.indice='';
		this.observer.reinitialize(this.element);
		this.value = this.observer.value;
		this.queryValue = null;
				
		if(this.element.getProperty('autocomplete')!= 'off'){
			this.element.setProperty('autocomplete', 'off')
				.addEvent('keydown', this.onCommand.bindWithEvent(this))
				.addEvent('mousedown', this.onCommand.bindWithEvent(this, [true]))
				.addEvent('blur', this.hideDelay.bind(this))
				.addEvent('trash', this.destroy.bind(this));
		}
		
		if (this.options.menu.length>0){
			this.menu.getChildren().each(function(t,index){t.removeClass('selected')});
			this.menu.getFirst().addClass('selected');
			this.options.postData.scope=this.options.menu[0][1];
		}
		this.toggleFocus(true);
	}
});

Element.extend(
{
	hide: function() 
	{
		this.effect('opacity', { wait: false, duration: 200}).start(0).chain(function(){this.setStyle('display', 'none');}.bind(this));
		this.fix.hide();
		//return this.setStyle('display', 'none');
	},
	
	show: function() 
	{
		this.effect('opacity', { wait: false, duration: 200}).start(1);
		this.setStyle('display', '');
		this.fix.show();
		if(this.fix.fix){
			this.fix.fix.setStyle('top', 0);
			this.fix.fix.setStyle('left', (this.getParent().getCoordinates().width)-10);
			this.setStyle('left', (this.getParent().getCoordinates().width)-10);
		}
	}
});



Autocompleter.fsm = new Class({

	options: {
		inheritWidth: false,
		onSelect: Class.empty,
		className: 'autocompleter',
		zIndex: 50,
		separator : ' => '
	},

	initialize: function(el, list, options) {
		this.setOptions(options);
		this.element = $('ui_'+el);
		this.choices = $(list).clone();
		this.choices.width = $(list).getStyle('width').toInt();
		$(list).setStyle('display','none');
		this.build(el);
		this.choices.getChildren().each(this.parseChoices, this);
	},

	/**
	 * build - Initialize DOM
	 *
	 * Builds the html structure for choices and appends the events to the element.
	 * Override this function to modify the html generation.
	 */
	build: function() {
        this.autocomplete = new Element('div', {
            'class': this.options.className,
            styles: {'zIndex': 9990, 'opacity':0}
        }).injectInside(document.body);
        this.list = new Element('div', {
                'class': 'list',
				styles: {zIndex: 9991}
            }).injectInside(this.autocomplete);
		
		this.choices.addClass(this.options.className)
			.setStyle('zIndex', this.options.zIndex)
			//.setStyle('opacity',0)
			.injectInside(this.list);
		
		//this.fix = new OverlayFix(this.choices);
		this.fix = new OverlayFix(this.autocomplete);
		
		//this.fx = this.choices.effect('opacity', $merge({
		this.fx = this.autocomplete.effect('opacity', $merge({
			wait: false,
			duration: 200
		}, this.options.fxOptions));
		this.addElementEvent(true);
		//document.body.addEvent('click', this.hideChoices.bind(this));
		//this.choices.addEvent('click', function(e){new Event(e).stopPropagation();}.bind(this));
		this.choices.addEvent('click', function(e){this.element.focus();$clear(this.time2hide);}.bind(this));
	},
	
	addElementEvent: function(all)
	{
		var max = 0;
		var test = /ui_(\w*)(_\d{1,2})$/;
		var found = this.element.getProperty('name').match(test);
		if (found && found.length>0 && $chk($(found[1]+'_max'))){
			max = $(found[1]+'_max').getAttribute('value');
		}
		var elements = [this.element];
		if(all){
			if (found && max>0){
				for(var i=1;i<=max;i++){
					if($chk($('ui_'+found[1]+'_'+i)))elements.include($('ui_'+found[1]+'_'+i));
				}
			}
		}
		else
		{
			if (found && max>0){
				if($chk($('ui_'+found[1]+'_'+max)))elements=[$('ui_'+found[1]+'_'+max)];
			}

		}
		elements.each(function(item){
			item.setProperty('autocomplete', 'off')
				.setProperty('readonly', 'readonly')
				//.addEvent('mousedown', this.showChoices.bind(this))
				.addEvent('blur', this.hideDelay.bind(this))
				.addEvent('focus', this.toggleFocus.bind(this, [true, item]));
				//.addEvent('click', function(e){new Event(e).stopPropagation();})
		},this);
	},
	
	toggleFocus: function(state, el) {
		if (this.focussed && state) return;
		if (el)this.element=el;
		this.focussed = state;
		if (!state) this.hideChoices();
		else this.showChoices();
	},
	
	hideChoices: function() {
		if (!this.visible) return;
		this.visible = this.value = false;
		this.fx.start(0);
		this.fix.hide();
	},
	
	hideDelay: function() {
		this.time2hide = this.toggleFocus.delay(300, this, false);
	},
	
	showDelay: function() {
		this.showChoices.delay(20, this);
	},


	showChoices: function() {
		if (this.visible || !this.choices.getFirst()) return;
		this.visible = true;
		var pos = this.element.getCoordinates();
		//this.choices.setStyles({
		this.autocomplete.setStyles({
			left: pos.left,
			top: pos.bottom
		});
		//if (this.options.inheritWidth) this.choices.setStyle('width', pos.width);
		if (this.options.inheritWidth) this.autocomplete.setStyle('width', pos.width);
		this.fx.start(1);
		this.fix.show();
	},

	choiceSelect: function(el) {
		var par = '';
		if (el.parent)par=el.parent.getText()+ this.options.separator;
		this.element.value= par + el.getFirst().getText();
		this.element.getPrevious().getPrevious().value= el.getFirst().getProperty('id');
		this.hideChoices();
		this.choices.getElements('ul').each(this.hide);
		this.fireEvent('onSelect', [this.element], 20);
	},

	addChoiceEvents: function(el) {
		return el.addEvents({
			mousedown: this.choiceSelect.bind(this, [el])
		});
	},
	
	parseChoices: function(el) {
		var subchoices = el.getLast();
		if (subchoices && subchoices.nodeName.toLowerCase() == 'ul') {
			el.addClass('parent').setStyle('zIndex',this.options.zIndex);
			subchoices.addEvent('click', function(e){new Event(e).stopPropagation();}.bind(this))
			var pos = el.getCoordinates();
			subchoices.setStyles({
				'position': 'absolute' ,
				'left': (el.getCoordinates().right)-10 ,
				'top': 0
			});
			subchoices.fix = new OverlayFix(subchoices);
			subchoices.setStyles({'opacity':0,'display':'none','zIndex':this.options.zIndex+10});
			subchoices.getChildren().each(this.parseChoices, this);			
			el.addEvents({
				mouseover: this.show.bind(this, [subchoices]),
				mouseout:this.hide.bind(this, [subchoices])
			});			
		} else {
			var parent = el.getParent().getParent();
			if (parent.nodeName.toLowerCase() == 'li'){
				el.parent = parent.getFirst();
			}
			this.addChoiceEvents(el);
		}
	},
	
	show: function(el){
		el.timeout = $clear(el.timeout);
		el.timein = el.show.delay(200, el);
	},

	hide: function(el){
		el.timein = $clear(el.timein);
		el.timeout = el.hide.delay(200, el);
	},
	
	addNewElement:  function() {
		this.addElementEvent(false);
	}
});

Autocompleter.fsm .implement(new Events);
Autocompleter.fsm .implement(new Options);




var OverlayFix = new Class({

	initialize: function(el) {
		this.element = $(el);
		if (window.ie){
			this.element.addEvent('trash', this.destroy.bind(this));
			this.fix = new Element('iframe', {
				properties: {
					frameborder: '0',
					scrolling: 'no',
					src: 'javascript:false;'
				},
				styles: {
					position: 'absolute',
					border: 'none',
					display: 'none',
					filter: 'progid:DXImageTransform.Microsoft.Alpha(opacity=0)'
				}
			}).injectAfter(this.element);
		}
	},

	show: function() {
		if (this.fix) this.fix.setStyles($extend(
			this.element.getCoordinates(), {
				display: '',
				zIndex: (this.element.getStyle('zIndex') || 1) - 1
			}));
		return this;
	},

	hide: function() {
		if (this.fix) this.fix.setStyle('display', 'none');
		return this;
	},

	destroy: function() {
		this.fix.remove();
	}

});
