/*
 **************************************
   $.chainedSelects() - jQuery plugin
 **************************************

* 2008 (c) pixy
* Licensed to Xacti Corp.

* Requires jQuery loaded

Usage i.e.:

	var Selects = jQuery('select#sel1').add('select#sel2').('select#sel3');
	var Data = [
		{ id:'A', txt:"Automotive", list:[
			{ id:1, txt:"Automotive Parts" },
			{ id:2, txt:"Service Stores" },
			{ id:3, txt:"Dealers" }
			]},
		{ id:'C', txt:"Consumer Goods", list:[
			{ id:4, txt:"Hobby" },
			{ id:5, txt:"Jewelry" },
			{ id:6, txt:"Toys" }
			]}
		];

	var params = {			// additional parameters, optional, may be empty

		nullOption: ['-select category-','-select subcategory-'],
		// creates empty options at the beginning of the list, like:
		// <option value="">-select category-</option>

//		grouping: [3,1,1],
		// groups first 3 levels of data in the first select, the second select will show the 4th level of data

		valueDivider: ':',
		// value separator for grouped value

		textDivider: ' &gt; '
		// text separator for grouped text value
		
		}

	var selected = 'C:5';	// initial value; optional, may be empty
	

	Selects.chainedSelects( data, selected, params );		// init

	var value = Selects.chainedSelects.val();		// gets value
	var valArr = Selects.chainedSelects.val2arr();	// gets value as array
	var str = Selects.chainedSelects.text();		// gets text
	Selects.chainedSelects.val('A:6');				// sets value
	Selects.chainedSelects.val(['A',6]);			// sets value
	Selects.chainedSelects.rebuild(otherData,0);	// reloads data and rebuilds the chain


	var value = Selects.chainedSelects.val();
	Selects.chainedSelects.disableData(value,1,0);	// disables selected item (removed form the list)
	Selects.chainedSelects.disableData(value,0,0);	// enables selected item again (returned to the list)
	Selects.chainedSelects.disableData(value,1,1);	// disables sublist of selected item (the submenu for the item won't be displayed)
	Selects.chainedSelects.disableData(value,0,1);	// enables sublist of selected item again (if exists)

*/

(function($){

// first, filter only SELECT elements, then call the next, "private" function

	$.fn.chainedSelects = function(data, params) {
		this.filter('select')._chainedSelects(data,params);
		}

// the main, "private" function

	$.fn._chainedSelects = function(data,params) {

		var params = $.extend( {

			nullOption: [],				// array, empty options for each dataLevel;
										// if given, an option with value=nullValue and specified text is created at the beginning of the select
										// if empty, only options according to the data are created, nothing more

			nullValue: '',				// vaule of nullOption items

			hide: true,					// should the inactive selects be hidden or visible & disabled

			valueDivider: '-',			// separator for grouped value

			textDivider: ' / ',			// separator for grouped text

			grouping: [],				// array; number of data levels to be grouped in each select; default is [1,1,...1]
										// i.e. grouping: [2,1] displays first to levels of data in the first select
										// i.e. grouping: [100] displays virtually all levels of data (up to 100) in the first select only

			depthSpacer: '   ',			// spacer to prefix text in each level grouped in a single select

			hardGrouping: true,			// if true, grouped levels are displayed in the grouping select, no linkinking to next is provided

			dataStruct : {				// names of data object properties; should the data structure be different, this is the way to remap it
				optionVal: 'id',
				optionText: 'txt',
				chainedList: 'list',
				disabled: 'disabled'
				}

			}, params );


		function addOptions(selElm,data,nullOption,levelCnt, depth,vPrefix) {
			function add(val,txt) {
				var o = document.createElement('OPTION');
				o.value = val;
				o.text = txt;
				// standard-compliant way doesn't work in IE (crash)
				try { selElm.add(o,null) }
				// IE non-standard sel.add(option,index)
				catch (err) { selElm.add(o) }
				}
			var i,l,v, vp = vPrefix || '', d = depth || 0, spc = '';
			for (i=0,l=d;i<l;i++) spc += params.depthSpacer;
			if (nullOption) add(params.nullValue,nullOption);
			if (data) {
				for (i=0,l=data.length;i<l;i++) {
					if (!data[i][params.dataStruct.disabled]) {
						v = vp + data[i][params.dataStruct.optionVal];
						add( v, spc + data[i][params.dataStruct.optionText] );
						if(levelCnt>1 && !data[i][params.dataStruct.disabledList]) addOptions( selElm, data[i][params.dataStruct.chainedList], '', levelCnt-1, d+1, v+params.valueDivider );
						}
					}
				}
			}

		var _prev = null, _first = null;

		this.each( function(nth){
			var _selElm = this, _nth = nth;
			this.chainedSelect = {
				selElm: _selElm,
				data: null,
				dataLevel:0,
				grouping: params.grouping[_nth] || 1,
				prev: _prev,
				next: null,
				rebuild: function(d,dataLvl) {
					this.data = d;
					this.dataLevel = dataLvl;
					var nulOpt = params.nullOption[this.dataLevel];
					// erase all select.options
					while(this.selElm.length) this.selElm.remove(this.selElm.length-1);
					// build new ones
					if (d && d.length) {
						addOptions(this.selElm,this.data,nulOpt,this.grouping);
						if (this.selElm.length) $(this.selElm).attr('disabled',false).show();
						else {
							$(this.selElm).attr('disabled',true);
							if (params.hide) $(this.selElm).hide();
							}
						}
					else {
						addOptions(this.selElm,[],nulOpt,1);
						$(this.selElm).attr('disabled',true);
						if (params.hide) $(this.selElm).hide();
						}
					if (this.next) this.next.chainedSelect.rebuild(null,nulOpt);
					},
				val: function(valArr) {
					if (valArr==undefined) {
						// get data
						var s = this.selElm.value;
						if (s==params.nullValue) return '';
						if (this.next && this.next.value && this.next.value!==params.nullValue) s += params.valueDivider + this.next.chainedSelect.val();
						return s;
						}
					else {
						// set data
						// valArr = an array of chained values;
						// or a string containing params.valueDivider divided values
						// or a number/string literal
						if (typeof valArr=='string') valArr = valArr.split(params.valueDivider);
						else if (typeof valArr=='number') valArr = [valArr];
						var i,l,v = valArr.slice(0,this.grouping);
						$(this.selElm).val(v.join(params.valueDivider));
						if (this.next) {
							$(this.selElm).trigger('change');
							v = valArr.slice(this.grouping);
							this.next.chainedSelect.val(v);
							}
						}
					},
				val2arr: function(v) {
					if (!v) v = this.val();
					return v.split(params.valueDivider);
					},
				text: function() {
					var v = this.selElm.value;
					if (v==params.nullValue) return '';
					var i,j,k,l, d = this.data, s = '', found;
					v = this.val2arr(v);
					for (j=0,k=v.length;j<k;j++) {
						found = null;
						for (i=0,l=d.length;i<l;i++) if (d[i][params.dataStruct.optionVal]==v[j]) { found = d[i]; break }
						if (found) {
							if (j>0) s += params.textDivider;
							s += found[params.dataStruct.optionText];
							d = found[params.dataStruct.chainedList];
							}
						else return '';
						}
					if (this.next && this.next.value!=='') {
						var s2 = this.next.chainedSelect.text();
						if (s2) s += params.textDivider + s2;
						}
					return s;
					},
				disableData: function(val,on,list) {
					var v = this.val2arr(val);
					var i,j,k,l, found = false, d = this.data;
					j = 0; k = v.length;
					while (!found && j<k) {
						i = 0; l = d.length;
						while (!found && i<l) {
							if (d[i][params.dataStruct.optionVal]==v[j]) {
								if (j==k-1) {
									if (list) d[i][params.dataStruct.disabledList] = on;
									else d[i][params.dataStruct.disabled] = on;
									found = 1;
									}
								else d = d[i][params.dataStruct.chainedList];
								break;
								}
							i++;
							}
						j++;
						}
					}
				} // end chainedSelect obj

			$(this).bind('change', function() {
				if (!this.chainedSelect || !this.chainedSelect.next) return;
				var v = $(this).val();
				if (v==params.nullValue) this.chainedSelect.next.chainedSelect.rebuild(null,this.chainedSelect.dataLevel+1);
				else {
					var i,j,k,l,d = this.chainedSelect.data;
					v = this.chainedSelect.val2arr(v);
					for (j=0,k=v.length;j<k;j++) {
						for (i=0,l=d.length;i<l;i++) {
							if (d[i][params.dataStruct.optionVal]==v[j]) {
								if (j==k-1) {
									if (k<this.chainedSelect.grouping && params.hardGrouping) this.chainedSelect.next.chainedSelect.rebuild([],this.chainedSelect.dataLevel+j+1);
									else this.chainedSelect.next.chainedSelect.rebuild(d[i][params.dataStruct.chainedList],this.chainedSelect.dataLevel+j+1);
									}
								else {
									d = d[i][params.dataStruct.chainedList];
									break;
									}
								}
							}
						}
					}
				});  // end bind.onchange

			if (_prev) {
				_prev.chainedSelect.next = this;
				this.chainedSelect.dataLevel = _prev.chainedSelect.dataLevel + _prev.chainedSelect.grouping;
				}
			_prev = this;
			if (!_first) _first = this;	

			}); // end this.each

		if (_first) {
			_first.chainedSelect.rebuild(data,0);
			}
		};	 // end function

	})(jQuery);
