В работе решается задача создания раскрывающегося HTML-списка, иерархия которого описана в текстовом поле HTML-документа (рис. 1).
Рис. 1. Пример описания раскрывающегося списка
Описание создается по приведенному ниже шаблону.
По этому описанию формируется HTML-код, воспроизводящий раскрывающийся список.
В частности, набранному в левом текстовом поле рис. 1 описанию списка отвечает следующий раскрывающийся список:
+ Хранилище СН
Конфигурация Гарантия
Таким образом, решатся задача генерации раскрывающегося HTML-списка по его описанию, заданному в текстовом поле HTML-документа по специальному шаблону.
Раскрывающийся список имеет древовидную структуру. Каждый элемент такого списка ассоциируется с ветвью иерархического дерева. Каждая ветвь характеризуется уровнем и позицией на текущем уровне. Так, в вышеприведенном примере, список содержит 2 уровня. На уровне 1 имеется шесть ветвей, а на уровне 2 - только одна ветвь.
Каждый элемент раскрывающегося списка предваряется номером, обрамленным символами #, например:
#1.2.3#
Далее следует элемент списка, например:
#1.2.3#Загрузка серийных номеров
Номер ветви формируется из номера родительской ветви и номера позиции в пределах текущего уровня ветви. В качестве разделителя используется точка. Например, номер
1.2.3
указывает на то, что родителем текущей ветви является ветвь 1.2, а сама текущая ветвь расположена на третьей позиции текущего уровня. То есть перед ветвью под номером 1.2.3 на текущем уровне распложены ветви под номерами 1.2.1 и 1.2.2.
Первая ветвь раскрывающегося списка имеет номер 1.
Таким образом, описание раскрывающегося списка содержит специальном образом пронумерованные элементы списка.
Набор тегов, необходимых для генерации раскрывающегося HTML-списка, возьмем из вышеприведенного примера. Без обрамляющей рамки этот пример воспроизводится следующим HTML-кодом:
<!DOCTYPE HTML>
<html>
<head>
<script>
function toggle(node, idVl) {
dvS = document.getElementById("d" + idVl).style
sp = document.getElementById("s" + idVl);
// Раскрывает ветвь списка
if (dvS.display == "none") {
sp.innerHTML = " – ";
dvS.display = "inline";
}
// Скрываем ветвь списка
else {
sp.innerHTML = " + ";
dvS.display = "none";
}
}
</script>
<style>
span {
border-style:dotted;
border-width:1px;
}
.p {
cursor:pointer;
}
</style>
</head>
<body>
<p class = "p" onClick = "toggle(this, 1)"><span id = "s1"> + </span>Хранилище СН</p>
<div id = "d1" style = "display:none">
<p style = "margin-left:30px">Конфигурация хранилища СН</p>
<p class = "p" style = "margin-left:10px" onClick = "toggle(this, 2)"><span id = "s2"> + </span>Заполнение хранилища СН</p>
<div id = "d2" style = "display:none">
<p style = "margin-left:60px">Обработки загрузки данных</p>
<p style = "margin-left:60px">Загрузка номенклатуры</p>
<p style = "margin-left:60px">Загрузка серийных номеров</p>
</div>
<p style = "margin-left:30px">Общий модуль СН</p>
</div>
<p style = "margin-left:20px">Конфигурация Гарантия</p>
</body>
</html>
Для генерации раскрывающегося HTML-списка нам понадобятся теги, расположенные между <body> и </body>.
Идея построения кода проста: соответствующая часть дерева (подсписок) скрывается или раскрывается за счет изменения свойства display блока div с этим подсписком. При этом, если подсписок скрыт, то родительскому элементу этого подсписка предшествует знак "плюс", или знак "минус" – в противном случае.
Знаки воспроизводятся тегом span с границей из точек (border-style:dotted).
Одновременно изменяемые span и div-объекты снабжены идентификаторами с одинаковой числовой частью, например s2 и d2.
Смена знака, а также изменение видимости div-блока выполняется click-обработчиком toggle, вызываемым при ударе мышью по абзацу, содержащему span-блок и предшествующему div-блоку. Это обработчик принимает в качестве параметра числовую часть идентификаторов span и div-объектов, получает ссылки на эти объекты и изменяет надлежащим образом их свойства.
Из примера видно, что при генерации раскрывающегося HTML-списка потребуются следующие теги:
<p class = "p" style = "margin-left:#px" onClick = "toggle(this, N)">...</p>
<span id = "sN"> + </span>
<div id = "dN" style = "display:none">...</div>
<p style = "margin-left:#px">...</p>
Символы # и N, присутствующие в тегах, заменяются по ходу генерации соответствующими значениями. Также в заголовок результирующего HTML-документа следует скопировать имеющиеся в примере строки между <head> и </head>.
Формируемый HTML-код обеспечивает отображение свернутого списка. Выделим в генерируемом коде три вида строк:
Запишем указанные виды строк, используя выше выделенные теги.
p-строка:
<p class = "p" style = "margin-left:#px" onClick = "toggle(this, N)"><span id = "sN"> + </span></p><div id = "dN" style = "display:none">
0-строка:
<p style = "margin-left:#px">Элемент списка</p>
d-строка:
<p style = "margin-left:#px">Элемент списка</p></div>...</div>
В приведенном оформлении с целью упрощения генерирующего кода открывающий тег div располагаются в одной строке с сопутствующим тегом span (см. оформление p-строки), а закрывающий тег div – в конце строки с последним абзацем div-блока (см. оформление d-строки). То есть при генерации кода для отображения приведенного в примере списка вместо строк
<p style = "margin-left:30px">Общий модуль СН</p>
</div>
будет создана следующая одна строка:
<p style = "margin-left:30px">Общий модуль СН</p></div>
В приводимая ниже схеме предполагается, что в описании списка (см. рис. 1), по которому список формируется, элементы списка разделены символом конца строки (\n) и не содержат этот символ:
Число уровней найдем после расщепления исходного описания списка в массив и анализа номера последнего элемента этого массива.
// Возвращает число уровней в списке
function findNL(arrT) {
s = arrT[arrT.length - 1];
return parseInt(s.substring(1, 2));
}
Вызов функции findN:
t = document.getElementById("txtr").value;
arrT = t.split("\n");
alert(findNL(arrT));
В приведенном коде тег с id = "txtr" - это текстовое поле textarea, содержащее описание списка (см. левое поле на рис. 1).
Для первого уровня nS = 1, а для последующих nS = nE + 1. Используем для поиска nE следующую функцию:
// Возвращает номер конечной строки уровня L
function clcNE(arrT, nS, L) {
for (k = nS - 1; k < arrT.length; k++) if (parseInt(arrT[k].substring(1, 2)) > L) return k;
return arrT.length;
}
Вызов функции clcNE:
t = document.getElementById("txtr").value;
arrT = t.split("\n");
// Ищем номер конечной строки уровня 1
nS = 1;
nE = clcNE(arrT, nS, 1);
alert(nE);
// Ищем номер конечной строки уровня 2
nS = nE + 1;
nE = clcNE(arrT, nS, 2);
alert(nE);
Как и ранее, тег с id = "txtr" - это текстовое поле textarea с описанием списка.
Массив arrP - это массив, информирующий о p-строках, arrL - это массив длин предусмотренных шаблоном номеров строк в описании списка (номера берутся без обрамляющих символов).
Выделение p-строки очевидно:
// Формирует для текущего уровня массивы arrP и arrL
function mkRrs(arrT, nS, nE, arrP, arrL) {
m = -1;
for (k = nS - 1; k < nE; k++) {
p = arrT[k].indexOf("#", 1);
m++;
arrP[m] = 0;
arrL[m] = p - 1;
if (k < nE - 1) {
p2 = arrT[k + 1].indexOf("#", 1);
if (p2 > p) arrP[m] = 1;
}
}
}
Вызов функции mkRrs:
t = document.getElementById("txtr").value;
arrT = t.split("\n");
// Ищем номер конечной строки уровня 1
nS = 1;
nE = clcNE(arrT, nS, 1);
alert(nE);
// Создаем массивы arrP и arrL
w = nE - nS + 1;
arrP = new Array(w);
arrL = new Array(w);
// Заполняем массивы arrP и arrL
mkRrs(arrT, nS, nE, arrP, arrL);
// Проверка
document.getElementById("txtr2").value = arrP.toString() + "\n" + arrL.toString();
Как и ранее, тег с id = "txtr" - это текстовое поле textarea с описанием списка. Тег с id = "txtr2" - это правое текстовое поле textarea на рис. 1.
Массив arrD - это массив, информирующий о d-строках.
Поиск d-строки также несложен:
Приведенную последовательность вычислений реализует следующая функция:
// Формирует для текущего уровня массив arrD
function mkRrD(nS, nE, arrP, arrL) {
w = nE - nS + 1;
arrD = new Array(w);
for (m = 0; m < w; m++) arrD[m] = 0;
for (m = 0; m < w; m++) {
if (arrP[m]) {
ntFnd = 1;
for (m2 = m + 2; m2 < w; m2++) {
if (arrL[m2] <= arrL[m]) {
arrD[m2 - 1] += 1;
ntFnd = 0;
break;
}
}
if (ntFnd) arrD[w - 1] += 1;
}
}
return arrD;
}
Вызов функции mkRrD:
t = document.getElementById("txtr").value;
arrT = t.split("\n");
// Ищем номер конечной строки уровня 1
nS = 1;
nE = clcNE(arrT, nS, 1);
// Создаем массивы arrP и arrL
w = nE - nS + 1;
arrP = new Array(w);
arrL = new Array(w);
// Заполняем массивы arrP и arrL
mkRrs(arrT, nS, nE, arrP, arrL);
// Заполняем массив arrD
arrD = mkRrD(nS, nE, arrP, arrL);
// Проверка
document.getElementById("txtr2").value = arrP.toString() + "\n" + arrL.toString() + "\n" + arrD.toString();
Как и ранее, теги с id "txtr" и "txtr2" - это соответственно левое и правое текстовые поля на рис. 1.
Имея массивы arrP и arrD и зная состав (теги) генерируемого HTML-кода, несложно создать и код, отображающий раскрывающийся HTML-список:
// Формирует код одного уровня раскрывающегося HTML-списка
function mkNLvl(arrT, nS, nE, arrP, arrL, arrD) {
nLvl = "";
pS = '<p class = "p" style = "margin-left:#px" onClick = "toggle(this, N)">'
spn = '<span id = "sN"> + </span>';
dv = '<div id = "dN" style = "display:none" >';
zrr = '<p style = "margin-left:#px">';
p = '</p>';
m = -1;
for (k = nS - 1; k < nE; k++) {
m++;
nW = arrL[m];
fNW = Math.floor(0.5 * nW);
lmnt = arrT[k].substring(nW + 2);
if (arrP[m]) {
nPx = "" + 10 * fNW;
pS2 = pS.replace(/#/, nPx);
pS2 = pS2.replace(/N/, "" + k);
spn2 = spn.replace(/N/, "" + k);
dv2 = dv.replace(/N/, "" + k);
nLvl = nLvl + pS2 + spn2 + lmnt+ p + dv2 + "\n";
}
nPx = (fNW) ? "" + 30 * fNW : "20";
zrr2 = zrr.replace(/#/, nPx);
if (arrD[m]) {
nLvl = nLvl + zrr2 + lmnt + p;
for (k2 = 0; k2 < arrD[m]; k2++) nLvl += "</div>";
nLvl += "\n";
}
if(arrP[m] + arrD[m] == 0) nLvl = nLvl + zrr2 + lmnt + p + "\n";
}
return nLvl;
}
Вызов функции mkNLvl:
t = document.getElementById("txtr").value;
arrT = t.split("\n");
// Ищем номер конечной строки уровня 1
nS = 1;
nE = clcNE(arrT, nS, 1);
// Создаем массивы arrP и arrL
w = nE - nS + 1;
arrP = new Array(w);
arrL = new Array(w);
// Заполняем массивы arrP и arrL
mkRrs(arrT, nS, nE, arrP, arrL);
// Заполняем массив arrD
arrD = mkRrD(nS, nE, arrP, arrL);
// Формируем код одного уровня раскрывающегося HTML-списка
nLvl = mkNLvl(arrT, nS, nE, arrP, arrL, arrD);
// Проверка
document.getElementById("txtr2").value = nLvl;
Как и ранее, теги с id "txtr" и "txtr2" - это соответственно левое и правое текстовые поля на рис. 1.
Код для отображения всего раскрывающегося HTML-списка создается в цикле, обеспечивающим последовательную обработку уровней в описании списка:
// Формирует раскрывающийся HTML-список
function mkTrVw() {
t = document.getElementById("txtr").value;
if (t.length == 0)
alert("Empty");
{
arrT = t.split("\n");
nL = findNL(arrT);
nS = 1;
trvw = "";
for (L = 1; L <= nL; L++) {
// Ищем номер конечной строки уровня L
nE = clcNE(arrT, nS, L);
// Создаем массивы arrP и arrL
w = nE - nS + 1;
arrP = new Array(w);
arrL = new Array(w);
// Заполняем массивы arrP и arrL
mkRrs(arrT, nS, nE, arrP, arrL);
// Заполняем массив arrD
arrD = mkRrD(nS, nE, arrP, arrL);
// Добавляем код текущего уровня раскрывающегося HTML-списка
trvw += mkNLvl(arrT, nS, nE, arrP, arrL, arrD);
nS = nE + 1;
}
// Отображаем результат
t2 = document.getElementById("txtr2")
t2.value = trvw;
}
}
Как и ранее, теги с id "txtr" и "txtr2" - это соответственно левое и правое текстовые поля на рис. 1.
Отображенный в правом поле результат следует вставить в HTML-документ, содержащий в заголовочной части приведенные выше теги script и style.
Для получения кода, отображающего раскрывающийся HTML-список, можно воспользоваться тестовыми данными, которые попадут в нижеследующее поле "Содержание раскрывающегося списка" после нажатия на кнопку "Заполнить". Далее нажимается кнопка "Сформировать". Также можно подготовить, следуя шаблону, и свои данные.
Содержание раскрывающегося списка | HTML-код раскрывающегося списка |
Кнопка "Сохранить" отвечает за запись в Cookie левого поля, а кнопка "Очистить" - за очистику правого поля.
По тестовому набору создается следующий раскрывающийся список:
+ P 1
+ P 2
+ P 3
В целом цель работы достигнута. Для повышения качества результата следует добавить в генерируемый код элементы, обеспечивающие привычное оформление раскрывающегося дерева - контуры ствола и ветвей, а также уточнить размеры левых границ подуровней списка. Кроме того, полезно перед формированием раскрывающегося HTML-списка проверить его описание на предмет соответствия шаблону.
1. Поллок Д. JavaScript. Руководство разработчика. - Спб.: Питер, 2006. - 544 с.