Участник:BattlePeasant/cattreetable.js

Материал из Википедии — свободной энциклопедии
$(cattreeTables)

function Clear(Node){	//очищает элемент от всех дочерних элементов
	var Child;
	while (Child = Node.firstChild)
		Node.removeChild(Child);
}

function GetText(Node){	//получает первый текстовый узел элемента (но не текст)
	var Child;
	var Children = Node.childNodes;
	for (var i = 0; Child = Children[i]; i++)
		if (Child.nodeType == 3) return Child;
}

function GetCatMembers(catmembers){	//приводит формат списка к старому виду после перехода на др. функцию API
	var str, list = [];
	for (i = 0; i < catmembers.length; i++){
		str = catmembers[i].title;
		if (str.indexOf('Категория:') === 0)
			str = str.substr(10);
		list.push(str);
	}
	return list;
}

function ExcludeCat(idx, list){	//исключает некоторые категории из списка
	var extext, exlist, exind;
	var Table = document.getElementById('cattreeTable' + idx);
	if (extext = Table.getAttribute('data-exclude')){
		exlist = extext.split('\\');
		for (var i = 0; i < exlist.length; i++)
			if ((exind = list.indexOf(exlist[i])) >= 0)
				list.splice(exind,1);
  }
  return list;
}

function GetCat(ctTitle, idx, row, cat, cont, asyncHandler){	//получить категории и статьи через mw.api
	var q = {
		action: 'query',
		format: 'json',
		list: 'categorymembers',
		cmtitle: 'Категория:'+ ctTitle,
		cmlimit: 200,
		cmprop: 'title'
	}
	if (cat)
		q.cmtype = 'subcat';
	else
		q.cmtype = 'page';
	if (cont)
		q.cmcontinue = cont;
	new mw.Api().get(q)
	.done(function(data){
		var list = GetCatMembers(data.query.categorymembers);
		var cont;
		if (cat)
			list = ExcludeCat(idx, list);
		if (data.continue)
			cont = data.continue.cmcontinue;
		asyncHandler(idx, list, cont);
    } )
}

function FillCell(ctTitle, idx, toprow, row, cat, cont){	//заполнить ячейку категориями или статьями
	var asyncFill = function(idx, list, cont){
		var a;
		var Table = document.getElementById('cattreeTable' + idx);
		var Cell = Table.rows[row].cells[0];
		Clear(Cell);
		for (var i = 0; i < list.length; i++){
			a = document.createElement('a');
			if (cat){
				a.href = '/wiki/' + encodeURIComponent('Категория:'+list[i]);
				a.title = 'Категория:'+list[i];
				a.onclick = clickCat;
			}else{
				a.href = '/wiki/' + encodeURIComponent(list[i]);
				a.title = list[i];
			}
			a.appendChild(document.createTextNode(list[i]));
			Cell.appendChild(a);
			if (i < list.length - 1)
				Cell.appendChild(document.createTextNode(' • '));
		}
		if (list.length){
			Table.rows[row].style.display = Table.rows[0].style.display;
		}else{
			Table.rows[row].style.display = 'none';
		}
		if (cont){
			var btntext = Table.getAttribute('data-moretext') || 'далее';
			var btn = CreateButton(idx, null, btntext, null, 'float:right');
			btn.firstChild.onclick = clickMore;
			btn.dataTitle = ctTitle;
			btn.dataCont = cont;
			Cell.appendChild(btn);
		}
		if (row >= 0)
			openNextLevel(idx, row);
	}
	if (!LocalFill(ctTitle, idx, toprow, row, cat))
		GetCat(ctTitle, idx, row, cat, cont, asyncFill);
}

function LocalFill(ctTitle, idx, toprow, row, cat){	//заполнение статично указанными статьями
	var Table = document.getElementById('cattreeTable' + idx);
	var HCell = Table.rows[toprow].cells[0];
	var Cell = Table.rows[row].cells[0];
	var Tree = HCell.dataArtlist;
	var TreeNode, subTree, a;
	var list = [];
	if (typeof Tree === 'undefined')
		return false;
	for (var i=0; TreeNode = Tree[i]; i++)
		if (GetText(TreeNode.a).nodeValue == ctTitle){
			subTree = TreeNode.subtree;
			break;
		}
	if (cat)
		Cell.dataArtlist = subTree;
	for (i = 0; i < subTree.length; i++){
		TreeNode = subTree[i];
		if ((cat && TreeNode.subtree) || (!cat && (typeof TreeNode.subtree === 'undefined')))
			list.push(TreeNode);
	}
	for (i = 0; i < list.length; i++){
		a = list[i].a.cloneNode(true);
		if (cat)
			a.onclick = clickCat;
		if (a.getAttribute('data-loadpage')){
			LoadPage(a, Cell);
			break;
		}
		if (i == 0){
			Clear(Cell);
		}
		Cell.appendChild(a);
		if (i < list.length - 1)
			Cell.appendChild(document.createTextNode(' • '));
	}
	if (list.length){
		Table.rows[row].style.display = Table.rows[0].style.display;
	}else{
		Table.rows[row].style.display = 'none';
   	}
	if (row >= 0)
		openNextLevel(idx, row);
	return true;
}

function DecodeEntities(str){	//декодирует спецсимволы
	div = document.createElement('div');
	div.innerHTML = str;
	return div.textContent;
}

function LoadPage(a, Cell){	//загружает в ячейку часть страницы ВП
	var page = a.getAttribute('data-loadpage');
	var Table = Cell.parentNode.parentNode.parentNode;
	var strelementid = a.getAttribute('data-loadpage-outerelement') || Table.getAttribute('data-loadpage-outerelement');
	strelementid = DecodeEntities(strelementid);
	if (!strelementid.length)
		strelementid = "body";
	jQuery.ajax({
		url: mw.config.get('wgScript') + '?' + $.param({title: page}),
		dataType: 'html'
	})
	.done(function(html) {
		var startpos = html.indexOf(strelementid);
		if (~startpos){
			var regexp = /\w+/g;
			regexp.lastIndex = html.lastIndexOf("<", startpos) + 1;
			var res = regexp.exec(html);
			var oTag = res[0];
			var cTag = '/' + oTag;
			startpos = html.indexOf(">", startpos);
			var endpos = startpos;
			var lvl = 1;
			while ( ~(endpos = html.indexOf("<", endpos + 1)) ){
				if (html.indexOf(cTag, endpos + 1) == endpos + 1)
					lvl--;
				if (html.indexOf(oTag, endpos + 1) == endpos + 1)
					lvl++;
				if (!lvl)
					break;
			}
			if (~endpos){
				html = html.substring(startpos + 1, endpos);
				a.insertAdjacentHTML('beforeEnd', html);
				Clear(Cell);
				Cell.appendChild(a);
			}
		}
	});
}

function openNextLevel(idx, row){	//автоматически открывает некоторый следующий уровень при первом показе таблицы
	var i, strpath, strsubcat;
	var Table = document.getElementById('cattreeTable' + idx);
	if (Table.hasAttribute('data-open')){
		strpath = Table.getAttribute('data-open');
		if ( ~(i = strpath.indexOf('\\')) ){
			strsubcat = strpath.substring(0, i);
			strpath = strpath.substring(i+1);
			Table.setAttribute('data-open',strpath);
		}else{
			strsubcat = strpath;
			Table.removeAttribute('data-open');
    	}
		showSubtree(strsubcat, idx, row);
    }
}

function clickCat(event){	//	нажатие на категорию (открывает подуровень или сворачивает)
	event = event || window.event;
	var a = event.target || event.srcElement;
	var Text = GetText(a);
	var Row = a.parentNode.parentNode;
	var row = Row.rowIndex;
	var Table = Row.parentNode.parentNode;
	var idx = Table.id.slice(12);
	if (row){
		showSubtree(Text.nodeValue, idx, row);
	}else{
		displayTable(idx, 0);
	}
	return false;
}

function clickMore(event){	//нажатие далее (загружает следующие 200)
	event = event || window.event;
	var btn = event.target || event.srcElement;
	btn = btn.parentNode;
	var Row = btn.parentNode.parentNode;
	var row = Row.rowIndex;
	var Table = Row.parentNode.parentNode;
	var idx = Table.id.slice(12);
	var cat = (row != (Table.rows.length - 1));
	FillCell(btn.dataTitle, idx, 0, row, cat, btn.dataCont);
	return false;
}

function CreateButton(idx, classname, btntext, href, csstext){	//создаёт «кнопку»
	var btn = document.createElement('span');
	btn.style.cssText = 'font-weight:normal; font-size:smaller; ' + csstext;
	if (classname)
		btn.id = classname + idx;
	var a = document.createElement('a');
	if (href)
		a.href = href;
	a.appendChild(document.createTextNode(btntext));
	btn.appendChild(a);
	return btn;
}

function CreateHLink(Text, idx){	//создаёт заголовок высшего уровня (впрочем, идентичный подзаголовкам)
	var a = document.createElement('a');
	a.href = '/wiki/' + encodeURIComponent('Категория:'+Text.nodeValue);
	a.onclick = clickCat;
	a.title = 'Категория:'+Text.nodeValue;
	a.appendChild(Text);
	return a;
}

function GetArt(Artlist, text){	//получить элемент А из списка с соотв. текстом
	for (var i=0; i < Artlist.length; i++)
		if (GetText(Artlist[i].a).nodeValue == text)
			return Artlist[i].a;
	return Artlist[Artlist.length - 1].a;
}

function cattreeTables(){	//вход
	var Table, HRow, HCell, Text, a, btn, btntext, parentcat, listtext, idx = 0, list = [], artlist;
	var Cattrees = document.getElementsByClassName('cattree')
	var asyncTitlelist = function(idx, list){
		var Table = document.getElementById('cattreeTable' + idx)
		Table.dataCatlist = list
	}
	for (var i = 0; Table=Cattrees[i]; i++){
		if (Table.tagName!='TABLE') continue;
		if (!(HRow = Table.rows[0])) continue;
		if (!(HCell = HRow.cells[0])) continue;
		if (PrepareFullArticleList(HCell)){
			Text = GetText(HCell);
			artlist = HCell.dataArtlist;
			a = GetArt(artlist, (Text) ? Text.nodeValue : null);
			a.onclick = clickCat;
			HCell.insertBefore(a,HCell.firstChild);
			if (Text)
				HCell.removeChild(Text);
		}else{
			Text = GetText(HCell);
			artlist = null;
			a = CreateHLink(Text, idx);
			HCell.appendChild(a);
		}
		Table.id = 'cattreeTable' + idx;
		if ((parentcat = Table.getAttribute('data-parent')) || (listtext = Table.getAttribute('data-catlist')) || (artlist && (artlist.length > 1))){
			if (parentcat){
				GetCat(parentcat, idx, -1, true, null, asyncTitlelist);
			}else if(listtext){
				Table.dataCatlist = listtext.split('\\');
			}else{
				for (var j = 0; j < artlist.length; j++)
					list[j] = GetText(artlist[j].a).nodeValue;
				Table.dataCatlist = list;
			}
			btntext = Table.getAttribute('data-prevtext') || 'пред.';
			btn = CreateButton(idx, 'prevcatButton', btntext, 'javascript:nextHeader(' + idx + ', -1)', 'float:left');
			HCell.appendChild(btn);
			btntext = Table.getAttribute('data-nexttext') || 'след.';
			btn = CreateButton(idx, 'nextcatButton', btntext, 'javascript:nextHeader(' + idx + ', 1)', 'float:right');
			HCell.appendChild(btn);
		}else{
			btntext = Table.getAttribute('data-expandtext') || 'показать';
			btn = CreateButton(idx, 'hidecatButton', btntext, 'javascript:displayTable(' + idx + ', 1)', 'float:right');
			HCell.appendChild(btn);
		}
		Table.style.display = 'table';
		if (!Table.classList.contains('collapsed'))
			displayTable(idx, 0);
		idx++
	}
}

function PrepareFullArticleList(HCell){	//если имеется список статей, подготавливаем (используется вместо получения из категорий)
	var Span, Child, Tree=[], TreeLevels=[Tree], TreeNode, TreeBranch, str, a;
	for (var i = 0; Span = HCell.children[i]; i++){
		if (Span.tagName == 'SPAN'){
			for (var j = 0; Child = Span.childNodes[j]; j++){
				if (Child.nodeType == 1){
					TreeNode = new Object();
					TreeNode.a = Child;
					TreeLevels[TreeLevels.length-1].push(TreeNode);
				}
				if (Child.nodeType == 3){
					str = Child.nodeValue;
					if (~str.indexOf('(')){
						TreeBranch = new Array();
						TreeNode.subtree = TreeBranch;
						TreeLevels.push(TreeBranch);
					}
					if (~str.indexOf(')'))
						for (var k = 0; k < str.length; k++)
							if (str[k] == ')'){
								TreeBranch = TreeLevels.pop();
								elementsToA(TreeBranch);
							}
				}
			}
			break;
		}
	}
	elementsToA(Tree);
	if (Tree.length)
		HCell.dataArtlist = Tree;
	return Tree.length;
}

function elementsToA(elements){	//преобразует элементы, использующиеся в качестве заголовков в А
	var a;
	for (var i = 0; i < elements.length; i++){
		if ((elements[i].subtree) && (elements[i].a.tagName != 'A')){
			a = document.createElement('a');
			a.appendChild(GetText(elements[i].a));
			elements[i].a = a;
		}
	}
}

function GetRowsCount(Table){	//считает количество непустых строк в таблице
	var Cell, rowscount = 0;
	for (var i = 1; i < Table.rows.length; i++){
		Cell = Table.rows[i].cells[0];
		if (Cell.firstChild)
			rowscount++;
	}
	return rowscount;
}

function IsHidden(Table){	//скрыты строки кроме верхней
	var Row;
	var hidden = true;
	for (var i = 1; Row = Table.rows[i]; i++)
	if (Row.style.display != 'none'){
		hidden = false;
		break;
	}
	return hidden
}

function HasBold(Cell){	//в ячейке имеются жирные элементы (развёрнут подуровень)
	var Child;
	var bold = false;
	for (var i = 0; Child = Cell.children[i]; i++){
		if (Child.style.fontWeight == 'bold'){
			bold = true;
			break;
		}
	}
	return bold;
}

function nextHeader(idx, step){	//скролинг заголовков высшего уровня
	var Child, HCell, text, a, btn;
	var Table = document.getElementById('cattreeTable' + idx);
	HCell =  Table.rows[0].cells[0];
	Child = HCell.firstChild;
	text = GetText(Child).nodeValue
	var ind = Table.dataCatlist.indexOf(text);
	var i = ind + step;
	btn = document.getElementById('prevcatButton' + idx);
	if (i <= 0){
		i = 0;
		btn.firstChild.disable = true;
	}else
		btn.firstChild.disable = false;
	btn = document.getElementById('nextcatButton' + idx)
	if (i >= Table.dataCatlist.length - 1){
		i = Table.dataCatlist.length - 1;
		btn.firstChild.disable = true;
	}else
		btn.firstChild.disable = false;
	if (i != ind){
		text = Table.dataCatlist[i];
		if (HCell.dataArtlist){
			a = GetArt(HCell.dataArtlist, text);
			a.onclick = clickCat;
		}else{
			a = CreateHLink(document.createTextNode(text), idx);
		}
		HCell.insertBefore(a, Child);
		HCell.removeChild(Child);
		if (!IsHidden(Table))
			showSubtree(text, idx, 0);
	}
}

function ShowTable(Table, state){	//показывает или скрывает строки, кроме верхней
	var Row;
	for (var i = 1; Row = Table.rows[i]; i++)
		if (state){
			if (Row.cells[0].children.length)
				Row.style.display = Table.rows[0].style.display
		}else{
			Row.style.display = 'none'
	}
}

function displayTable(idx, mode){	//сворачивание и разворачивание таблицы
	var Cell, btnstate, btntext, btn;
	var Table = document.getElementById('cattreeTable' + idx);
	var ctTitle = GetText(Table.rows[0].cells[0].firstChild).nodeValue;
	var rowscount = GetRowsCount(Table);
	var hidden = IsHidden(Table);
	if (mode){
		if (!rowscount){
			btnstate = true;
			showSubtree(ctTitle, idx, 0);
		}else{
			btnstate = (hidden);
			ShowTable(Table, btnstate);
		}
	}else{
		if ((HasBold(Table.rows[1].cells[0])) || (hidden)){
			btnstate = true;
			showSubtree(ctTitle, idx, 0);
		}else{
			btnstate = false;
			ShowTable(Table, btnstate);
		}
	}
	if (btn = document.getElementById('hidecatButton' + idx)){
		if (btnstate)
			btntext = Table.getAttribute('data-collapsetext') || 'свернуть';
		else
			btntext = Table.getAttribute('data-expandtext') || 'показать';
		var Text = GetText(btn.firstChild);
		Text.nodeValue = btntext;
	}
}

function showSubtree(ctTitle, idx, toprow){	//вывести подуровень (строка заголовков и строка данных)
	var Row, Cell, Child, Text;
	var Table = document.getElementById('cattreeTable' + idx);
	var nextrow = null;
	var datarow = Table.rows.length-1;
	if (datarow > 1){
		nextrow = toprow + 1;
		if ((datarow - nextrow) < 1){
			Row = Table.insertRow(datarow++);
			Row.style.cssText = Table.rows[1].style.cssText;
			Row.style.display = 'none';
			Cell = Row.insertCell(0);
			Cell.style.cssText = Table.rows[1].cells[0].style.cssText;
		}
	}
	if (toprow > 0){
		Cell = Table.rows[toprow].cells[0];
		for (var i = 0; Child = Cell.children[i]; i++){
			Text = GetText(Child);
			if ((Text) && (Text.nodeValue == ctTitle))
				Child.style.fontWeight = 'bold';
			else
				Child.style.fontWeight = 'normal';
		}
	}
	if (nextrow != null){
		for (i = nextrow + 1; i < datarow; i++){
			Table.rows[i].style.display = 'none';
			Clear(Table.rows[i].cells[0]);
		}
		FillCell(ctTitle, idx, toprow, nextrow, true);
	}
	if (!Table.hasAttribute('data-open'))
		FillCell(ctTitle, idx, toprow, datarow, false);
}