Веб-сайт с минимальными затратами: учебник HTML

Глава 12. Электронные документы

Глава 12. Электронные документы

Договор N10

Идея «скрыть-отобразить содержимое определённого уровня» используется в различных электронных документах: например, в договоре, оформленном в MS Word с помощью заголовков разного уровня, или в MS Excel, в таблице с промежуточными итогами. В подобных документах можно представить структуру так, что около заголовков определённого уровня появляются знаки «+» или «-» - при щелчке по этим знакам на экран выводятся (или удалются с экрана) заголовки ниже лежащих уровней.

Вот html-текст очень коротенького документа, на котором мы покажем, как эту задачу можно выполнить средствами HTML:

<meta http-equiv="content-type" content="text/html; charset=windows-1251">
<script src='dog1.js'></script>
<link rel="stylesheet" href="dog1.css" type="text/css">
<style type="text/css" id="st1"></style>
<div>
<h1>Договор N10</h1>
<p>ООО "Блик" в лице директора и ОАО "Трезвость" в лице председателя 
 заключили настоящий договор о нижеследующем:</p>
<h2><span id="s1" onclick="doit('s1')">+ </span>1. Предмет договора</h2>
<h3 class="s1">1.1. ООО "Блик" оказывает некие услуги.</h3>
<h3 class="s1">1.2. ОАО "Трезвость" оплачивает услуги.</h3>
<h2><span id="s2"  onclick="doit('s2')">+ </span>2. 
 Ответственность сторон</h2>
<h3 class="s2">2.1. Если услуги не оказаны, штраф.</h3>
<h3 class="s2">2.2. Если услуги не оплачены, штраф.</h3>
</div>

ex29 Файлы примеров: dhtml4 (dhtml4)

В html-структуру документа заложена следующая идея: className ряда нижележащих заголовков назвать так же, как и идендтификатор верхнего, управляющего заголовка (вернее, части заголовка - вложенного в заголовок элемента span). В файл css для заголовков нижнего (третьего, и, если бы они были, четвёртого-пятого) уровня записано правило:

h3 {font-size: 1em; margin:.5em 0 0 2em; display:none;}

То есть при открытии документа пользователь видит только заголовки уровня 1-2. Элементы span в начале каждого заголовка второго уровня содержат текст «-» (минус) и функцию doit в качестве значения своего свойства onclick:

function doit(cls) {
 var sc=document.getElementById(cls);
 var tg=document.getElementsByTagName ("h3");
 for(i=0; i

Функция находит все элементы следующего уровня (h3), затем проверяет, имеют ли найденные элементы className, равный идентификатору текущего элемента (вызвавшего функцию). Попутно функция меняет цвет префикса заголовка: если плюс, то красный, если минус - зелёный. Можете попробовать самостоятельно добавить к тексту заголовки более низких уровней (h4) и создать систему, отображающую или скрывающую их по щелчку на заголовках h3.

Договор N10: развитие

Как всегда, после взгляда на готовый продукт возникает ощущение его несовершенства. Опять, как и в работе над сносками книги, достаточно очевидно слабое место: большое количество именованных элементов. Задача первая - попробовать упростить структуру (и тем самым сделать её надёжнее). Второй момент - попробуем всё-таки углубить уровни вложенности текста и посмотреть, насколько это всё будет работать. Третье - нельзя ли сделать автоматическую нумерацию пунктов, как в Worde?

Первую задачу решить довольно легко, если вкладывать подчинённые данному элементу подзаголовки в один общий контейнер, например, div. После заголовка 1. ставим открывающий тэг <div>, затем пишем все пункты 1.1., 1.2. и т. д., затем ставим закрывающий тэг </div>. И всё хорошо: при щелчке мышью на заголовке ищем nextSibling, и в найденном элементе div ищем всех потомков (все вложенные элементы), и делаем их видимыми. Или ещё проще: делаем видимым или невидимым сам общий элемент div.

Вторую задачу выполним просто добавив в документ текст. Третья задача, к сожалению, простым способом в HTML не решается. Существуют специальные элементы - нумерованные списки: в тэги <ol> и </ol> вкладывается несколько элементов li (то есть фрагментов текста, заключённых в тэги <li> и </li>). Попробуйте - на экране все элементы li станут пронумерованными.

<ol>
<li>Первый</li>
<li>Второй</li>
<li>Третий</li>
</ol>

Стиль нумерации можно изменять: делать латинским цифрами, буквами… Внутрь одного списка ol можно вкладывать другие списки, HTML при этом будет делать на экране новые отступы в глубину текста для каждого уровня вложенности. Всё, в общем, хорошо, кроме одного: HTML не может делать многоуровневые списки, в каждом вложенном списке нумерация будет точно такой же, как и во внешнем: 1, 2, 3 - или a) b) c)...

Поэтому наша нумерация будет полуавтоматической: с помощью вложенных друг в друга списков, в начало пунктов которых мы будем вручную добавлять префикс - номер «родительского» заголовка для данной группы пунктов:

...
<ol><li><span class="s1" onclick="doit(this)">+ </span>
Предмет договора
<ol class="hiddn">
<li><span class="s2" onclick="doit(this)">+ 1.</span>
ООО "Блик" оказывает некие услуги.
<ol class="hiddn">
<li><span class="s3" onclick="doit(this)"> 1.1.</span>
Например, обувь чистит</li>
...

В самой первой строчке примера нет в явном виде номера (на экране появится «1»), и я не знаю, можно ли его быстро получить через DOM (объектную модель документа) HTML. В любом случае, конечно, можно просто пересчитать все элементы ol, проверить наличие у них потомков и вычислить нужный номер программно. Но не сейчас.

ex30 Файлы примеров: dhtml5 (dhtml5)

У нас пока куча других проблем: как теперь окрасить плюсы и минусы в разный цвет? Как заставить префиксы (вместе с плюсами) появляться точно перед началом пункта списка?

В предыдущей версии для окраски плюса мы просто назначали всему элементу span нужный цвет. Теперь у нас кроме плюса там есть ещё и префикс из чисел и точек. Его красить не нужно. Для такой ситуации в системе CSS существуют псевдоэлементы :first-letter. Мы пишем, например, в правиле: p:first-letter {color:red} - и первая буква каждого абзаца окрашивается в красный цвет. Так же поступим и в нашем случае:

span:first-letter {color:#990000; }

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

В HTML-CSS можно позиционировать элементы весьма произвольно, если задать им правило position:absolute. После задания этого правила можно указать точные координаты элемента на странице, и он встанет на это место поверх любых других элементов (невзирая на их собственное расположение).

У нас проблема в том, что префикс располагается где-то в середине элемента li, надо его сдвинуть влево, к началу элемента. Для этого после задания абсолюной позиции надо задать элементу ещё и параметр left - сколько отступать слева (можно и отрицательное значение). От чего именно браузер будет отмерять отступ влево? В явном виде этого указать нельзя, но мы знаем, что правила CSS гласят: отступ будет делаться от границы ближайшего позиционированного предка данного элемента. Если нет ни одного позиционированного вообще - от границы элемента body.

Нам, конечно, удобнее вести отсчёт от самого ближайшего предка - элемента li, в который вложен наш span. Для этого li надо сделать позиционированным: задать какое-нибудь значение его свойству position, всё равно, какое. Как раз для «всё равно какого» в CSS есть удобное значение relative - оно предназначено для небольших смещений элемента относительно его собственной нормальной позиции; если не задавать никакого смещения, элемент так и останется на своём месте, но станет позиционированным. CSS в результате станет примерно таким:

span {cursor:pointer; font-weight:normal; position:absolute; 
  left:-3.5em; }
span.s1, span.s11 {left:-2.8em;}
span:first-letter {color:#990000; }
li.ie {list-style-type: none }
ol {margin:0 0 1em 3em; line-height:1.4em; padding:0}
li {position:relative; padding:0}

Ну, и ещё куча классов для разных span из-за того, что в разных li они почему-то смещаются по-разному (весь текст полностью в файле dog1.css) - потому что у нас объективно три разной длины варианта span: плюс без префикса (в первом уровне), плюс с префиксом (во втором), префикс без плюса (в третьем). Да ещё отдельно стоящий плюсик вверху, для показа всего текста всех уровней сразу. И на закуску самое «сладкое»: вся эта конструкция в Интернет Эксплорере версии 5 не работает. Поэтому таблицу стилей надо подключать динамически с самого начала, при загрузке страницы, и при этом определять не только типы, но и версии браузеров.

Нельзя во время загрузке страницы найти элемент llink rel="stylesheet"... по идентификатору, потому что пока страница полностью не загрузилась, браузер ещё не видит всех элементов. Можно, например, записать распознавание браузера в функцию и вызывать её по событию onload (<body onload="функция">). Но мы для разнообразия поступим сейчас по-другому (таких примеров нам ещё не встречалось): вообще ничего не напишем в html-коде про самый главный CSS-файл для нашего html-документа, а в файле dog1.js вне функций (то есть прямо в процессе загрузки страницы) сформируем динамическую запись в html-код, изменяющуюся в зависимости от типа и версии браузера:

 var agent = navigator.userAgent.toLowerCase();
 var css;
 if (agent.indexOf("msie 5") > -1 && agent.indexOf("opera") == -1) 
  css="dog1ie5.css";
 else if (agent.indexOf("msie 6") > -1 && agent.indexOf("opera") == -1) 
  css="dog1ie6.css";
 else css="dog1.css";
 document.write("<link rel='stylesheet' href='"+css+"' type='text/css' />");

Похожий код мы уже использовали в других примерах, разница в том, что мы ищем теперь в названии браузера не просто «msie», а «msie 5» или «msie 6». Ну, и не изменяем атрибут существующего элемента, а, используя метод document.write, создаём в процессе открытия страницы новый элемент link. В функции, раскрывающей очередной уровень списка, много деталей, которые нам ещё не встречались и требуют пояснения:

function doit(a) {
 var master=a.parentNode;
 var slaves=master.childNodes;
 for(var el in slaves) {
  if (slaves[el].tagName=="OL") {
   slaves[el].style.display=(slaves[el].style.display=="block")
     ?"none":"block";
  }
 }
 var s = String(a.firstChild.nodeValue);
 var clss=a.className;
 a.className=(clss.length==2)?clss+"1":clss.substr(0,2);
 a.firstChild.nodeValue=(s.charAt(0)=="+")?
   s.replace("+", "–"):s.replace("–", "+"); 
}

Во-первых, конструкция for... in: предварительно мы создаём массив из всех потомков элемента, в который вложены строки списка, записываем этот массив в переменную slaves; затем пишем волшебную формулу с for... in: «для каждого идентификатора элемента el в массиве slaves найти по этому идентификатору сам элемент массива (slaves[el]) и выполнить для него некоторые действия - сделать видимым или невидимым».

Затем мы записываем в переменную s текст текущего элемента span (по которому щёлкнули) и если первый символ этого текста (находим с помощью метода charAt(0) - нумерация символов начинается с нуля) «+», меняем его на «–» с помощью метода replace("+", "–") (и наоборот). Затем меняем имя класса текущего элемента: если длина имени (clss.length) равна двум (значит, имя имеет вид «s1», «s2» и т. д.), добавляем к этому имени единицу (и оно становится, соответственно, «s11», «s21» и т. д.), а в правилах нашего главного CSS-файла записано, как меняются в зависимости от этого цвета первого символа элемента «span». Ну, а если длина имени класса не равна двум (значит, она больше), сокращаем до длины в два символа: clss.substr(0,2) - «выбрать из переменной clss подстроку начиная с первого символа и длиной в два символа».

Функция, запускаемая отдельно стоящим вверху плюсиком, работает в точности так же, как и функция из примера omne\js5 (отображение или сокрытие всех примечаний в документе). Разумеется, в нашем последнем примере функция показа всех уровней и функция показа отдельного фрагмента противоречат друг другу по идеологии, и после нескольких щелчков по плюсам, вызывающим эти разные функции, они начинают отображать (скрывать) содержимое не так, как ожидалось. В этом случае не остаётся ничего другого, как нажать функциональную клавишу F5.

«Естественный» WYSIWYG редактор HTML

Мы уже показывали в Главе 8, как построить импровизированный визуальный редактор HTML из хорошего текстового редактора с синтаксической подсветкой и браузера. Но под «визуальностью» можно понимать разное. В том нашем случае - вписал тэг <b> и </b> в код документа, нажал в браузере «F5» - и наглядно видишь, как шрифт стал полужирным. Существует ещё другая визуальность, более высокой степени: выделил фрагмент текста мышкой, нажал какую-нибудь кнопку, и шрифт стал полужирным. Называется WYSIWYG - what you see is what you get («что вы видите, то и получите», то есть если в результате ваших действий шрифт на экране стал полужирным, он таким и останется на вашей страничке. Правда, в этом втором случае вы можете так и не узнать, что шрифт становится полужирным благодаря тэгу <b> (а может быть, и <strong>!). Но всё равно приятно бывает иногда вот так, запросто пооформлять html-страничку, без всяких там тэгов и джава скриптов. А иногда, как в нашем последнем документе (договоре), такое непосредственное редактирование бывает даже удобнее, чем правка html-кода.

На ОС Windows давно существует, так сказать, «естественный» визуальный редактор html-страниц: почтовая программа Outlook Express (мы говорили о ней в Главе 8). Если вы не меняли её настройки (в частности, формат отправляемых сообщений), то при написании письма можете делать шрифт полужирным и курсивным, менять его размер и цвет и ещё кое-какие атрибуты текста. Всё это в результате записывается и отправляется получателю в виде html-кода. Но вы можете и никуда не отправлять написанное и оформленное «письмо», а сохранить его как файл в формате html (через меню «Файл» - «Сохранить как». А потом посмотреть код в файле. Впрочем это баловство. Нам ведь надо наоборот, html-файл как-то поместить в Outlook Express и там редактировать (а уже потом сохранить обратно в файл).

Это делается с помощью такого понятия, как «Документы на бланке». Выберите в меню OE «Создать» - «Документ на бланке» - «Выбор бланка», найдите в открывшемся окне нужный вам файл - и он появится в OE, доступный для непосредственного визуального редактирования. После внесения изменений выберите пункт меню «Сохранить как» и сохраните файл в формате html с нужным именем.

Редактировать (визуально) html-файл можно и прямо в окне Интернет Эксплорера - внутренний механизм Windows тот же самый, что и при редактировании в Outook Express, только этот механизм в IE по умолчанию не доступен, его надо запустить с помощью небольшого скрипта и служебного html-файла. Мы не будем в этой книге разбирать работу скрипта, разрешающего редактирование html-файла прямо в окне IE, потому что этот скрипт использует нестандартные возможности IE (таких возможностей нет в спецификациях W3C). Однако использовать скрипт вы можете и без объяснений: сохраните к себе на компьютер архив с папки CD omne\soft\menuExt\menuext.zip (soft/menuExt/menuext.zip), распакуйте его, найдите в папке install файл menuExt.inf, щёлкните по нему правой кнопкой мыши и выберите из контекстного меню пункт «Установить». Вместе с доступом к html-редактированию будет установлено ещё несколько надстроек для IE (все они описаны в файле readme.htm, находящемся в том же архиве). Удалить установленные надстройки можно через «Панель управления» компьютера - «Установка и удаление программ» - «IEMenuExt».

После установки IEMenuExt и перезапуска IE можно щёлкнуть по странице правой кнопкой мыши, выбрать из выпавшего меню пункт «Редактировать» и работать с текстом прямо как в текстовом редакторе, только не видя html-тэгов. После окончания редактирования надо через меню «Файл» (или комбинацией клавиш Ctrl+s) сохранить документ.

ie03.gif

Мы не разбираем здесь все возможности IEMenuExt, потому что работают они только в Интернет Эксплорере, и, по сравнению с некоторыми стандартными функциями браузеров Gecko (мы упоминали уже об Инспекторе DOM), выглядят как неуклюжие костыли. Даже и визуальное редактирование гораздо лучше реализовано в одном из последних свободно распространяемых браузеров Gecko - SeaMonkey. Дистрибутив этого браузера можно скачать из Интернета (наберите в строке поиска Яндекса слова «скачать SeaMonkey»); единственный его недостаток - объём: если дистрибутив Оперы занимает 4 Мб, Firefox'а - 5 Мб, то SeaMonkey - все 12 Мб. Зато там есть этот самый Composer (визуальный редактор) и встроенная почтовая программа.

Composer SeaMonkey, несомненно, гораздо более мощный инструмент редактирования html-страниц, чем IE, однако и на солнце бывают пятна: последний документ, над которым мы работали, выглядит в Composer'е очень бледно:

edit01.gif

В нём попросту невозможно непосредственно (как, собственно, мы мечтали) редактировать пункты ниже первого уровня вложенности - их не видно на экране. В IE же при переходе в режим редактирования раскрываются (как и в Outlook Express) все скрытые объекты:

edit02.gif

И мы можем создавать новые пункты, сохраняя структуру нашего документа, с помощью обычного копирования и последующей замены текста. Выделите последний пункт в договоре, затем через меню Edit (Правка) выберите пункт Copy (Копировать).

edit03.gif

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

edit04.gif

Теперь напечатайте новый текст:

edit05.gif

Затем удалите оставшуюся от старого текста букву, охранявшую форматирование (отделявшую элемент span от остального текста). Теперь можете даже выделить текст и с помощью комбинаций клавиш Ctrl+B сделать его полужирным; а для выделения курсивом нажать Ctrl+I (так же работают сочетания клавиш в Outlook Express, да и в Word'е). Теперь сохраните файл через меню Файл - Сохранить, закройте его и откройте снова в браузере - новая строчка добавлена! Причём, благодаря нашей «полуавтоматике» вам не нужно ставить очередной номер пункта; а если вы удалите какую-то строчку, остальные изменят нумерацию автоматически.

edit06.gif

Создание оглавления

Оглавление для большого документа создаётся и работает по принципу примечаний, описанному в Главе 10: в нужное место текста, перед заголовком, вставляется именованная закладка вида <a name="a02t">&#60;**</a>; в текст оглавления вписывается ссылка на эту закладку, атрибут href которой имеет вид путь_к_документу#имя_закладки (файл на CD omne\htm6\wattsa.htm).

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

ex31 Файлы примеров: htm7 (htm7)

В примере на сайте показан простейший вариант такой системы; основной файл pframe.html, содержащий фреймы-ссылки на содержательные страницы так мал, что его текст можно привести здесь целиком:

<title>pushkin-frame</title>
<frameset  cols="29%,*">
  <frame name="toc" src="../img/toc.html">
  <frame name="main" src="../img/p1.html" >
</frameset>

Во второй строке находится важный атрибут элемента frameset- cols: в нём задаётся относительная ширина левого фрейма в 29% от общей ширины окна, а правому фрейму отдаётся всё остальное пространство - *. Каждому фрейму с помощью атрибута src назначается документ для загрузки, а также имя, которое будет использоваться в ссылках: раз у нас в одном окне теперь несколько «субокон», браузер при щелчке по ссылке должен знать, в каком именно «субокне» открывать документ (или закладку). Текст оглавления (файл toc.html, который будет загружаться в левый фрейм под именем toc) также не очень велик:

<title>pushkin-toc</title>
<meta http-equiv="content-type" content="text/html; charset=windows-1251">
<style type="text/css">a {display:block; margin-top:.7em}</style>
<a href="p1.html#toc8" target="main">Стихи</a>
<a href="p1.html#toc9" target="main">МОЯ ЭПИТАФИЯ</a>
<a href="p1.html#toc22" target="main">ЭКСПРОМПТ НА АГАРЕВУ</a>
<a href="p1.html#toc38" target="main">НАДПИСЬ НА СТЕНЕ БОЛЬНИЦЫ</a>
<a href="p1.html#toc51" target="main">ЗАВЕЩАНИЕ К<ЮХЕЛЬБЕКЕРА></a>
<a href="p1.html#toc64" target="main">Проза</a>
<a href="p1.html#toc65" target="main">ПИКОВАЯ ДАМА</a>
<a href="p1.html#toc66" target="main">I.</a>

(Мы назначили элементу a правило display:block, чтобы не вкладывать его в элементы p). В файле p1.html (который будет загружаться в правый фрейм под именем main) рядом с соответствующими заголовками расставлены все необходимые метки (закладки), имеющие атрибуты href="toc8", "toc9" и т. д.

Всю эту нудную и кропотливую работу по расстановке меток-закладок и «собиранию» оглавления можно автоматизировать. Причём, не для какого-то конкретного документа, а раз и навсегда, чтоб больше уже об этом не думать.

Автоматическое оглавление для одного документа

Сначала всё-таки попробуем автоматизировать создание оглавления для конкретного документа p1.html.

ex32 Файлы примеров: dhtml6 (dhtml6)

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

Предположим, нам удалось сгенерировать оглавление (набор ссылок на закладки в основном документе). Как браузер узнает, в каком окне надо будет прокручивать текст (наши документы ведь не во фреймах)? По такой же методике, как и для фреймов: в атрибуте target каждой ссылки мы должны будем указать имя окна основного документа. А какое это имя, где оно записано? Вот пока нигде не записано, его и нет, оно никакое. К счастью, его можно записать в скрипте с помощью обыкновенной формулы: window.name="main", после чего задать значение атрибута ссылок target равным этому имени, то есть тоже "main".

Общий алгоритм работы скрипта по созданию оглавления будет таков: 1) назначить текущему окну имя main; 2) открыть новое окно (window.open); 3) найти все заголовки в основном документе (элементы h1-h6); 4) вставить рядом с каждым найденным заголовком метку, записать в окно оглавления соответствующую метке ссылку.

2) (Первый пункт мы, можно сказать, уже выполнили, поэтому переходим сразу ко второму). Синтаксис команды, открывающей новое окно, таков: window.open(url, name, свойства), где url - путь к файлу, который будет загружен в новое окно (у нас файла нет), name - в нашем случае какое угодно имя, свойства состоят из целого набора определений. Тут не до конца ещё всё ясно, похоже, что W3C не определяет толком в своих спецификациях объект window, поэтому его свойства надо искать в описаниях работы конкретных браузеров (например, на стр. http://www.mozilla.org/docs/dom/domref/dom_window_ref76.html#1019331). По счастью, свойства, которые нам нужны, одинаковы в Gecko и в Интернет Эксплорере - это размеры окна, его положение на экране и некоторые мелкие детали типа полос прокрутки.

Ширина и высота нового окна так и назначается: width=400, height=600 (как слышится, так и пишется). Позицию мы установим на экране - крайнюю правую, для этого надо вычислить координату левого угла окна, она будет равна разнице между общей шириной экрана и шириной самого окна: screen.availWidth-400; status - отображать строку состояния внизу окна, resizable - позволять пользователи произвольно менять размеры, scrollbars - полосы прокрутки. Целиком функция, открывающая новое окно, будет выглядеть так:

var tocwin, toc, i=0;
function maketoc(){
 var left=screen.availWidth-400;
 var args = "width=400,height=600,left="+left+",top=20,scrollbars=yes,resizable=yes,status=yes";
 tocwin = window.open('',"toc",args);
 tocwin.focus();
 toc=tocwin.document.open();
 toc.write("<meta http-equiv='content-type' content='text/html; charset=windows-1251'>");
 toc.write("<style type='text/css'>a {display:block}</style>");
 toc.close();
 window.name="main";
 testlist(document.body);
}

Часть переменных (первая строка) мы инициализируем вне функции, чтобы их значения можно было передавать от одной функции к другой. После открытия окна с заданными параметрами мы передаём ему фокус - оно становится самым верхним среди всех окон на экране. Затем мы открываем в новом окне документ для записи, записываем в него некоторую служебную информацию (мета-тэг, стиль) и закрываем документ - на экране он, конечно, останется, но записывать туда новую информацию методом document.write() после «закрытия» будет нельзя. В конце мы вызываем новую функцию, которая и будет просматривать всё содержимое текущего документа и по определённым нами правилам создавать для него оглавление:

function testlist(elem){
 var elist=elem.childNodes, al, text, j, id;
 for (var el in elist) {
 i++;
 if (elist[el].tagName=="H1" || elist[el].tagName=="H2" || elist[el].tagName=="H3" || elist[el].tagName=="H4" || elist[el].tagName=="H5" || elist[el].tagName=="H6") {
  j=elist[el].tagName.substr(1,1);
  al = document.createElement("A");
  id = "toc"+i.toString();
  al.Name=id;
  al.id=id;
  text=tocwin.document.createTextNode(elist[el].firstChild.nodeValue);
  elist[el].insertBefore(al,elist[el].firstChild);
  al = tocwin.document.createElement("A");
  al.href=tocwin.opener.location+"#"+id;
  al.target="main";
  al.style.marginLeft=j+"em";
  al.style.marginTop=(j>3)?".5em":"1em";
  al.appendChild(text);
  tocwin.document.body.appendChild(al);
  }
  else {
   if (elist[el].nodeType==1) {
    testlist(elist[el]);}
   }
 }
}

В качестве параметра elem предыдущая функция передаёт новой элемент body основного документа. Общий алгоритм работы функции таков: она выбирает всех потомков элемента, переданного в качестве параметра (в начале - body), проверяет, не является ли данный потомок заголовком (<h1> - <h6>); если является, производит манипуляции с оглавлением, иначе проверяет, является ли данный потомок элементом, могущим иметь своих потомков (вдруг в body, как у нас, вложен элемент div), и если является, вызывает для этого элемента ту же самую функцию testlist(elem) - и так, пока не будут просмотрены все вложенные друг в друга элементы в документе. Рекурсия такая (функция вызывает сама себя).

Для создания новых элементов мы используем метод document.createElement() (а также document.createTextNode() для создания текста новых элементов), для вставки вновь созданных элементов в документ - два разных метода: insertBefore (чтобы вставить закладку впереди заголовка в основном документе) и appendChild (чтобы вставить очередной пункт оглавления в конец всех существующих пунктов в новом окне).

Ссылкам в оглавлении мы назначаем отступ от левого края в зависимости от уровня заголовка: чем ниже уровень, тем больше число (максимальное - в заголовке h6), и это число мы делаем количеством единиц отступа «em». Отступ от верхнего края назначаем проще: если заголовок уровня 1-3, отступ 1em, если другого уровня - 0.5em.

Самая сложная формула получается для атрибута href ссылок: tocwin.opener.location+"#"+id. Здесь opener - окно, которое открыло данное новое окно; location - путь к файлу, открытому в окне (в данном случае - в старом окне); id состоит у нас из слова toc и некоторого числа, получаемого обычным приращением переменной i в цикле.

Вот, пожалуй, и все хитрости. Запускаем скрипт щелчком по ссылке «Создать оглавление» - открывается новое окно в правом верхнем углу экрана и в него записываются ссылки на закладки, расставляемые перед каждым заголовком в основном окне.

Остаётся мелочь: сделать так, чтобы этот скрипт мог запускаться не только в нашем «специальном» документе, но вообще в любом документе HTML. Это возможно.

Автоматическое оглавление для всех

Запускать свой собственный скрипт на произвольной странице можно с помощью меню обозревателей «Избранное» (в Gecko - «Закладки», «Bookmarks»). В Избранном находятся обыкновенные файлы url (или записи в html-документе, имеющие вид файла url). Вот типичный пример содержимого такого файла:

[InternetShortcut]
URL=http://www.large.ru/bookmarklets/

Хитрость, которую придумали люди (сейчас уже трудно установить, кто именно был первым, я сам получил информацию с сайта www.large.ru), заключается в том, чтобы после знака равенства написать вместо пути к файлу код JavaScript:

[InternetShortcut]
URL=javascript:alert("Hi!");

ex33 Файлы примеров: dhtml7 (dhtml7)

После того как вы создали файл url, его можно обычным перетаскиванием с помощью мыши поместить в меню IE Избранное (Favorits): нажать мышкой на имя файла, тащить в IE в меню «Избранное», держать мышку, не отпуская, пока не раскроется пункт меню, затем подтащить мышь куда-нибудь между другими ссылками и отпустить там. Так же можно поместить нужные url-файлы и в меню браузеров Gecko «Закладки» («Bookmarks»).

Если вы установили надстройку IEMenuExt, описанную в этой главе в разделе «Естественный» WYSIWYG редактор HTML, то в вашем меню IE «Избранное» уже находится несколько таких мелких закладок-скриптов (называемых в просторечии «закладурками»). Можно, казалось бы, записать наш скрипт для оглавления в файл url и поместить в «Избранное» очередную заклад(ур)ку. Существуют, однако, два ограничения: скрипт должен быть записан одной строкой (без переводов строк внутри файла) и иметь общую длину не более 512 символов. Первое условие (удалить в скрипте все переводы строк) выполнить несложно, а второе для нашей задачи невыполнимо вообще.

Однако это не означает невыполнимости самой задачи. Придётся немного изменить архитектуру решения: маленький скрипт будет создавать в открытом документе (любом!) ссылку (с текстом «Создать оглавление») для запуска большого скрипта. При этом большой скрипт должен будет находиться в заранее условленном месте вашего компьютера, в примере мы сделали этим местом папку C:\www (предполагая, что вы уже создали её при работе с предыдущими главами книги). Таким образом, текст «малого скрипта» в составе url-файла toc.url будет выглядеть примерно так:

[InternetShortcut]
URL=javascript:function $(w){with (w.document) {var b=body, text=createTextNode("Создать 
оглавление"), a=createElement("A"), s=createElement("SCRIPT")}; a.href="javascript:maketoc()"; 
a.appendChild(text); s.type="text/javascript"; s.src="../img/file:///C:/www/toc.js"; 
b.insertBefore(a,b.firstChild); b.insertBefore(s,b.firstChild);} $(this);

Никаких переводов строк (кроме перевода после «[InternetShortcut]») в файле быть не должно. Если вы поместите этот файл в «Избранное» IE, а затем выберете вновь появившуюся закладку «toc» в том же меню, в активном окне (в самом верху текста документа) появится надпись-ссылка «Создать оглавление». Если вы уже скопировали файл toc.js (из папки CD omne\dhml7) в нужное место - C:\www, то можете щёлкать по вновь созданной ссылке - и оглавление появится в новом окне (если в основном документе есть хотя бы один заголовок).

Запустить скрипт через «Закладки» в Опере можно даже и не пытаться. В браузерах Firefox и SeaMonkey система работает, но с некоторыми ограничениями:

  1. Лучше файл url сохранять в формате UNICODE (точнее, utf-8): открыть его в Блокноте (или редакторе Bred), выбрать в меню «Файл» пункт «Сохранить как», выбрать формат (или кодировку) utf-8 и сохранить под другим именем (в наших примерах сохранено под именем tocu.url);
  2. Найстройки безопасности браузеров Gecko не позволяют создавать оглавление для страниц, открытых прямо с сервера в Интернете (а не с локального диска). Вообще-то это логично: браузер оберегает нас от попыток любой интернет-страницы запустить какие-то программы на нашем компьютере. Правда, браузер почему-то не понимает, что мы сами же и вписали (с его помощью) в экземпляр страницы, открытый на нашем компьютере «подозрительный» код - ссылку для запуска скрипта C:\www\toc.js.

С учётом описанных ограничений можно сказать, что система создания оглавления надёжно работает в браузерах Gecko с локальными (сохранёнными на ваш компьютер) интернет-страницами.

ex34 Файлы примеров: dhtml7 (dhtml7)

Комментарии