Учебник по программированию.

Учимся программировать вместе с JavaScript.

 

 

Учебник по программированию. Первые шаги. Язык программирования PascalABC.NET.

 


Учебник по программированию. Создание сайтов. Первые шаги.

Глава IV.
Становимся программистами.

Данная глава посвящена решению задач из 20 параграфа. Здесь будет приведён материал, которого не было в предыдущих главах, поэтому рекомендуется после того, как вы решили (или попробовали решить) задачи самостоятельно ознакомиться с данной главой. Так же она будет полезна для того, что бы точнее понять, что имелось в виду в условии задачи.

§21. Вывод дерева элементов.

Прежде чем начинать писать код, необходимо сформулировать, что должно получиться в итоге.

В данной задаче нам необходимо написать программу, которая будет выводить дерево элементов html документа. Выводить дерево будем в новое окно браузера. Само дерево построим за счёт изменения свойств CSS. Каждый элемент в дереве будет выглядеть как прямоугольник, внутри которого будет находиться имя тэга элемента. Т.к. сам текст так же является отдельным элементом, при этом он не имеет тэга, то вместо тэга у такого элемента в прямоугольнике будет находиться сам текст. Так же из дерева элементов необходимо исключить комментарии и текстовые элементы, которые не отображаются в браузере, такие как пробелы и символы конца строки. Элементы головы документа так же отображать не будем.

Далее на рисунке представим эскиз примерно того, что должно получиться:

Эскиз дерева элементов.

Дерево элементов в простейшем виде.

В простейшем виде дерево элементов может выглядеть следующим образом: каждый элемент пишется в новой строчке, но имеет отступ от левого края в соответствии с родством. Т.е. элементы потомки должны быть правее своих родителей.

Построение такого дерева позволит нам сделать первый шаг.

Осуществить процесс построения простейшего дерева можно с помощью рекурсии. Т.е. создадим функцию, которая будет выводить элемент в новой строчке и если данный элемент имеет потомков, то функция будет вызывать сама себя для вывода уже потомков. Если элемент не имеет потомков, после его вывода необходимо будет просто выйти из функции.

При создании кода нам понадобиться функция open(), которая создаёт новое окно и возвращает на него ссылку.

Примечание: на практике в целях безопасности при простом вызове функции open() браузеры, как правило, блокируют создание новых окон и выводят сообщение, в котором можно снять блокировку. Эта функция не блокируется браузерами только в том случае, если она была вызвана в результате действия пользователя, например нажатия на кнопку. В нашем случае кнопка добавится к дереву элементов, что может быть не желательно, поэтому при отображении документа будем разрешать создание нового окна вручную.

Так же нам необходимо знать о том, что для вывода элемента в дерево достаточно вывести сам объект олицетворяющий элемент. В таком случае JavaScript автоматически преобразует объект в строку типа «[object HTMLBodyElement]».

Далее пример получившегося из вышеприведённых размышлений кода:

<!DOCTYPE html> <head> </head> <html> <body> <div id = "div_1"><span>Привет </span><span>всем </span><span>!</span></div> <div><span>Здравствуйте </span><span>дорогие </span><span>друзья </span><span>!</span></div> <script> var str = '';//Будет содержать в себе дерево элементов function DOM_Tree(el,s)//Создаёт дерево элементов //el – элемент, который необходимо вывести //s - отступ { str += s +el + '<br>'; if (el.childNodes.length == 0)//Если нет потомков, то return;//выходим из функции else //Для каждого потомка вызываем эту же функцию с отсупом + 2 пробела for (var i = 0; i<el.childNodes.length;i++) DOM_Tree(el.childNodes[i],s+'&nbsp;&nbsp;'); } //Формируем строку с деревом элементов DOM_Tree(document.body,''); //Создаём новое окно var treeWin = open(); //Выводим дерево в новое окно treeWin.document.write(str); </script> </body> </html>
Документ DOM_Tree.html.
Дерево элементов в простейшем виде.

Далее усовершенствуем код таким образом, что бы текстовые элементы выводились в виде текста, а остальные элементы выводились в виде тэга. Так же не будем выводить комментарии и содержимое элемента script. Плюсом ко всему все символы, коды которых меньше 32, так же выводить не будем, т.к. они не отображаются браузерами и могут быть отдельным текстовым элементом.

Здесь нам понадобиться свойство data всех текстовых элементов, в котором содержится сам текст.

Далее приведём только код получившейся:

<script> var str = ''; function DOM_Tree(el,s) { if ((el=='[object Text]'&&el.data<32)||el=='[object Comment]') return; str += s +(el=='[object Text]'?el.data:el.tagName) + '<br>'; if (el.childNodes.length == 0 || el.tagName == 'SCRIPT') return; else for (var i = 0; i<el.childNodes.length;i++) DOM_Tree(el.childNodes[i],s+'&nbsp;&nbsp;'); } DOM_Tree(document.body,''); var treeWin = open(); treeWin.document.write(str); </script>
Дерево без ненужных элементов.

Создание массива элементов.

Для графического построения дерева элементов нам понадобиться создать массив всех элементов. Причём каждый элемент в массиве должен содержать в себе следующие данные: текст; уровень; координаты; потомки.

Текст  это либо тэг, либо текст (в том случае, если это текстовый элемент). Уровень  уровень в дереве элементов, следующий рисунок наглядно демонстрирует, что имеется в виду:

Уровни и координаты в дереве элементов.

Координаты  координаты точки соединения рамки элемента с линией, соединяющей с родителем (будут высчитаны на этапе рисования). На предыдущем рисунке одна такая точка обозначена.

Потомки  массив индексов потомков.

Для создания массива создадим функцию за основу которой возьмём функцию DOM_Tree из предыдущего примера.

Далее получившийся код:

<script> function El(t,l) {//конструктор элементов this.text = t;//тэг или текст элемента this.level = l;//уровень this.x;this.y;//координаты this.child = [];//массив потомков, будет содержать индексы } var elements = [];//массив элементов var iEl = 0;//Индекс массива элементов function DOM_Tree(el,l) { //el - элемент //l - уровень елемента //Если элемент комментарий или текстовый и не отображается, то выходим if ((el=='[object Text]'&&el.data<32)||el=='[object Comment]') return; //Создаём текст элемента t = el=='[object Text]'?el.data:el.tagName; //Создаём новый элемент в массиве var curEL = iEl++;//Индекс текущего элемента elements[curEL] = new El(t,l); //Если потомков нет или текущий элемент script, то if (el.childNodes.length == 0 || el.tagName == 'SCRIPT') return;//выходим else {//Если потомки есть, то var iChild = 0;//Индекс массива потомков for (var i = 0; i<el.childNodes.length;i++) { if (!(el.childNodes[i] == '[object Text]' && el.childNodes[i].data<32)) elements[curEL].child[iChild++] = iEl;//заполняем массив потомков DOM_Tree(el.childNodes[i],l+1);//для каждого потомка вызываем DOM_Tree } } } //Создаём массив элементов DOM_Tree(document.body,0); //Создаём новое окно var treeWin = open(); //Выводим массив элементов в новое окно for (var i = 0; i<elements.length; i++) { var str = ''; for (var j = 0;j<elements[i].level; j++) str += '&nbsp;&nbsp;'; treeWin.document.write(str,elements[i].text,'<br>'); } </script>
Дерево из массива элементов.

Как видно результат не изменился.

Теперь проверим правильность работы программы и выведем данные элементов получившегося массива:

treeWin.document.write(str,elements[i].text,'; уровень - ',elements[i].level,'; индекс - ',i,'; потомки - ',elements[i].child,'<br>');
Вывод данных из массива элементов.

Графическое построение.

Теперь, когда у нас есть массив элементов можем приступить непосредственно к рисованию дерева элементов.

Блоки элементов будем рисовать, создавая таблицы с рамками (именно таблицы для того, чтобы выровнять текст в них по вертикали). Для того, что бы можно было нарисовать блоки в произвольном месте, необходимо установить свойство position в absolute. Далее пример, в котором за счёт изменения свойств css выведено дерево элементов, такое же, как на эскизе приведённом вначале параграфа:

<!DOCTYPE html> <head> <style> .block { border:solid 1px; position:absolute; text-align:center; width:80px; height:50px; } .line { border-left:solid 1px; border-top:solid 1px; position:absolute; } </style> </head> <body> <!--Выводим блоки с элементами--> <table class = "block" style = "left:200px;top:20px;"><td>BODY</td></table> <table class = "block" style = "left:100px;top:100px;"><td>DIV</td></table> <table class = "block" style = "left:300px;top:100px;"><td>DIV</td></table> <table class = "block" style = "left:30px;top:180px;"><td>SPAN</td></table> <table class = "block" style = "left:160px;top:180px;"><td>SPAN</td></table> <table class = "block" style = "left:300px;top:180px;"><td>Дорогие<br>друзья!</td></table> <!--Соединяем блоки линиями--> <span class = "line" style = "left:240px;top:70px;height:15px;width:0px;"></span> <span class = "line" style = "left:140px;top:85px;height:15px;width:0px;"></span> <span class = "line" style = "left:340px;top:85px;height:15px;width:0px;"></span> <span class = "line" style = "left:140px;top:85px;height:0px;width:200px;"></span> <span class = "line" style = "left:140px;top:150px;height:15px;width:0px;"></span> <span class = "line" style = "left:340px;top:150px;height:30px;width:0px;"></span> <span class = "line" style = "left:70px;top:165px;height:15px;width:0px;"></span> <span class = "line" style = "left:200px;top:165px;height:15px;width:0px;"></span> <span class = "line" style = "left:70px;top:165px;height:0px;width:130px;"></span> </body>
Вывод дерева за счёт изменения свойств css.

Теперь, когда мы освоились с технологией вывода дерева за счёт изменения свойств css, можем приступить к написанию кода. В первую очередь вставим в новое окно таблицу стилей и напишем две функции для вывода блока и линий.

//Вставляем таблицу стилей var sStyleSheet = '<head>'+ '<style>'+ '.block {'+ ' border:solid 1px;'+ ' position:absolute;'+ ' text-align:center;'+ ' width:80px;'+ ' height:50px;'+ '}'+ '.line {'+ ' border-left:solid 1px;'+ ' border-top:solid 1px;'+ ' position:absolute;'+ '}'+ '</style>'+ '</head>'; treeWin.document.write(sStyleSheet); //Функция вывода блока //x,y - координата; s - строка внутри блока function blockDraw(x,y,s) { var sTemp = '<table class = "block" style = "left:'+ (x-40)+//Вставка координаты x 'px;top:'+ y+//Вставка координаты y 'px;"><td>'+ s+//Вставка текста '</td></table>'; treeWin.document.write(sTemp); } //Функция рисует линию от (x1;y1) до (x2;y2) function lineDraw(x1,y1,x2,y2) { var sTemp = '<span class = "line" style = "left:'+ (x1<x2?x1:x2)+ 'px;top:'+ (y1<y2?y1:y2)+ 'px;height:'+ Math.abs(y2-y1)+ 'px;width:'+ Math.abs(x2-x1)+ 'px;"></span>'; treeWin.document.write(sTemp); } //Проверка работы функции blockDraw blockDraw(100,20,'Hellow world!'); //Проверка функции lineDraw lineDraw(100,70,100,85);
Проверка работы функций blockDraw и lineDraw.

Далее нам необходимо рассчитать координаты блоков каждого элемента. В следующем приведённом коде это функция calcCoords. Она будет пользоваться функцией maxEl, которая вычисляет максимальное количество дочерних элементов на каком либо одном уровне. После того, как координаты всех блоков рассчитаны, можно нарисовать всё дерево.

Обратите внимание, что при выводе текстовых элементов в приведённом ниже коде выводится только первые 5 символов. Это необходимо для того, что бы блоки оставались фиксированного размера.

Понадобиться ещё функция lineConnect, которая соединяет блок элемента с блоками всех предков.

//Функция нахождения максимального количества дочерних элементов на одном уровне function maxEl(iEl) { var num = 0; if (elements[iEl].child.length == 0) num = 1 else { for (var i = 0;i<elements[iEl].child.length;i++) num += maxEl(elements[iEl].child[i]); } return num; } //Функция заполняет координатами элементы массива элементов function calcCoords(iEl,leftBorder) { var mEl = maxEl(iEl);//Макс количество элементов на уровне if (mEl == 0) elements[iEl].x = leftBorder + 50 else elements[iEl].x = mEl*100/2+leftBorder; elements[iEl].y = elements[iEl].level*80+20; if (elements[iEl].child.length>0) { var lB = leftBorder; for (var i = 0; i<elements[iEl].child.length; i++) { calcCoords(elements[iEl].child[i],lB); lB = lB+maxEl(elements[iEl].child[i])*100; } }; } calcCoords(0,0); //Функция соединяет линиями блоки элементов function lineConnect(iEl) { //Если нет предков, то выходим if (elements[iEl].child.length == 0) return; //Рисуем линии к каждому предку for (var i = 0;i<elements[iEl].child.length;i++) { var x1,y1,x2,y2; x1 = elements[iEl].x; y1 = elements[iEl].y+50; x2 = x1; y2 = y1+15; lineDraw(x1,y1,x2,y2) y1 = y2; x2 = elements[elements[iEl].child[i]].x; lineDraw(x1,y1,x2,y2) x1 = x2; y2 = elements[elements[iEl].child[i]].y; lineDraw(x1,y1,x2,y2) } } //Непосредственно рисуем блоки с элементами for (var i = 0; i<elements.length;i++) { blockDraw(elements[i].x,elements[i].y,elements[i].text.substr(0,5)); lineConnect(i); }
Результат работы программы вывода дерева элементов.

Примечание: если вы копируете код из книги, собирая его по частям, то для получения данного результата необходимо удалить код, который тестирует функции blockDraw и lineDraw.

Задачи.

1. Собрать весь код в отдельный файл js и протестировать программу вывода дерева элементов на своих html документах.

2. Модернизировать получившийся код таким образом, что бы все функции и переменные стали методами и свойствами одного объекта. Это необходимо для того, что бы не получилось совпадения имён при использовании кода в других html документах.

3. Сделать так, что бы при наведении мышкой на текстовый элемент выводилась всплывающая подсказка со всем текстом. Подсказка должна появляться через небольшой промежуток времени.

4. Добавить возможность увеличивать и уменьшать дерево элементов.