Виталий Сизов
программист, специалист по разработке на платформе Microsoft Office для Web (Visual Basic for Applications, JavaScript),
sva@hot.ee

Постановка задачи

Развитие Интернета все больше привлекает внимание коммерческих организаций, желающих представить миру собственные коллекции товаров и услуг в виде электронных магазинов, прейскурантов, спецификаций. В большинстве случаев начинающие электронную коммерцию предприятия не имеют корпоративных Web-серверов, и у них нет специалистов, способных реализовать базы данных SQL и технологии клиент-сервер. Все, чем располагает "обычная" компания, - это документы Microsoft Office (таблицы Excel, базы данных Access и тексты Word). Домашние страницы таких компаний размещаются чаще всего на бесплатных серверах, с ограниченным набором возможностей.

Но оказывается, всего этого вполне достаточно, чтобы реализовать если уж не магазин, то вполне приличный электронный ларек. Не потребуется даже мощный редактор HTML, от которого просто не будет проку, поскольку страницы со списком товаров должны формироваться динамически. Все, что действительно необходимо, - это исходные данные в виде списка, Visual Basic for Applications, JavaScript, CSS и текстовый редактор Notepad.

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

Идентификатор

Назначение Комментарий

code

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

name

Наименование товара Cтрока

home

Домашняя страница товара Ссылка на страницу с подробным описанием в виде полного или неполного URL

unit

Единица измерения Строка (для штучных товаров обычно не заполняется)

price

Цена единицы Число
comment Краткое описание Строка, возможно, содержащая HTML-тэги

Создав структуру базы данных, следует ее реализовать и заполнить теми данными, которые содержатся в имеющихся документах Microsoft Office. Лучше всего для этой цели подходит рабочая таблица Excel.

Создание коллекции объектов

После того, как база данных некоторым образом подготовлена, можно приступить к созданию HTML-кода страницы. Используем простую заготовку.
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=
windows-1251">
<title>Электронный магазин</title>
<style TYPE="text/css">
<!--
//-- Здесь будет таблица стилей
-->
</style>
<script>
<!-- hide this script from non-JavaScript browsers

n = (document.layers) ? 1:0
ie = (document.all) ? 1:0

//-- Здесь будет Script

// done hiding from old browsers -->
</script>
</head>

<body>

//-- Здесь будет тело документа

</body>
</html>

В разделе HEAD приступим к составлению скрипта (см. место Script в коде-заготовке). Для начала позаботимся о разумном представлении базы данных. Создадим полноценную коллекцию объектов goods (товары). Каждый экземпляр из этой коллекции будет иметь свойства, одноименные определенным выше реквизитам. Дополнительно нам пригодятся еще два свойства: len (счетчик экземпляров) и maxlen (максимально допустимое количество экземпляров в коллекции). Свойство maxlen не обязательно, но если нужно ограничить размер загружаемой базы данных, его можно использовать в методе Add. Создание коллекции неограниченного размера выполняется следующим образом:

var MAX_ITEM = 1

function MakeArray(n) {
  for (var i = 1; i <= n; i++) {
    this[i] = 0
  }
  this.maxlen = n
  this.len = 0
  return this
}

var goods = MakeArray(MAX_ITEM)

function Item(code, name, home, unit, price, comment) {
  this.code = code
  this.name = name
  this.home = home
  this.unit = unit
  this.price = price
  this.comment = comment
}

Для добавления экземпляров в коллекцию goods понадобится единственный метод (функция) Add:

function Add(code, name, home, unit, price, comment) {
  goods.len++
  goods[goods.len] = new Item(code, name, home, unit, price, 
comment)
}

Использование метода Add

Поскольку коллекция объектов создана, наша страница готова к приему информации. Для каждой записи исходной базы данных необходимо выполнить метод Add. На языке JavaScript это должно выглядеть следующим образом:

Add(code1, "name1", "home1", "unit1", price1, "comment1") 
Add(code2, "name2", "home2", "unit2", price2, "comment2") 
Add(code3, "name3", "home3", "unit3", price3, "comment3") 
. . . 
. . . 
. . . 
Add(codeI, "nameI", "homeI", "unitI", priceI, "commentI") 
. . . 
. . . 
. . . 
Add(codeN, "nameN", "homeN", "unitN", priceN, "commentN") 

В скобках здесь стоят конкретные значения, извлекаемые из исходной таблицы Excel, содержащей N записей. Следует обратить внимание, что строковые данные заключаются в кавычки, а для числовых кавычки не обязательны. Другими словами, вся премудрость загрузки базы данных заключается в преобразовании исходной таблицы в текстовый файл, строки которого начинаются со слова Add, далее в круглых скобках следуют реквизиты, разделенные запятыми. Отсутствующий реквизит нужно представить в виде (""). Избыточность в данном случае очень незначительна. Текстовый файл приемлемой для Web длины (200-300 Кбайт) позволяет записать несколько тысяч наименований.

Конечно, файл, представляющий базу данных в такой экзотической форме, должен существовать самостоятельно. Для его подключения к HTML-странице потребуется описание (подразумевается, что имя файла базы данных - list.js):

Эти строки следует поместить также в разделе HEAD, желательно ниже основного скрипта, помещенного на место Script, чтобы многократные вызовы метода Add следовали после описания самой функции. Теперь любой браузер, открыв страницу, загрузит и объемную базу данных.

Группировка записей

При постановке задачи мы уже отмечали, что данные необходимо группировать. Самая современная и эффектная форма группирования информации на HTML-странице - это nuggets (самородки), или, в современной терминологии, - Web Parts. Nugget - это внедренное в страницу окно, которое может открываться и сворачиваться. Заголовок nugget - это часто гиперссылка на домашнюю страницу группы. Технически реализовать nugget несложно; эта конструкция состоит из двух частей - заголовка и тела. Заголовок может быть представлен единственной строкой таблицы с двумя ячейками. В первой ячейке расположено наименование окна, а во второй - кнопка, вызывающая развертывание и свертывание тела.

Тело nugget - это именованный контейнер

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

<table cellpadding="0" border="0" cellspacing="0" 
class="nuggetHeader">
  <tr>
    <td nowrap width="100%" class="nuggetTitle" valign="center">
    <a href="nugget.htm" class="nuggetTitleText">Nugget 
Title</a></td>
    <td nowrap class="nuggetButtonWrapper">
    <img id="nToggle" src="images/blank.gif" title="Click to 
maximize / minimize"
    onClick="document.all.nContent.style.display= _
    document.all.nContent.style.display=='none'?'':'none'; _
    if(document.images)this.src= _
    document.all.nContent.style.display=='none'?imgMax.src:
imgMin.src;"
    WIDTH="17" HEIGHT="17"></td>
  </tr>
</table>
<div valign="top" id="nContent">
  <table width="100%" border="0" cellpadding="0" cellspacing="0" 
class="nuggetBody">
    <tr>
      <td>Nugget Body</td>
    </tr>
  </table>
</div>

Представленный выше фрагмент - лишь пример оформления nuggets; копировать и вставлять его в текст создаваемого DHTML-документа не потребуется.

Для формирования nugget достаточно задать имя и гиперссылку на домашнюю страницу группы. Для этой цели вполне подходят имеющиеся реквизиты name и home. Чтобы распознать во входном потоке заголовки разделов, достаточно зарезервировать специальные значения поля code. Например, если в поле code текущей записи стоит значение "пусто", то эту запись следует рассматривать в качестве заголовка нового раздела. Естественно, поля unit, price и comment придется в этом случае проигнорировать.

Таким образом, алгоритм обработки загруженной коллекции goods сводится к следующему. Для каждого экземпляра goods анализируется свойство code. Если code имеет значение "пусто", то нужно сформировать заголовок nugget с именем name и гиперссылкой home и начать новую таблицу. В противном случае - сформировать обычную строку текущей таблицы.

К сожалению, элегантная конструкция раскрывающегося окна будет работать только в Internet Explorer. Netscape Communicator не имеет объекта style и свойства display, и в нем описанная выше конструкция будет выглядеть статичной. Окно nugget будет постоянно раскрытым, а кнопка Maximize / Minimize окажется бесполезной. Чтобы не вводить в заблуждение пользователя, эта кнопка инициализируется "пустым" значением (src = "images/blank.gif").

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

Функция DisplayList

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

var n_count = 0

function DisplayList() {
  for (i = 1; i <= goods.len; i++) {
    if ((goods[i].code == "") || (i == 1)) {
      if (n_count > 0) {
        document.writeln("</table>")
        document.writeln("</td>")
        document.writeln("</tr>")
        document.writeln("</table>")
        document.writeln("</div>")
      }
      n_count++
      document.writeln("<table cellpadding=\"0\" 
border=\"0\" cellspacing=\"0\" class=\"nuggetHeader\">")
      document.writeln("<tr>")
      document.writeln("<td nowrap width=\"100%\" 
class=\"nuggetTitle\" valign=\"center\">")
      document.writeln(" ")
      if (goods[i].home != "") {
        document.writeln("<a target=\"_blank\" href=\"" + 
goods[i].home + "\" class=\"nuggetTitleText\">" + 
goods[i].name + "</a>")
      } else {
        document.writeln("<span class=\"nuggetTitleText\">" 
+ goods[i].name + "</span>")
      }
      document.writeln(" ")
      document.writeln("</td>")
      document.writeln("<td nowrap 
class=\"nuggetButtonWrapper\">")
      document.writeln("<img id=\"nToggle" + n_count + "\" 
src=\"images/blank.gif\" title=\"Click to maximize / 
minimize\" onClick=<BR>\"document.all.nContent" + n_count + 
".style.display=document.all.nContent" + n_count + 
".style.display=='none'?'':'none';if(document.images)
this.src=document.all.nContent" + n_count + 
".style.display=='none'?imgMax.src:imgMin.src;\" WIDTH=\"17\" 
HEIGHT=\"17\">")
      document.writeln("</td>")
      document.writeln("</tr>")
      document.writeln("</table>")
      document.writeln("<div valign=\"top\" id=\"nContent" 
+ n_count + "\">")
      if (n_count%2 == 0) {
        document.writeln("<table width=\"100%\" border=\"0\" 
cellpadding=\"5\" cellspacing=\"0\" class=\"nuggetBodyEven\">")
      } else {
        document.writeln("<table width=\"100%\" border=\"0\" 
cellpadding=\"5\" cellspacing=\"0\" class=\"nuggetBodyOdd\">")
      }
      document.writeln("<tr>")
      document.writeln("<td>")
      document.writeln("<table>")
    } else {
      if (i%2 == 0) {
        document.writeln("<tr class=\"lineEven\">")
      } else {
        document.writeln("<tr class=\"lineOdd\">")
      }
      if (goods[i].home != "") {
        document.writeln("<td><a target=\"_blank\" href=\"" + 
goods[i].home + "\">" + goods[i].name + "</a></td>")
      } else {
        document.writeln("<td>" + goods[i].name + "</td>")
      }
      document.writeln("<td>" + goods[i].unit + "</td>")
      document.writeln("<td>" + goods[i].price + "</td>")
      document.writeln("<td><a href=\"javascript:void(0)\" 
onClick=\"BasketInsert(" + i +")\">Add</a></td>")
      document.writeln("</tr>")
      if (goods[i].comment != "") {
        document.writeln("<tr>")
        document.writeln("<td colspan=\"4\">" + 
goods[i].comment + "</td>")
        document.writeln("</tr>")
      }
    }
  }
  document.writeln("</table>")
  document.writeln("</td>")
  document.writeln("</tr>")
  document.writeln("</table>")
  document.writeln("</div>")
}

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

Большинство стилей, которые могут потребоваться, приведены ниже:

<style TYPE="text/css">
<!--

a { font-family: Tahoma; color: #11057E; text-decoration: 
none; font-weight: bold; }
a:link { font-family: Tahoma; color: #11057E; text-decoration: 
none; font-weight: bold; }
a:hover { color: #ff0000; }
p { font-family: Tahoma; font-size: 8pt; }
td { font-family: Tahoma; font-size: 8pt; }

.nuggetHeader { background-color: steelblue; }
.nuggetTitle { cursor: default; border: 2px groove 
lightsteelblue; background-color: steelblue; width: 100%; }
.nuggetTitleText { color: white; font-size: 10pt; 
font-weight: bold; font-family: Arial; }
.nuggetButtonWrapper { cursor: hand; border: 2px groove 
lightsteelblue; background-color: steelblue; vertical-align: 
middle; }
.nuggetBody { background-color: white; }
.nuggetBodyOdd { background-color: white; }
.nuggetBodyEven { background-color: ghostwhite; }
a.nuggetTitleText { color: white; font-size: 10pt; 
font-weight: bold; font-family: Arial; }
a.nuggetTitleText:active { color: yellow; font-size: 10pt; }

.lineEven { background-color: pink; }
.lineOdd { background-color: gold; }

-->
</style>

Заключительные конструкции

Все, что осталось сделать, - это разместить вызов функции DisplayList и решить вопрос о первоначальном состоянии nuggets - оставить их развернутыми или свернуть. Обычно nuggets сворачивают, чтобы пользователь мог охватить взглядом весь список товарных групп. Но можно один из nuggets оставить открытым, чтобы обратить особое внимание, например, на новинки коллекции. Поскольку все nuggets пронумерованы, сделать это не составляет труда. Не стоит забывать и о том, что в заголовках nuggets используются графические элементы. Для них надо предусмотреть операторы предварительной загрузки:

if(document.images){
  imgMax = new Image(17,17)
  imgMax.src = "images/max.gif"
  imgMin = new Image(17,17)
  imgMin.src = "images/min.gif"
  imgBlank = new Image(17,17)
  imgBlank.src = "images/blank.gif"
}

Операторы для начального свертывания nuggets помещают в функцию init(), которая завершает Script в заголовке страницы. Как уже говорилось выше, nuggets не предназначены для Netscape Communicator, поэтому функция init() выглядит следующим образом:

function init() {
  if (ie) {
    for (i = 1; i <= n_count; i++) {
      document.all["nContent" + i].style.display = "none"
      if (document.images) {
        document.images["nToggle" + i].src=imgMax.src
      }
    }
  }
}

Вызов функции init выполняется в тэге :

Ну вот, наконец настало время для Web-дизайна. Только теперь мы добрались до тела страницы и можем продемонстрировать утонченный вкус. К сожалению, все, что нам осталось сделать в разделе BODY, - это поместить единственный оператор:

<script>
<!--
DisplayList()
//-->
</script>

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

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