/*	Создание компактного списка (тестовый вариант).
 *
 *	Автор : AKS (aksus-69@yandex.ru). 
 *	Спасибо за помощь в написании сценария Илье Лебедеву (ilya@lebedev.net)&&(http://cms.debugger.ru). 
 *	Сценарий тестировался в IE6, Opera7+, Gecko/20040206+. 
 *
 *	Функция createCompactList позволяет создать компактный 
 *	маркированный список независимо от количества пунктов и 
 *	вложенных списков.  
 *
 *	Объект-прототип данной функции содержит несколько объектов, каждый 
 *	из которых я попытался создать таким образом, чтобы он представлял 
 *	из себя хранилище данных определенного назначения.
 *
 */

function createCompactList(settingsUtil, sListName) { 
	this.listName = sListName; 
	if(typeof(settingsUtil) == "function") { 
		this.settingsUtil = new settingsUtil; 
	} 
	if(typeof(this.settingsUtil.slideDelay) == "number") { 
		this.toolsUtil.slideDelay = this.settingsUtil.slideDelay; 
	} 
} 

/* Объект settingsUtil содержит несколько свойств, от которых 
 * будет зависеть внешний вид "переключателей" дополнительных скрытых 
 * пунктов списка. 
 *
 * !!! Эти параметры являются резервными на тот случай, если в коде 
 * тестовой страницы в конструктор не будет передан одноименный объект 
 * с параметрами настройки. 		
 */

createCompactList.prototype.settingsUtil = new function() { 

/* this.useImage - позволяет испoльзовать для "переключателей" 
 * изображения или текстовые символы (если true, то будут использованы изображения). 
 */
	this.useImage = false; 

/* this.openersName - часть имени класса, которое в дальнейшем примет 
 * один из двух видов: "opener_span" или "opener_img". 
 *	
 * !!! Если в таблице стилей потребуется изменить имя класса, не забудьте о суффиксах "_span" и "_img"
 */
	this.openersName = "opener_"; 

/* В случае необходимости, будут предварительно загружены изображения для 
 * "переключателей". 
 * !!! Необходимо задать путь к файлам этих изображений. 
 */
	if(this.useImage) { 
		this.closed = new Image; 
		this.opened = new Image; 
		this.closed.src = "style/closed.gif"; 
		this.opened.src = "style/opened.gif"; 
	} 

/* this.openersTag - задает имя тэга для "переключателя". 
 * !!! Целиком зависит от выбранного параметра this.useImage. 
 */
	this.openersTag = (this.useImage) ? "img" : "span";  

/* this.openersValue позволит задать текстовое содержимое переключателей, 
 * если изображения не используются (this.useImage = false). 
 */
 
	this.openersValue = { 
		closed : (this.useImage) ? this.closed.src : ">>", 
		opened : (this.useImage) ? this.opened.src : "<<" 
	} 
} 

/* Объект expandUtil содержит методы для создания "переключателей" дополнительных 
 * скрытых пунктов, для проверки состояний этих пунктов и т.д. 
*/

createCompactList.prototype.expandUtil = { 

/* openers - массив, в который будут помещены все "переключатели", к тому же 
 * все объекты этого массива будут иметь дополнительные свойства: 
 *	active - (true/false) список открыт/закрыт или открывается/закрывается. 
 *	closed - (false/true) список открыт или закрыт. 
 *	nodes - элементы списка <li>, относящегося к данному "переключателю". 
 *	list - непосредственно список (элемент <ul>). 
 */
	openers : [],  

/* createOpener - создает элемент-"переключатель", задавая ему имя класса. 
 */
	createOpener : function(oThis) { 
		var oElement = document.createElement(oThis.settingsUtil.openersTag); 
		if(oThis.settingsUtil.useImage) { 
			oElement.alt = "+"; oElement.width = 1; oElement.height = 1;
			oElement.src = oThis.settingsUtil.openersValue.closed; 
		} 
		else { 
			oElement.appendChild(document.createTextNode(oThis.settingsUtil.openersValue.closed)); 
		} 
// ORIGINAL: oElement.className = oThis.settingsUtil.openersName.concat(oThis.settingsUtil.openersTag); 
		oElement.className = oThis.settingsUtil.openersName.concat("closed"); 
		return oElement; 
	}, 

/* setOpeners - анализирует главный список на предмет наличия вложенных списков, 
 * создавая, где необходимо, элементы-"переключатели", а также вызывает метод 
 * для скрытия всех элементов дополнительных списков, если они не скрыты. 
 */ 
	setOpeners : function(oThis) {  
		var oList = document.getElementById(oThis.listName), 
		aListItems = (oList) ? oList.getElementsByTagName("li") : false;  
		if(typeof(aListItems.length) != "undefined") { 
			for(var i = 0, l = aListItems.length; i < l; i++) { 
				var oList = aListItems[i].getElementsByTagName("ul")[0]; 
				if(typeof(oList) != "undefined") { 
					var k = this.openers.length; 
					this.openers[k] = this.createOpener(oThis);
					oThis.eventUtil.addListener(this.openers[k], { 
						"onmouseup" : function(e) { oThis.toolsUtil.onClick(e, oThis); }, 
						"onmousedown" : function(e) { oThis.eventUtil.preventDefault(e); }, 
						"onclick" : function(e) { oThis.eventUtil.preventDefault(e); }, 
						"onclick" : function(e) { oThis.toolsUtil.onClick(e, oThis); }, 
						"onselectstart" : function(e) { oThis.eventUtil.preventDefault(e); }}); 
					// ORIGINAL: aListItems[i].insertBefore(this.openers[k], oList);
					//var aLinkItem = aListItems[i].getElementsByTagName("h2")[0];
					this.openers[k].id = oThis.listName + "_node_" + k + "l";
					var aLinkItem = aListItems[i].getElementsByTagName("h2")[0];
					if (aLinkItem==null) {
						aLinkItem = aListItems[i].getElementsByTagName("a")[0];
						aLinkItem.name=oThis.listName + "_node_" + k;
					}
					//aLinkItem.insertBefore(this.openers[k],aLinkItem.firstChild); 
					aListItems[i].insertBefore(this.openers[k],aListItems[i].firstChild);
					aListItems[i].style.height="auto";
					// Создание пустого объекта для симметрии.
					if (oThis.settingsUtil.useImage) {
						var aLinkNull = this.createOpener(oThis);
						aLinkNull.className = "opener_null"; aLinkNull.alt = ".";
						aLinkItem.insertBefore(aLinkNull,null);
					}
					// AKCUOP END.
					this.openers[k].active = false; 
					this.openers[k].closed = true; 
					this.openers[k].nodes = this.getItems(oList);  
					this.openers[k].list = oList; 
					this.setInvisible();  
				} 
			}  
		} 
	}, 

/* getItems - собирает массив элементов <li> для помещения их в св-во nodes объектов openers. 
 
*/
	getItems : function(oList) {
                var aNodes = [], aItems = oList.getElementsByTagName("li");
                for(var i = 0, l = aItems.length; i < l; i++) { 
                        if(aItems[i].parentNode.tagName.toLowerCase() == "ul" && aItems[i].parentNode == oList) {  aNodes.push(aItems[i]); } 
                } 
                return aNodes; 
        },

/* checkOpeners - проверяет значение св-ва active объектов openers, не позволяя открывать/закрывать 
 * список в момент, когда вложенный в него список находится в активном состоянии.  
 */
	checkOpeners : function(oTarget) { 
		for(var i = 0, l = this.openers.length; i < l; i++) { 
			if(oTarget.active || this.compareItems(oTarget)) { return true; } 
		} 
		return false; 
	}, 

/* compareItems - вспомогательный метод для checkOpeners. 
 */
	compareItems : function(oTarget) { 
		var aItems = oTarget.list.getElementsByTagName(oTarget.tagName); 
		for(var i = 0, l = aItems.length; i < l; i++) { 
			for(var k = 0, iL = this.openers.length; k < iL; k++) { 
				if(aItems[i] == this.openers[k] && this.openers[k].active) { return true; } 
			} 
		} 
		return false; 
	}, 

/* setInvisible - проверяет значение св-ва display элементов списка, 
 * устанавливая его в "none", если оно иное. 
 */
	setInvisible : function() { 
		for(var i = 0, l = this.openers.length; i < l; i++) { 
			for(var k = 0, iL = this.openers[i].nodes.length; k < iL; k++) { 
				var sCheck = this.getStyle(this.openers[i].nodes[k], "display"); 
				if(sCheck && sCheck != "none") { this.openers[i].nodes[k].style.display = "none"; } 
			} 
		} 
	}, 

/*	getStyle - вспомогательный метод для setInvisible. 
 */
	getStyle : function(oElement, sProp) { 
		if(oElement.currentStyle) { return oElement.currentStyle[sProp]; } 
		else if(document.defaultView) { 
			return document.defaultView.getComputedStyle(oElement, null).getPropertyValue(sProp); } 
		else { return false; } 
	} 
} 

/* Объект toolsUtil содержит методы для управления открытием/скрытием дополнительных списков. 
 */ 
		 
createCompactList.prototype.toolsUtil = { 

/* slideDelay - промежуток времени для открытия/скрытия одного за другим элементов <li>, 
 * устанавливается на тестовой странице, здесь установлено значение по-умолчанию. 
 */
	slideDelay : 50,  

/* onClick - метод, вызываемый при клике по "переключателю", проверяющий "активность" данного 
 * и вложенных в него списков. Если при закрытии данного списка вложенные в него списки открыты, 
 * то они также будут закрыты. 
 */
	onClick : function(oEvent, oThis) { 
		var oTarget = oThis.eventUtil.currentTarget(oEvent); 
		if(oTarget.tagName.toLowerCase() != oThis.settingsUtil.openersTag.toLowerCase()) { return false; } 
		if(oThis.expandUtil.checkOpeners(oTarget)) { return false; }  
		oTarget.closed = !oTarget.closed; 
		this.switchDisplay(oThis, oTarget, oTarget.closed); 
		if(oTarget.closed) { 
			var oInlays = oTarget.list.getElementsByTagName("ul"); 
			if(typeof(oInlays) != "undefined") { 
				for(var i = oInlays.length - 1; i >= 0; i--) { 
					for(var k = 0, iL = oThis.expandUtil.openers.length; k < iL; k++) { 
						if(oInlays[i] == oThis.expandUtil.openers[k].list) { 
							this.switchDisplay(oThis, oThis.expandUtil.openers[k], oTarget.closed); 
						} 
					} 
				} 
			} 
		}
	}, 

/* switchDisplay - меняет содержимое "переключателя" на противоположное, определяет список, как 
 * "активный", и вызывает метод для работы с элементами <li> списка. 
 */
	switchDisplay : function(oThis, oTarget, bValue) { 
		var oProps = (bValue) ? "closed" : "opened";  
		/* ORIGINAL:
		if(oThis.settingsUtil.useImage) { oTarget.src = oThis.settingsUtil.openersValue[oProps]; } 
		else { oTarget.firstChild.data = oThis.settingsUtil.openersValue[oProps]; } 
		*/
		if(oThis.settingsUtil.useImage) { 
			oTarget.src = oThis.settingsUtil.openersValue[oProps];
			oTarget.alt = (bValue) ? "+" : "-";
			oTarget.className = oThis.settingsUtil.openersName.concat((bValue) ? "closed" : "opened");
		} else { 
			oTarget.firstChild.data = oThis.settingsUtil.openersValue[oProps];
			oTarget.firstChild.className = oThis.settingsUtil.openersName.concat((bValue) ? "closed" : "opened");
		} 
		// AKCUOP END.
		oTarget.active = true; 
		if(bValue) { oTarget.closed = bValue; } 
		this.startSlide(oTarget, bValue); 
	}, 

/* startSlide - вызывает для каждого элемента <li> открывающегося/закрывающегося списка 
 * метод данного объекта setTimeout. 
 */
	startSlide : function(oTarget, bValue) { 
		var iCount = 0, iLen = oTarget.nodes.length; 
		if(bValue) { for(; iCount < iLen; iCount++) { 
			this.setTimeout(oTarget, oTarget.nodes[iCount], bValue, iCount); }}
		else { for(--iLen; iLen >= 0; iLen--, iCount++) { 
			this.setTimeout(oTarget, oTarget.nodes[iLen], bValue, iCount); }}		
	}, 

/* setTimeout - для каждого элемента <li> вызывает метод showItems для показа/скрытия этого элемента
 * через промежуток времени, который определяется следующим образом:
 * 	значение this.slideDelay умножается на порядковый номер элемента в массиве nodes текущего openers. 
 */ 
	setTimeout : function(oTarget, oItem, bValue, iCount) { 
		var oThis = this, iDelay = this.slideDelay*iCount; 
		return setTimeout(function() { oThis.showItems(oTarget, oItem, bValue); }, iDelay);
	}, 

/* showItems - покажет/скроет элементы <li>, определяя при этом первый/последний элементы 
 * текущего списка для индикации состояния active. 
 */
	showItems : function(oTarget, oItem, bValue) { 
		oItem.style.display = (bValue) ? "none" : "block"; 
		if((bValue) ? oItem == oTarget.nodes[oTarget.nodes.length - 1] : oItem == oTarget.nodes[0]) { 
			oTarget.active = false; 
		} 	
	} 
} 

/* Объект eventUtil содержит методы, необходимые для обработки событий, которые 
 * представлены в кроссбраузерном формате. Наименования методов говорять сами за 
 * себя и в комментариях не нуждаются. 
 */

createCompactList.prototype.eventUtil = { 
	addListener : function(oElement, oParams) { 
		if(!oElement.attachEvent) { 
			oElement.attachEvent = function(e, f) { 
				return this.addEventListener(e.substr(2), f, false); 
			} 
		} 
		for(var i in oParams) { oElement.attachEvent(i, oParams[i]); } 
	}, 
	preventDefault : function(oEvent) { 
		if(!oEvent.preventDefault) { 
			oEvent.preventDefault = function() { 
				return this.returnValue = false; 
			} 
		} 
		oEvent.preventDefault(); 
	}, 
	currentTarget : function(oEvent) { 
		if(!oEvent.target) { 
			oEvent.target = oEvent.srcElement; 
		} 
		return oEvent.target; 
	}  		
}


 /* Объект settingsUtil содержит несколько свойств, от которых 
 * будет зависеть внешний вид "переключателей" дополнительных скрытых 
 * пунктов списка, а также время закрытия/открытия этих списков.  		
 */

var settingsUtil = function() { 

/* this.useImage - позволяет испoльзовать для "переключателей" 
 * изображения или текстовые символы (если true, то будут использованы изображения). 
 */
	this.useImage = true; 

/* this.openersName - часть имени класса, которое в дальнейшем примет 
 * один из двух видов: "opener_span" или "opener_img". 
 *	
 * !!! Если в таблице стилей потребуется изменить имя класса, не забудьте о суффиксах "_span" и "_img"
 */
	this.openersName = "opener_"; 

/* В случае необходимости, будут предварительно загружены изображения для 
 * "переключателей". 
 * !!! Необходимо задать путь к файлам этих изображений. 
 */
	if(this.useImage) { 
		this.closed = new Image; 
		this.opened = new Image; 
		this.closed.src = "/style/spacer.gif"; 
		this.opened.src = "/style/spacer.gif"; 
	} 

/* this.openersTag - задает имя тэга для "переключателя". 
 * !!! Целиком зависит от выбранного параметра this.useImage. 
 */
	this.openersTag = (this.useImage) ? "img" : "span";  

/* this.openersValue позволит задать текстовое содержимое переключателей, 
 * если изображения не используются (this.useImage = false). 
 */
 
	this.openersValue = { 
		closed : (this.useImage) ? this.closed.src : "", 
		opened : (this.useImage) ? this.opened.src : "" 
	} 

/* this.slideDelay позволит установить время между появлением/скрытием 
 * каждого пункта скрытого списка. 
 */
	this.slideDelay = 25; 
}
