/*

 * jQuery UI Multiselect

 *

 * Authors:

 *  Michael Aufreiter (quasipartikel.at)

 *  Yanick Rochon (yanick.rochon[at]gmail[dot]com)

 * 

 * Dual licensed under the MIT (MIT-LICENSE.txt)

 * and GPL (GPL-LICENSE.txt) licenses.

 * 

 * http://www.quasipartikel.at/multiselect/

 *

 * 

 * Depends:

 *	ui.core.js

 *	ui.sortable.js

 *

 * Optional:

 * localization (http://plugins.jquery.com/project/localisation)

 * scrollTo (http://plugins.jquery.com/project/ScrollTo)

 * 

 * Todo:

 *  Make batch actions faster

 *  Implement dynamic insertion through remote calls

 */





(function($) {



$.widget("ui.multiselect", {

	_init: function() {

		this.element.hide();

		this.id = this.element.attr("id");

		this.container = $('<div class="ui-multiselect ui-helper-clearfix ui-widget"></div>').insertAfter(this.element);

		this.count = 0; // number of currently selected options

		this.selectedContainer = $('<div class="selected"></div>').appendTo(this.container);

		this.availableContainer = $('<div class="available"></div>').appendTo(this.container);

		this.selectedActions = $('<div class="actions ui-widget-header ui-helper-clearfix"><span class="count">0 '+$.ui.multiselect.locale.itemsCount+'</span><a href="#" class="remove-all">'+$.ui.multiselect.locale.removeAll+'</a></div>').appendTo(this.selectedContainer);

		this.availableActions = $('<div class="actions ui-widget-header ui-helper-clearfix"><input type="text" class="search empty ui-widget-content ui-corner-all"/><a href="#" class="add-all">'+$.ui.multiselect.locale.addAll+'</a></div>').appendTo(this.availableContainer);

		this.selectedList = $('<ul class="selected connected-list"><li class="ui-helper-hidden-accessible"></li></ul>').bind('selectstart', function(){return false;}).appendTo(this.selectedContainer);

		this.availableList = $('<ul class="available connected-list"><li class="ui-helper-hidden-accessible"></li></ul>').bind('selectstart', function(){return false;}).appendTo(this.availableContainer);

		

		var that = this;



		// set dimensions

		this.container.width(this.element.width()+1);

		this.selectedContainer.width(Math.floor(this.element.width()*this.options.dividerLocation));

		this.availableContainer.width(Math.floor(this.element.width()*(1-this.options.dividerLocation)));



		// fix list height to match <option> depending on their individual header's heights

		this.selectedList.height(Math.max(this.element.height()-this.selectedActions.height(),1));

		this.availableList.height(Math.max(this.element.height()-this.availableActions.height(),1));

		

		if ( !this.options.animated ) {

			this.options.show = 'show';

			this.options.hide = 'hide';

		}

		

		// init lists

		this._populateLists(this.element.find('option'));

		

		// make selection sortable

		if (this.options.sortable) {

			$("ul.selected").sortable({

				placeholder: 'ui-state-highlight',

				axis: 'y',

				update: function(event, ui) {

					// apply the new sort order to the original selectbox

					that.selectedList.find('li').each(function() {

						if ($(this).data('optionLink'))

							$(this).data('optionLink').remove().appendTo(that.element);

					});

				},

				receive: function(event, ui) {

					ui.item.data('optionLink').attr('selected', true);

					// increment count

					that.count += 1;

					that._updateCount();

					// workaround, because there's no way to reference 

					// the new element, see http://dev.jqueryui.com/ticket/4303

					that.selectedList.children('.ui-draggable').each(function() {

						$(this).removeClass('ui-draggable');

						$(this).data('optionLink', ui.item.data('optionLink'));

						$(this).data('idx', ui.item.data('idx'));

						that._applyItemState($(this), true);

					});

			

					// workaround according to http://dev.jqueryui.com/ticket/4088

					setTimeout(function() { ui.item.remove(); }, 1);

				}

			});

		}

		

		// set up livesearch

		if (this.options.searchable) {

			this._registerSearchEvents(this.availableContainer.find('input.search'));

		} else {

			$('.search').hide();

		}

		

		// batch actions

		$(".remove-all").click(function() {

			that._populateLists(that.element.find('option').removeAttr('selected'));

			return false;

		});

		$(".add-all").click(function() {

			that._populateLists(that.element.find('option').attr('selected', 'selected'));

			return false;

		});

	},

	destroy: function() {

		this.element.show();

		this.container.remove();



		$.widget.prototype.destroy.apply(this, arguments);

	},

	_populateLists: function(options) {

		this.selectedList.children('.ui-element').remove();

		this.availableList.children('.ui-element').remove();

		this.count = 0;



		var that = this;

		var items = $(options.map(function(i) {

	      var item = that._getOptionNode(this).appendTo(this.selected ? that.selectedList : that.availableList).show();



			if (this.selected) that.count += 1;

			that._applyItemState(item, this.selected);

			item.data('idx', i);

			return item[0];

    }));

		

		// update count

		this._updateCount();

  },

	_updateCount: function() {

		this.selectedContainer.find('span.count').text(this.count+" "+$.ui.multiselect.locale.itemsCount);

	},

	_getOptionNode: function(option) {

		option = $(option);

		var node = $('<li class="ui-state-default ui-element" title="'+option.text()+'"><span class="ui-icon"/>'+option.text()+'<a href="#" class="action"><span class="ui-corner-all ui-icon"/></a></li>').hide();

		node.data('optionLink', option);

		return node;

	},

	// clones an item with associated data

	// didn't find a smarter away around this

	_cloneWithData: function(clonee) {

		var clone = clonee.clone();

		clone.data('optionLink', clonee.data('optionLink'));

		clone.data('idx', clonee.data('idx'));

		return clone;

	},

	_setSelected: function(item, selected) {

		item.data('optionLink').attr('selected', selected);



		if (selected) {

			var selectedItem = this._cloneWithData(item);

			item[this.options.hide](this.options.animated, function() { $(this).remove(); });

			selectedItem.appendTo(this.selectedList).hide()[this.options.show](this.options.animated);

			

			this._applyItemState(selectedItem, true);

			return selectedItem;

		} else {

			

			// look for successor based on initial option index

			var items = this.availableList.find('li'), comparator = this.options.nodeComparator;

			var succ = null, i = item.data('idx'), direction = comparator(item, $(items[i]));



			// TODO: test needed for dynamic list populating

			if ( direction ) {

				while (i>=0 && i<items.length) {

					direction > 0 ? i++ : i--;

					if ( direction != comparator(item, $(items[i])) ) {

						// going up, go back one item down, otherwise leave as is

						succ = items[direction > 0 ? i : i+1];

						break;

					}

				}

			} else {

				succ = items[i];

			}

			

			var availableItem = this._cloneWithData(item);

			succ ? availableItem.insertBefore($(succ)) : availableItem.appendTo(this.availableList);

			item[this.options.hide](this.options.animated, function() { $(this).remove(); });

			availableItem.hide()[this.options.show](this.options.animated);

			

			this._applyItemState(availableItem, false);

			return availableItem;

		}

	},

	_applyItemState: function(item, selected) {

		if (selected) {

			if (this.options.sortable)

				item.children('span').addClass('ui-icon-arrowthick-2-n-s').removeClass('ui-helper-hidden').addClass('ui-icon');

			else

				item.children('span').removeClass('ui-icon-arrowthick-2-n-s').addClass('ui-helper-hidden').removeClass('ui-icon');

			item.find('a.action span').addClass('ui-icon-minus').removeClass('ui-icon-plus');

			this._registerRemoveEvents(item.find('a.action'));

			

		} else {

			item.children('span').removeClass('ui-icon-arrowthick-2-n-s').addClass('ui-helper-hidden').removeClass('ui-icon');

			item.find('a.action span').addClass('ui-icon-plus').removeClass('ui-icon-minus');

			this._registerAddEvents(item.find('a.action'));

		}

		

		this._registerHoverEvents(item);

	},

	// taken from John Resig's liveUpdate script

	_filter: function(list) {

		var input = $(this);

		var rows = list.children('li'),

			cache = rows.map(function(){

				

				return $(this).text().toLowerCase();

			});

		

		var term = $.trim(input.val().toLowerCase()), scores = [];

		

		if (!term) {

			rows.show();

		} else {

			rows.hide();



			cache.each(function(i) {

				if (this.indexOf(term)>-1) { scores.push(i); }

			});



			$.each(scores, function() {

				$(rows[this]).show();

			});

		}

	},

	_registerHoverEvents: function(elements) {

		elements.removeClass('ui-state-hover');

		elements.mouseover(function() {

			$(this).addClass('ui-state-hover');

		});

		elements.mouseout(function() {

			$(this).removeClass('ui-state-hover');

		});

	},

	_registerAddEvents: function(elements) {

		var that = this;

		elements.click(function() {

			var item = that._setSelected($(this).parent(), true);

			that.count += 1;

			that._updateCount();

			return false;

		})

		// make draggable

		.each(function() {

			$(this).parent().draggable({

	      connectToSortable: 'ul.selected',

				helper: function() {

					var selectedItem = that._cloneWithData($(this)).width($(this).width() - 50);

					selectedItem.width($(this).width());

					return selectedItem;

				},

				appendTo: '.ui-multiselect',

				containment: '.ui-multiselect',

				revert: 'invalid'

	    });

		});

	},

	_registerRemoveEvents: function(elements) {

		var that = this;

		elements.click(function() {

			that._setSelected($(this).parent(), false);

			that.count -= 1;

			that._updateCount();

			return false;

		});

 	},

	_registerSearchEvents: function(input) {

		var that = this;



		input.focus(function() {

			$(this).addClass('ui-state-active');

		})

		.blur(function() {

			$(this).removeClass('ui-state-active');

		})

		.keypress(function(e) {

			if (e.keyCode == 13)

				return false;

		})

		.keyup(function() {

			that._filter.apply(this, [that.availableList]);

		});

	}

});

		

$.extend($.ui.multiselect, {

	defaults: {

		sortable: true,

		searchable: true,

		animated: 'fast',

		show: 'slideDown',

		hide: 'slideUp',

		dividerLocation: 0.6,

		nodeComparator: function(node1,node2) {

			var text1 = node1.text(),

			    text2 = node2.text();

			return text1 == text2 ? 0 : (text1 < text2 ? -1 : 1);

		}

	},

	locale: {

		addAll:'Add all',

		removeAll:'Remove all',

		itemsCount:'items selected'

	}

});



})(jQuery);

