
	jQuery.multiselect = function ( wrapper, url, options )
	{
		var $$					= this;
		
		var $url				= url;
		var $data				= null;
		var $sort_col			= "";
		var $sort_dir			= "";
		var $affects			= null;
		var $onchange			= null;
		var $extra_options		= null;
		var $load_initial		= true;
		
		if ( options )
		{
			if ( options.affects != null ) $affects = $(options.affects);
			if ( options.onchange != null ) $onchange = options.onchange;
			if ( options.extra_options != null ) $extra_options = options.extra_options;
			if ( options.load_initial != null ) $load_initial = options.load_initial;
		}
		
		var $wrapper			= $(wrapper);
		var $value				= $("input.value", $wrapper);
		var $add_button			= $(".button_wrapper.add", wrapper);
		var $add_all_button		= $(".button_wrapper.add_all", wrapper);
		var $remove_button		= $(".button_wrapper.remove", wrapper);
		var $long_list			= $(".long_list", wrapper);
		var $short_list			= $(".short_list", wrapper);

		if ( $load_initial ) load_data ();
		update_buttons ();
		
		$value.click ( function ()
			{
				load_data ();
			}
		);
		
		$add_button.click ( function ()
			{
				if ( $(this).hasClass ( "disabled" ) ) return;
				
				$long_list.find ( "tr.data_row.selected" ).removeClass ( "selected" ).appendTo ( $short_list.find ( "tbody" ) );
				update_buttons ();
				sort ( $short_list );
				selection_changed ();
			}
		);
		
		$remove_button.click ( function ()
			{
				if ( $(this).hasClass ( "disabled" ) ) return;
				$short_list.find ( "tr.data_row.selected" ).removeClass ( "selected" ).appendTo ( $long_list.find ( "tbody" ) );
				update_buttons ();
				sort ( $long_list );
				selection_changed ();
			}
		);
		
		$add_all_button.click ( function ()
			{
				if ( $(this).hasClass ( "disabled" ) ) return;
				
				$long_list.find ( "tr.data_row" ).removeClass ( "selected" ).appendTo ( $short_list.find ( "tbody" ) );
				update_buttons ();
				selection_changed ();
			}
		);
		
		$wrapper.find ( ".extra_option" ).change ( function ()
			{
				load_data ();
			}
		);
		
		function load_data ()
		{
			$value.val ( "" );
		
			var url = $url;
			var count = 0;
			if ( url.indexOf ( "?" ) >= 0 ) count ++;
			$wrapper.find ( ".extra_option" ).each ( function ()
				{
					( count == 0 ) ? url += "?" : url += "&amp;";
					url += $(this).attr ( "name" ) + "=" + $(this).val ();
					count ++;
				}
			);
			
			if ( $extra_options )
			{
				if ( url.indexOf ( "?" ) >= 0 ) count ++;
				for ( name in $extra_options )
				{
					( count == 0 ) ? url += "?" : url += "&";
					url += name + "=" + $($extra_options [name]).val ();
					count ++;
				};
			}
			
			$.getJSON ( url, function ( data )
				{
					$data = data;
					insert_headers ();
					insert_data ();
					update_buttons ();
				}
			);
		}
		
		function insert_headers ()
		{
			$wrapper.find ( "thead th" ).remove ();
			
			for ( key in $data.columns )
			{
				$("<th>")
					.attr ( "class", key )
					.html ( $data.columns [key] )
					.appendTo ( $("thead tr", $short_list) );
				$("<th>")
					.attr ( "class", key )
					.html ( $data.columns [key] )
					.appendTo ( $("thead tr", $long_list) );
			}
			
			$("thead th", $wrapper).click ( function ()
				{
					header_click ( $(this) );
				}
			);
			
			$("thead th:eq(0)", $long_list).addClass ( "asc" );
			$("thead th:eq(0)", $short_list).addClass ( "asc" );
		}
		
		function insert_data ()
		{
			$wrapper.find ( "tr.data_row" ).remove ();
			$wrapper.find ( "tr.spacer" ).remove ();
			
			for ( key in $data.data )
			{
				var row = $("<tr>").addClass ( "data_row" );
				$("<input type=\"hidden\" class=\"id\" />").val ( key ).appendTo ( row );
				for ( col in $data.columns )
				{
					$("<td>").appendTo ( row ).addClass ( col ).html ( $data.data [key] [col] );
				}
				row.click ( function ()
					{
						data_row_click ( this );
					}
				);
				row.hover (
					function ()
					{
						$(this).addClass ( "hover" );
					},
					function ()
					{
						$(this).removeClass ( "hover" );
					}
				);
				
				if ( $data.data [key] ['selected'] == true )
				{
					row.appendTo ( $("tbody", $short_list ) );
				} else {
					row.appendTo ( $("tbody", $long_list ) );
				}
			}
			
			selection_changed ();
			
			sort ( $long_list );
			sort ( $short_list );
		}
		
		function data_row_click ( elem )
		{
			$(elem).toggleClass ( "selected" );
			
			if ( $(elem).parent ().parent ().parent ().hasClass ( "long_list" ) )
			{
				$("tr.data_row", $short_list).removeClass ( "selected" );
			} else {
				$("tr.data_row", $long_list).removeClass ( "selected" );
			}
			
			update_buttons ();
		}
		
		function update_buttons ()
		{
			var long_count = $long_list.find ( "tr.data_row.selected" ).size ();
			if ( long_count > 0 )
			{
				$add_button.removeClass ( "disabled" );
			} else {
				$add_button.addClass ( "disabled" );
			}
			
			var items = $long_list.find ( "tr.data_row" ).size ();
			if ( items > 0 )
			{
				$add_all_button.removeClass ( "disabled" );
			} else {
				$add_all_button.addClass ( "disabled" );
			}
			
			var short_count = $short_list.find ( "tr.data_row.selected" ).size ();
			if ( short_count > 0 )
			{
				$remove_button.removeClass ( "disabled" );
			} else {
				$remove_button.addClass ( "disabled" );
			}
		}
		
		function header_click ( elem )
		{
			var list = elem.parent ().parent ().parent ().parent ();
			
			if ( elem.hasClass ( "asc" ) )
			{
				elem.removeClass ( "asc" ).addClass ( "desc" );
			} else
			if ( elem.hasClass ( "desc" ) )
			{
				elem.removeClass ( "desc" ).addClass ( "asc" );
			} else {
				elem.addClass ( "asc" );
			}
			
			$("thead th", list).not ( elem ).removeClass ( "asc" ).removeClass ( "desc" );
			
			sort ( list );
			
		}
		
		function sort ( list )
		{
			/*var sort_heading = null;
			$("thead th", list).each ( function ()
				{
					if ( $(this).hasClass ( "asc" ) || $(this).hasClass ( "desc" ) )
					{
						sort_heading = $(this);
					}
				}
			);
			if ( sort_heading == null ) return;
			
			sort_heading.hasClass ( "asc" ) ? $sort_dir = "asc" : $sort_dir = "desc";
			$sort_col = sort_heading.attr ( "class" ).split ( " " ) [0];
			
			var rows = $("tr.data_row", list).get ();
			rows.sort ( function ( a, b )
				{
					var valA = $("td." + $sort_col, a).html ().toUpperCase ();
					var valB = $("td." + $sort_col, b).html ().toUpperCase ();
					
					if ( $sort_dir == "asc" )
					{
						if ( valA < valB ) return -1;
						if ( valA > valB ) return 1;
					} else {
						if ( valA > valB ) return -1;
						if ( valA < valB ) return 1;
					}
					
					return 0;
				}
			);
			
			$.each ( rows, function ( index, row )
				{
					$("tbody", list).append ( row );
				}
			);*/
		}
		
		function selection_changed ()
		{
			var str = "";
			$("tr.data_row", $short_list).each ( function ( index, item )
				{
					if ( str.length > 0 ) str += ",";
					str += $("input.id", item).val ();
				}
			);
			$value.val ( str );
			
			if ( $affects != null )
			{
				$affects.each ( function ()
					{
						$("input.value", this).trigger ( "click" );
					}
				);
			}
			
			update_buttons ();
			
			if ( $onchange )
			{
				$onchange ();
			}
		}
		
	}

