Byte/RE ИТ-издание

Новые возможности DHTML

Виталий Сизов
sva@hot.ee

Программируемые стили

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

Эксперименты со стилями начала компания Netscape — уже с версии 4 браузер Netscape Navigator позволяет создавать таблицы стилей на языке JavaScript. Эта возможность получила название JavaScript Style Sheets (JSSS). Для примера рассмотрим следующий код.

<HTML>
<HEAD>
<STYLE TYPE="text/JavaScript">
 tags.P.borderWidth = 100;
 tags.P.borderStyle = 'none';
 tags.B.color = "red";
</STYLE>
<LINK REL="stylesheet" TYPE="text/JavaScript" HREF="fancystyle" 
	TITLE="Fancy">
</HEAD>
<BODY>
<P>Spacious paragraph !</P>
<P><B>Angry spacious paragraph!</B></P>
</BODY>
</HTML>

Здесь в элементе

<STYLE>

использованы конструкции JavaScript, с помощью которых изменяются атрибуты оформления обычных тегов

<P> и <B>

Элемент

<LINK>

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

В JSSS имеются специальные функции, делающие механизм работы со стилями очень гибким. Рассмотрим некоторые из них.

<STYLE TYPE="text/JavaScript">
 tags.H1.color = 'pink';
 tags.H1.margins(1,2,3,4);
 tags.H1.rgb(50,50,50);
 contextual(tags.H1, tags.B).fontStyle = "italic";
 function change_it()
 {
   if (color == 'pink') fontStyle = "medium";
 }
 tags.I.apply = change_it();
</STYLE>

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

<B>,

расположенный внутри элемента

<H1>,

отображается курсивом, но при этом конфликта с элементами

<I>,

также оказавшимися внутри

<H1>,

не происходит.

Механизм JSSS, предложенный Netscape, достаточно мощен и изощрен, но при работе со стилями обычно приходится не только создавать таблицы стилей, с чем JSSS справляется прекрасно, но и изменять стили в процессе отображения страницы. А здесь решение Netscape далеко не столь элегантно.

Изменение стилей в DHTML предполагает возможность записи их значений с помощью JavaScript. Чтобы это стало возможным в Netscape Navigator, необходимо создавать объекты Layer. Но изменение стиля с помощью JavaScript вовсе не означает, что элемент автоматически изменит свое изображение. Как документ в целом, так и его слои (layers), уже появившиеся на экране, сформированы в соответствии со значениями стилей, заданными на момент отображения. Новые значения, установленные JavaScript, обычно могут вступить в силу лишь после перегрузки (reload) документа или его части. Исключение из этого правила составляют только цвет фона (background color) и графический элемент (image) — их можно менять в любое время.

Совсем по-другому обстоит дело с управлением стилями в браузерах Microsoft. Начиная с версии 4.0, Internet Explorer предоставляет пользователю так называемые динамические стили, доступные для чтения и записи из любого языка программирования, прежде всего JScript и VBScript.

Другими словами, с управляемыми стилями в Internet Explorer все было хорошо с самого начала. Посмотрим, что же предлагает Microsoft в ответ на инновации со стилями, введенные Netscape.

Механизм behaviors и файлы HTC

В то время как Netscape развивала механизм стилей на базе JSSS, Microsoft создала концепцию behaviors — мощный и эффективный инструмент управления динамическими стилями. DHTML Behaviors — компонент объектной модели, отвечающий за поведение (изменение) стилей в зависимости от событий, которые происходят при отображении Web-страницы в Internet Explorer.

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

Механизм behaviors поддерживается в Internet Explorer начиная с версии 5.0, но считается, что только в версии 5.5 он стал функционировать в полном объеме.

Рассмотрим, как выглядит Web-страница с behaviors на примере кода, "подсвечивающего" строки при наведении на них курсора мыши (mouseover highlights). Без использования behaviors желаемый эффект достигается с помощью следующего кода.

<HEAD>
<STYLE>
  .HILITE { color:red; letter-spacing:2; }
</STYLE>
</HEAD>
<BODY>
<UL>
<LI onmouseover="this.className='HILITE'"
  onmouseout ="this.className=''">Example</LI>
</UL>
</BODY>

Здесь для подсвечивания элемента списка

<LI> 

использован трюк с переопределением имени класса стиля CSS при возникновении событий onmouseover и onmouseout. Очевидно, что вся работа в приведенном примере сосредоточена вокруг таблицы стилей и событий, т. е. именно тех компонентов, ради управления которыми и был задуман механизм behaviors.

Для браузера Internet Explorer 5.0 этот фрагмент можно переписать уже по-другому.

<HEAD>
<STYLE>
  LI {behavior:url(hilite.htc)}
</STYLE>
</HEAD>
<BODY>
<UL>
  <LI>Example</LI>
</UL>
</BODY>

Или даже совсем кратко, поместив элемент behavior внутри атрибута style:

<BODY>
<UL>
  <LI STYLE="behavior:url(hilite.htc)">Example</LI>
</UL>
</BODY>

Если попытаться перевести приведенные коды на обычный язык, то получится следующее. Элементу

<LI> 

ставится в соответствие динамический стиль, поведение которого описано в файле hilite.htc. Таким образом, само по себе ключевое слово behavior никакой особой нагрузки не несет. Это просто атрибут-указатель, один из многих, допустимых в определении стилей CSS. Настоящий же программный компонент behavior скрыт от наблюдателя. Просматривая код страницы в браузере, мы не сможем понять, почему строки, заключенные в тегах

<LI>, 

изменяют свое представление при наведении на них курсора мыши.

Секрет behaviors заключается в том, что поведение стиля описано на языке программирования, не являющегося принадлежностью CSS. Существует несколько способов программирования behaviors. В случае применения JScript для хранения программы используются файлы специального формата с расширением .HTC (HTML Component).

Рассмотрим пример.

<HTML>
<HEAD>
  <STYLE>
    .help { behavior:url(help.htc); }
  </STYLE>
</HEAD>
<BODY>
  Обычный текст
  <DIV CLASS=help DELAY=2000>Щелкни мышью для помощи</DIV>
  Обычный текст
</BODY>
</HTML>

Здесь в заголовке страницы определена таблица стилей с единственным классом (help), имеющим собственную "программу поведения", которая описана в файле help.htc. Этот файл может выглядеть следующим образом:

<script language="JScript">
attachEvent("onclick", event_onclick);

function event_onclick()
{
  alert("Извините, ничем помочь не могу");
}
</script>

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

Для примера с подсвечиванием строк HTC-файл может выглядеть следующим образом (hilite.htc):

<PUBLIC:ATTACH EVENT="onmouseover" ONEVENT="Hilite()" />
<PUBLIC:ATTACH EVENT="onmouseout"  ONEVENT="Restore()" />
<SCRIPT LANGUAGE="JScript">
var normalColor;

function Hilite()
{
  if (event.srcElement == element)
  {
    normalColor = style.color;
    runtimeStyle.color  = "red";
    runtimeStyle.cursor = "hand";
  }
}

function Restore()
{
  if (event.srcElement == element)
  {
     runtimeStyle.color  = normalColor;
     runtimeStyle.cursor = "";
  }
}
</SCRIPT>

Behaviors можно применять не только в случаях интерактивного воздействия пользователя на элементы страницы. В объектной модели документа предусмотрено достаточно событий, способных инициировать функции behavior. Например, для реализации постоянных визуальных эффектов целесообразно использовать событие OnContentReady. В этом случае XML-заголовок в HTC файле будет выглядеть следующим образом.

<PUBLIC:ATTACH EVENT = "oncontentready" ONEVENT ="DoIt()"/>

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

Строго говоря, HTC представляет собой XML-документ, включающий две структуры — блок script и окружающий блок component:

<PUBLIC:COMPONENT>
  <SCRIPT>
  </SCRIPT>
</PUBLIC:COMPONENT>

Наличие блока component позволяет описывать компонент как классический объект, имеющий свойства и методы, к которому можно обратиться из любого места DHTML с помощью программного кода. Свойства и методы HTC задаются соответствующими XML-элементами, например:

<PUBLIC:PROPERTY NAME="hiliteColor" />
<PUBLIC:METHOD NAME="startFlying" />

После того, как свойства и методы объявлены, к ним можно обращаться везде, где это потребуется.

<PUBLIC:METHOD NAME="tick" />
<PUBLIC:METHOD NAME="startFlying" />
:
<SCRIPT LANGUAGE="JScript">
var currCount;
var flyCount;
var flying;
var msecs;
var oTop, oLeft;
msecs = 50;
flyCount = 20;
flying = false;
runtimeStyle.position = "relative";
runtimeStyle.visibility = "hidden";
window.attachEvent("onload", onload);

function onload()
{
  if (delay != "none") {
    window.setTimeout(uniqueID+".tick()", delay);
  }
}

function tick()
{
  if (flying == false) {
    startFlying();
  } else {
    doFly();
  }
}

function startFlying()
{
  if (from=="top") {
    runtimeStyle.posTop = -offsetTop-offsetHeight;
  } else if (from=="bottom") {
    runtimeStyle.posTop = 
      element.document.body.clientHeight;
  } else if (from=="right" ) {
    runtimeStyle.posLeft = 
      element.document.body.clientWidth;
  } else {
    runtimeStyle.posLeft = -offsetLeft-offsetWidth;
  }
  runtimeStyle.visibility = "visible";
  flying = true;
  oTop = runtimeStyle.posTop;
  oLeft = runtimeStyle.posLeft;
  currCount = 0;
  doFly();
}

function doFly()
{
  var dt, dl;
  currCount++;
  dt = oTop / flyCount;
  dl = oLeft / flyCount;
  runtimeStyle.posTop -= dt;
  runtimeStyle.posLeft -= dl;
  if (currCount < flyCount) {
    window.setTimeout(uniqueID+".tick();", msecs);
  } else {
    runtimeStyle.posTop = 0;
    runtimeStyle.posLeft = 0;
    flying = false;
    evObj = createEventObject();
    evObj.setAttribute("resulty", uniqueID);
    finished.fire(evObj);
  }
}
</SCRIPT>

Приведенный код, заимствованный из документации Microsoft SDK, обеспечивает эффект "летающих блоков" текста, например, на страницах презентаций. Примером вызова этого компонента может служить следующая DHTML-страница, содержащая помимо behavior еще и теги, определенные пользователем:

<HTML XMLNS:InetSDK>
<HEAD>
<TITLE>Flying Text Using Custom Tags</TITLE>
<STYLE>
<!--
  P   { font-size: 16pt;
        font-weight:600;
        font-family: Arial,Helvetica;
        color: gold;
        margin-top:.2in; margin-bottom: 0.2in;
        margin-left:0.5in; margin-right:0.5in;
      }

  DIV { font-size: 14pt;
        font-weight:600;
        font-family: Arial,Helvetica;
        color: "white";
        margin-top: 0.1in;
        margin-left: 1.0in; margin-right: 0.5in;
      }

  @media all {
    InetSDK:FLY { behavior: url(fly.htc); }
  }
-->
</STYLE>
</HEAD>

<BODY BGPROPERTIES="FIXED" BGCOLOR="#000000">
<P>
  <InetSDK:FLY delay="1000" from="top">
    Use Dynamic HTML to differentiate your content
    and create compelling Web sites
  </InetSDK:FLY>
</P>
<P>
  <InetSDK:FLY delay="2000" from="right">
    Use the Document Object Model (DOM)
    to create interactive documents
  </InetSDK:FLY>
</P>
<InetSDK:FLY delay="3000" from="bottom">
  <DIV>Interact with the user through
    the event model</DIV>
  <DIV>Interactively change the appearance of 
    the document with Dynamic Styles 
    and Dynamic Content</DIV>
  <DIV>Take precise control over your layout 
    with CSS-positioning</DIV>
</InetSDK:FLY>
</BODY>
</HTML>

HTC-файлы с JScript — это не единственное средство для реализации behaviors. Создавать Web-компоненты можно и с помощью языков программирования Visual C++, Visual Basic и Java. Для Internet Explorer 5+ разработчики наряду с технологией HTC могут использовать технологию Microsoft Windows Script Component (WSC). Кроме того, Microsoft предлагает целый ряд встроенных компонентов. Это так называемые default behaviors — компоненты, реализованные в самом обозревателе Internet Explorer:

<STYLE>
  .saveFavorite { behavior:url(#default#savefavorite) }
</STYLE> 

Здесь для класса saveFavorite задается использование встроенного behavior с именем savefavorite. Принадлежность этого компонента к категории встроенных возможностей видна по специальному префиксу #default#. Приведенный фрагмент можно использовать для добавления ссылки в меню "Избранное".

Несовместимость браузеров

Как правило, Web-программист начинает работу над новым проектом JavaScript с операторов, распознающих тип браузера. Затем практически в каждой функции он вынужден предусматривать альтернативные ветви, реализующие необходимые операции в рамках объектных моделей конкурирующих браузеров.

На первый взгляд, инновации в направлении CSS способны только усложнить положение вещей, тем более что некоторые из них даже вызывают аварийное завершение работы браузеров — "чужаков". С другой стороны, разработчики в качестве одного из самых важных результатов нововведений указывают на возможность полностью отделить представление Web-страниц от контента. Действительно, поскольку программы теперь связаны со стилями, а сами таблицы стилей могут загружаться (внедряться) из внешних файлов, то динамические возможности целого сайта теперь так же легко изменить, как цвет или шрифт, — для этого достаточно просто модифицировать внешнюю таблицу CSS или связанные с ней HTC-файлы.

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

<script language="JavaScript1.2" type="text/javascript">
<!--
var bV = parseInt(navigator.appVersion);
NS4 = (document.layers) ? true : false;
IE4 = ((document.all)&&(bV >= 4)) ? true : false;
DOM = (!document.layers && !document.all && bV >= 4)
       ? true : false;
capable = (NS4 || IE4 || DOM) ? true : false;
with (document) {
  if (DOM) {
    var lstyle = "<style type='text/css'>";
    lstyle += . . .; // стили для других обозревателей DOM
    lstyle += "</style>";
    write(lstyle);
  } else {
    write("<style type='text/css'>");
    if (NS4) {
      write(". . ."); // стили для Netscape Navigator
    } else if(IE4) {
      write(". . ."); // стили для Internet Explorer
  }
    write("</style>");
  }
}
//-->
</script>

Разместив подобные строки JavaScript в разделе

<HEAD> 

Web-страницы, можно без ограничений использовать как JSSS компании Netscape, так и Web-компоненты Microsoft.

Web-компоненты — профессиональный пример

В качестве заключительного примера рассмотрим, как с помощью технологии Web-компонентов создать весьма эффектную Web-страницу типа Digital Dashboard (DDB).

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

Для решения проблемы можно использовать Web Parts — Web-компоненты, отображаемые в отдельных окнах на странице DDB. Окна для Web Parts — это не то же самое, что привычные окна Windows, и не порождают новых экземпляров приложения. Эти окна можно свертывать, раскрывать и перемещать в пределах страницы, но нельзя накладывать друг на друга. Последнее обстоятельство исключает вероятность случайного перекрытия важной информации элементами переднего плана.

Управление Web Parts на странице DDB напоминает верстку журнальной или газетной страницы, где прямоугольные заметки размещаются в одной плоскости, чтобы эффективно занять всю площадь. Пользователь, работающий с DDB, собирает в своем поле зрения все компоненты, которые в текущий момент наиболее значимы для принятия решения. Другие компоненты, менее актуальные или второстепенные, временно закрываются или перемещаются в дальнюю часть страницы.

Еще одно важное требование, предъявляемое к DDB, — запоминание и восстановление текущего расположения Web Parts.

Из приведенного описания следует, что уже сама по себе DDB — это достаточно сложная DHTML-структура, не говоря уже о формировании или извлечении содержимого для ее компонентов. Рассмотрим код Web Part.

<!--BEGIN WEB PART ITEM-->
<table border="0" class="clsPart" id="{ID}">
<tbody>
  <tr>
    <td class="clsPartHead">
      <img class="clsPartHead" src="gripBlue.gif">
    </td>
    <td class="clsPartHead">
      <b class="clsPartHead">{CAPTION}</b>
    </td>
    <td class="clsPartRight">
      <img class="clsMinimize" src="downlevel.gif">
    </td>
  </tr>
  <tr>
    <td colSpan="3">
      <table border="0" width="100%">
<tbody>
        <tr>
          <td>
<!-- WEB PART BODY HERE -->
          </td>
        </tr>
</tbody>
      </table>
    </td>
  </tr>
</tbody>
</table>
<!--END WEB PART ITEM-->

Как видно из приведенного фрагмента, контейнер для Web Part состоит из двух частей — заголовка и тела. В теле Web Part может размещаться произвольная информация: скажем, текст, таблицы, графические изображения, inline-фреймы или программные объекты. Но для решения нашей задачи это роли не играет. А вот вторая часть Web Part, заголовок, напротив, очень важен.

Заголовок представляет собой таблицу, состоящую из одной строки и трех ячеек. В средней ячейке располагается название компонента — {CAPTION}. Справа и слева от названия помещаются два графических элемента, показывающих, какие операции с компонентом предусмотрены. Элемент справа — кнопка "minimize / maximize", позволяющая свертывать и раскрывать окно. Элемент слева — ручка (grip), показывающая возможность перетаскивания компонента.

Обе части Web Part находятся внутри таблицы, выполняющей функцию контейнера. Каждый такой контейнер должен иметь уникальный идентификатор {ID}, чтобы обеспечить возможность запоминания и восстановления местоположения компонентов.

Чтобы оптимально использовать всю площадь страницы, Web Parts делают одинаковой ширины в пределах своей колонки или "полосы". Количество полос на странице произвольное (обычно две или три). Каждая полоса может иметь свою ширину и отличный от других полос стиль оформления (цветовое решение). При таком подходе страница DDB в исходном состоянии напоминает журнальную.

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

<table>
  <tr>
<!--BEGIN CONTAINER-->
    <td vAlign="top">
      <table border="0" class="clsPartContainer">
<tbody>
        <tr>
          <td vAlign="top">
<!--WEB PART ITEM 1-->
<!--WEB PART ITEM 2-->
. . .
<!--WEB PART ITEM N-->
          </td>
        </tr>
</tbody>
      </table>
    </td>
<!--END CONTAINER-->
  </tr>
</table>

Если на странице DDB предусматривается несколько полос-контейнеров, то приведенная конструкция повторяется в части между комментариями

<!-BEGIN CONTAINER-> и <!-END CONTAINER->.
<table>
  <tr>
<!--CONTAINER 1-->
<!--CONTAINER 2-->
<!--CONTAINER 3-->
  </tr>
</table>

Поскольку современные технологии не предусматривают на Web-странице никаких кодов JavaScript, то единственное, что осталось сделать для завершения DDB, — это внедрить таблицу стилей CSS, включающую все классы, упомянутые в приведенных выше фрагментах.

<html>
<head>
<title>DDB Page</title>
<link rel="stylesheet" href="ddb.css" type="text/css">
</head>
<body>
  <table>
    <tr>
<!--CONTAINER 1-->
<!--CONTAINER 2-->
<!--CONTAINER 3-->
    </tr>
  </table>
</BODY>
</HTML>

Вот как может выглядеть подобная таблица.

.clsPartContainer {
  BEHAVIOR: url(webparts.htc);
  POSITION: relative;
}
.clsPart { }
.clsPartConent { }
.clsPartBottom { }
.clsPartHead { CURSOR: move; }
.clsDragWindow {
  BACKGROUND-COLOR: #666666;
  BORDER-BOTTOM: #000000 2px solid;
  BORDER-LEFT: #000000 2px solid;
  BORDER-RIGHT: #000000 2px solid;
  BORDER-TOP: #000000 2px solid;
  DISPLAY: none; 
  FILTER: alpha( opacity=20 );
  POSITION: absolute;
}
.clsPartRight {
  BACKGROUND-IMAGE: url(chevronUp.gif);
  CURSOR: hand;
}
.clsPartRightHidden {
  BACKGROUND-COLOR: #cccccc;
  BACKGROUND-IMAGE: url(chevronDown.gif);
  CURSOR: hand;
}
.clsPartLeft { }
IMG.clsMinimize { VISIBILITY: hidden; }

Именно здесь, в таблице стилей, появляется ссылка на программный код, с помощью которого будет происходить очень непростая работа по управлению динамическими компонентами. Как и следовало ожидать, она выполняется с помощью компонента behavior — он расположен в файле webparts.htc.

Можно заметить также, что в таблице стилей появились новые классы, на которые не было ссылок в основном коде страницы DDB. Это прежде всего класс clsDragWindow, описывающий прямоугольник с полупрозрачным фоном. Такой элемент будет использоваться для имитации перемещения Wеb Parts, пока пользователь сохраняет в нажатом состоянии левую клавишу мыши. Второй класс, clsPartRightHidden, описывает кнопку maximize для ситуации, при которой тело Web Part скрыто.

Наиболее тонкий момент — запоминание информации о взаимном расположении компонентов. Здесь используется встроенный компонент behavior #default#userData. В тело контейнера для Web Parts следует поместить именованный пустой элемент

с атрибутом стиля BEHAVIOR: url(#default#userData):

<DIV class=storeUserData id=oLayout></DIV>

А приведенная таблица стилей CSS пополнится еще одним классом:

.storeUserData { behavior: url(#default#userData); }

Важно отметить, что userData behavior для запоминания данных между сеансами использует накопитель UserData store, память которому выделяется в рабочем пространстве Internet Explorer. Это обычный XML-файл, записываемый в каталог /Application Data/Microsoft/Internet Explorer/UserData/… У этого компонента куда больше возможностей и выше быстродействие, чем у традиционных cookies. Компонент UserData behavior доступен в Internet Explorer начиная с версии 5.0 — на платформе как Microsoft Win32, так и UNIX. Формат XML позволяет запоминать данные в структурированном виде, т. е. сохранять и восстанавливать настоящие программные объекты. Это новшество быстро оценили разработчики. Так, например, крупнейшая поисковая система Google использует userData behavior для хранения всех пользовательских настроек.

Рамки статьи не позволяют привести программный код компонента Webparts.htc, да в этом и нет необходимости. На то он и Web-компонент, чтобы существовать автономно и использоваться там, где в этом появится потребность.

Вам также могут понравиться