Вариации на тему электронного магазина. Часть 2
Передача данных между страницами
Использование почтовой службы клиента
Покупательская корзина
Саму по себе покупательскую корзину реализовать несложно. Это такая же коллекция объектов, как и goods, с двумя незначительными отличиями. У объекта коллекции отсутствует свойство .comment — для выбранного товара примечания не требуются. Зато появляется новое свойство .quantity — количество экземпляров товара.
function BasketItem(code, name, home, unit, price, quantity) { this.code = code this.name = name this.home = home this.unit = unit this.price = eval(price) this.quantity = eval(quantity) }
Но вот методы, необходимые для такой коллекции, в большой мере зависят от общей организации Web-страниц электронного магазина. Существует два принципиально различных варианта такой организации: с фреймами и без фреймов. Ниже рассматриваются особенности каждого из этих вариантов.
Страницы без фреймов
С точки зрения современных тенденций Web-дизайна страницы без фреймов являются более предпочтительными. Не в последнюю очередь это вызвано тем, что все больше страниц сегодня генерируется на сервере, а писать Perl-программы и ASP для коротких страницгораздо проще. В нашем случае все обстоит совершенно по-другому. Стремление выставить на обозрение покупателю большое количество товаров соответственно влечет за собой и увеличение размеров страницы. В случае же страниц без фреймов для отображения состояния покупательской корзины подходящего места не найти. В самом деле — не заставлять же пользователя постоянно «прыгать» в начало или конец страницы, чтобы посмотреть, сколько товаров он отложил.
Оптимальное решение для длинных страниц без фреймов — заставить корзину следовать за покупателем. Реализация этого — задача непростая. Зато освоение механизма плавающих объектов может стать источником очень эффектных художественных приемов. Покупательскую корзину можно изобразить самыми разными способами — от простого прямоугольного окна до реалистичных рисунков с изощренными элементами управления.
Плавающие объекты
Подобно Pop-up-форме для ввода количества товара любой плавающий объект является слоем — layer, и задается контейнером <DIV></DIV> и соответствующей таблицей стилей. Но прежде чем рисовать корзину и заставлять ее двигаться, следует проанализировать, что должна представлять собой такая корзина.
Прежде всего покупателю важно знать, сколько и на какую сумму он набрал товаров. Это два поля, предназначенные для отображения числовых значений. Очевидно, что рядом должны находиться кнопки для выполнения основных действий — выписки счета и очистки (удаления) корзины. Это терминальные операции, применяемые к корзине в целом. Но кроме глобальных действий необходимо обеспечить покупателю возможность воздействовать и на отдельные объекты в самой корзине. А это потребует гораздо больше пространства на экране, чем нужно для пары итогов. В результате может оказаться, что плавающий образ корзины займет пол-экрана, закрыв значительную часть списка товаров. Чтобы этого не случилось, корзину должны представлять несколько плавающих окон. Одно из них, минимальных размеров с общим итогом, должно присутствовать на экране все время, если корзина не пустая. Другие окна, предназначенные для отображения отдельных покупок, следует показывать только при необходимости отредактировать содержимое корзины.
Таким образом, придется разрабатывать функцию, перемещающую не один, а множество плавающих объектов — окон. Подобная функция выглядит следующим образом:
function FlyAll() { bStopFly = true if (ie) { iDiffY = document.body.scrollTop iDiffX = document.body.scrollLeft } if (n) { iDiffY = self.pageYOffset iDiffX = self.pageXOffset } if (iDiffY != iLastScrollY) { percent = .1 * (iDiffY - iLastScrollY) if (percent > 0) percent = Math.ceil(percent) else percent = Math.floor(percent) for (i = 1; i <= flyBox.len; i++) { if (flyBox[i].follow) { oSlide = flyBox[i].o if (ie) oSlide.style.pixelTop += percent if (n) oSlide.top += percent bStopFly = false } } iLastScrollY = iLastScrollY + percent if (bStopFly) StopFly() } if (iDiffX != iLastScrollX) { percent = .1 * (iDiffX - iLastScrollX) if (percent > 0) percent = Math.ceil(percent) else percent = Math.floor(percent) for (i = 1; i <= flyBox.len; i++) { if (flyBox[i].follow) { oSlide = flyBox[i].o if (ie) oSlide.style.pixelLeft += percent if (n) oSlide.left += percent bStopFly = false } } iLastScrollX = iLastScrollX + percent if (bStopFly) StopFly() } }
Перед использованием этой функции требуется создать коллекцию «плавающих» объектов flyBox. Производится это точно так же, как и коллекция товаров, только объекты создаются с другими свойствами.
var flyBox = MakeArray(MAX_ITEM) function ItemFly(o, bFollow) { this.o = o this.follow = bFollow } function AddFly(o, bFollow) { flyBox.len++ flyBox[flyBox.len] = new ItemFly(o, bFollow) }
Наличие свойства .follow позволяет временно отключать режим «следования» отдельного объекта, что приводит к перераспределению объектов на экране. Булева переменная bStopFly в функции FlyAll() вообще отключает таймер, если не существует ни одного активного объекта, которые требуется двигать.
Для запуска и остановки процесса используется следующая пара функций:
function InitFly() { oSlide = null if (ie) { iLastScrollY = document.body.scrollTop iLastScrollX = document.body.scrollLeft } if (n) { iLastScrollY = self.pageYOffset iLastScrollX = self.pageXOffset } if (n || ie) action = window.setInterval("FlyAll()",1) bFlyActive = true } function StopFly() { oSlide = null if (n || ie) action = window.clearInterval(action) bFlyActive = false }
Для проверки и изменения свойства .follow отдельного объекта используются следующие функции:
function ToggleFly(o) { iFlyCount = 0 for (i = 1; i <= flyBox.len; i++) { if (flyBox[i].o == o) { flyBox[i].follow = !flyBox[i].follow } if (flyBox[i].follow) iFlyCount++ } if (iFlyCount > 0) { if (!bFlyActive) InitFly() } else { StopFly() } } function IsFly(o) { for (i = 1; i <= flyBox.len; i++) { if (flyBox[i].o == o) { return flyBox[i].follow } } return false }
Функция ToggleFly построена таким образом, что включение свойства .follow отдельного объекта в ситуации, когда отсутствуют другие активные объекты, приводит к запуску общего процесса. И наоборот — когда сбрасывается .follow у последнего из активных объектов, останавливается и весь процесс. Подобное решение исключает неоправданное использование ресурсов браузера на обработку прерываний таймера в случае бездействующего процесса.
Относительное положение плавающих окон на экране задается с помощью стилей. Следует подобрать расположение объектов таким образом, чтобы в процессе перемещения они перекрывали минимум полезной информации.
Однако это не всегда удается, особенно при использовании варианта с произвольным расположением товаров на странице, продиктованным фантазией дизайнера.
В этом случае нужно предусмотреть возможность «перетаскивания» объектов на новые позиции при нажатой клавише мыши. Непосредственное перетаскивание объекта выполняется с помощью такой функции:
function MoveIt(e) { if (n) { giMousePosX = e.pageX giMousePosY = e.pageY } if (oWhichIt == null) { return true } bBorder = false if (ie) { if (event.clientX <= 0 || event.clientX > document.body.offsetWidth - 22 || event.clientY <= 0 || event.clientY > document.body.offsetHeight - 22) { bBorder = true } else { iNewX = (event.clientX + document.body.scrollLeft) iNewY = (event.clientY + document.body.scrollTop) iDistanceX = (iNewX - iCurrentX) iDistanceY = (iNewY - iCurrentY) iCurrentX = iNewX iCurrentY = iNewY oWhichIt.style.pixelLeft += iDistanceX oWhichIt.style.pixelTop += iDistanceY } event.returnValue = false } else { oWhichIt.moveTo(e.pageX - iFloatTouchedX, e.pageY - iFloatTouchedY) if (oWhichIt.left < 0 + self.pageXOffset) { oWhichIt.left = 0 + self.pageXOffset bBorder = true } if (oWhichIt.top < 0 + self.pageYOffset) { oWhichIt.top = 0 + self.pageYOffset bBorder = true } if ((oWhichIt.left + oWhichIt.clip.width) >= (window.innerWidth + self.pageXOffset) - 20) { oWhichIt.left = ((window.innerWidth + self.pageXOffset) - oWhichIt.clip.width) - 20 bBorder = true } if ((oWhichIt.top + oWhichIt.clip.height) >= (window.innerHeight + self.pageYOffset)) { oWhichIt.top = ((window.innerHeight + self.pageYOffset) - oWhichIt.clip.height) bBorder = true } } if (bBorder) DropIt() return false }
Чтобы пользователь не увлекся и не перетащил объект за границы экрана, осуществляются проверки для всех четырех сторон. Числовые константы 20 и 22 — не что иное, как ширина в пикселах полосы прокрутки в окне браузеров.
Объект для перетаскивания задается с помощью следующей функции:
function GrabIt(o) { oWhichIt = o if (n) { iFloatTouchedX = giMousePosX - o.pageX iFloatTouchedY = giMousePosY - o.pageY window.onmousemove = MoveIt } if (ie) { if (event != null) { iCurrentX = (event.clientX + document.body.scrollLeft) iCurrentY = (event.clientY + document.body.scrollTop) } else { iCurrentX = 0 iCurrentY = 0 } document.onmousemove = MoveIt } }
Чтобы «отцепить» объект от мыши и в случае, когда достигнута граница экрана, вызывается функция:
function DropIt() { oWhichIt = null if (n) { window.onmousemove = MouseHandler } if (ie) { document.onmousemove = null } }
Таким образом, для плавающих объектов, представляющих покупательскую корзину, необходимо предусмотреть вспомогательные управляющие элементы. Для переключения режима следования это может быть кнопка «Follow Me!». Для перетаскивания объекта — некоторая часть картинки, например ручка «корзины на колесиках».
Отображение и редактирование
Основное окно покупательской корзины должно появляться в момент выбора первой покупки. Поэтому вызов функции ShowBasket следует поместить в метод BasketAdd для варианта без фреймов.
function BasketAdd(code, name, home, unit, price, quantity) { basket.len++ basket[basket.len] = new BasketItem(code, name, home, unit, price, quantity) b_count++ b_summa += Math.round(eval(price) * eval(quantity)) ShowBasket(basket.len) }
Здесь используются глобальные переменные b_count и b_summa, в которых хранятся количество покупок и соответственно общая сумма. Метод ShowBasket зависит от реализации контейнера, представляющего окно корзины. При его разработке следует избегать использования в плавающем объекте элементов форм: полей ввода, стандартных кнопок и т.д. Это вызвано тем, что при «плавании» в Netscape Navigator такие элементы будут отставать от общего фона, что отрицательно скажется на всей затее.
Не следует также забывать, что в основном окне корзины требуется разместить элементы управления, открывающие дополнительные окна для редактирования или просмотра отдельных покупок.
Вид дополнительных окон для отображения элементов зависит от фантазии дизайнера. Однако следует учитывать тот факт, что эти окна не настоящие и размеры их на экране должны ограничиваться разумными рамками. Но теоретически количество покупок в корзине может быть большим. Поэтому самое простое — это предусмотреть возможность отображения только одной записи, доступной для редактирования. Как следствие, появляется необходимость разработки методов навигации по коллекции товаров в корзине. Это могут быть методы GoTop, GoBottom, GoNext и GoPrevious. Что касается непосредственно редактирования, то оно заключается лишь в изменении количества товара. Для этого достаточно изменить свойства .quantity тем же способом, как это делалось при выборе товара из «прайс-листа», — с помощью существующей pop-up-формы oFormQ. Можно ограничиться и более простым вариантом, создав метод DeleteItem, присваивающий свойству .quantity нулевое значение. Ведь если покупатель захочет не удалить, а изменить количество экземпляров товара, он сможет вернуться к «прайс-листу» и выбрать тот же товар в другом количестве. Не стоит заботиться и о реорганизации коллекции после обнуления отдельного элемента. Записи с нулевым количеством можно пропустить и позже, при формировании итогового счета.
На рис. 1 показан вариант страницы без фреймов. На заднем плане можно увидеть nuggets со списком товаров, разбитым на группы.
На переднем плане показаны все элементы, описанные выше. Это и pop-up-форма для ввода количества товара, всплывающая в нужной позиции, и два плавающих окна, представляющих покупательскую корзину. Основное окно с итогами содержит кнопки навигации и удаления текущего элемента. В правом верхнем углу основного окна расположена кнопка, открывающая и скрывающая дополнительное плавающее окно, в котором отображается текущая запись.
Страницы с фреймами
Вариант с фреймами намного проще. Здесь нет никаких «плавающих» объектов, не требуются методы навигации по покупательской корзине и, что самое главное, появляется возможность представления множества доступных товаров на произвольном количестве страниц. Ведь коллекция выбранных товаров хранится на отдельной странице. Ее «время жизни» не зависит от навигации в пространстве множества «прайс-листов».
Сама покупательская корзина в этом случае изображается в виде двух числовых полей, содержащих количество товаров и общую сумму покупки. Эти элементы статические и присутствуют на экране постоянно. Просто в начальный момент оба поля нулевые, что ничуть не смущает пользователя.
К сожалению, представление итоговых сумм удобными элементами <INPUT TYPE=text …> не рекомендуется. Дело в том, что Netscape Navigator не поддерживает атрибут READONLY и любопытный пользователь может захотеть «подправить» итоговую сумму. Поэтому следует попытаться отобразить нужные данные обычным текстом. Дело это далеко не простое (из-за различия объектных моделей браузеров) и требует создания layers.
Однако принципиальной особенностью варианта электронного магазина с фреймами является необходимость передачи данных между различными страницами.
Передача данных между страницами
Из соображений безопасности прямое обращение между различными Web-страницами запрещено. Но только не в случае, если эти страницы одновременно присутствуют в виде фреймов в одном окне браузера.
Любые переменные, описанные на страницах фреймов, автоматически становятся свойствами объекта document, и к ним можно свободно обращаться извне. Предположим, что страница с корзиной находится во фрейме с именем contents и в ней описаны переменные:
var glob_code = 0 var glob_name = "" var glob_home = "" var glob_unit = "" var glob_price = 0 var glob_quantity = 0
Тогда функция QuantityOK, обрабатывающая событие на другой странице, может выглядеть следующим образом:
function QuantityOK() { HideIt(oFormQ) str = oFormQ.document.FormQ.Q.value if (checkNum(str)) { q = eval(str) i = select_item if (Math.round(q) > 0) { parent.contents.glob_code = goods[i].code parent.contents.glob_name = goods[i].name parent.contents.glob_home = goods[i].home parent.contents.glob_unit = goods[i].unit parent.contents.glob_price = goods[i].price parent.contents.glob_quantity = q parent.contents.location.href= "javascript:BasketSetAdd()" } } }
Мало того, что эта функция передает необходимые сведения путем установки свойств другой страницы, она еще и вызывает внешний метод BasketSetAdd.
Что касается метода на странице contents, то он очевиден:
function BasketSetAdd() { BasketAdd(glob_code, glob_name, glob_home, glob_unit, glob_price, glob_quantity) }
На рис. 2 показан вариант страницы с фреймами, построенный на том же материале, что и вариант на рис. 1.
Обратите внимание, что итоговые суммы не допускают прямого вмешательства со стороны пользователя.
Редактирование списка покупок
Вариант с фреймами предполагает принципиально иной подход для отображения и редактирования покупательской корзины. Ведь коллекция объектов создается и хранится на странице, для отображения которой на экране выделено очень мало места. Это не позволяет показать даже единственную запись.
Зато в наличии имеется полноценное окно соседнего фрейма, к которому разрешен доступ. Если воспользоваться этим окном, то можно отобразить не только одну запись, но и все товары в покупательской корзине — ведь фрейм товаров допускает прокрутку.
Очевидно, что для отображения содержимого корзины потребуется динамически генерировать страницу целиком. К счастью, мы уже научились делать это, когда использовали вариант DHTML со списками. Более того, DHTML для корзины может оказаться более простым — ведь здесь не требуется создавать товарные группы и nuggets. К сожалению, размеры статьи не позволяют привести полный текст функции, поэтому рассмотрим только основные его фрагменты.
function ShowBasket() { // инициализация переменных для итогов . . . o = parent.main.document // начало формирования страницы o.writeln("<html>") o.writeln("<head>") . . . // требуется javascript //для функций редактирования o.writeln("<script>") o.writeln("<!—") o.writeln("n = (document.layers) ? 1:0") o.writeln("ie = (document.all) ? 1:0") . . . // начало вывода тела o.writeln("<body onLoad=\"init()\">") o.writeln("<div id=\"divFormQ\">") o.writeln("<form name=\"FormQ\">") . . . // вывода заголовка таблицы o.writeln("<table border=\"0\" width=\"100%\">") o.writeln(" <tr class=\"lineHdr\">") o.writeln(" <th width=\"50\">Item #</th>") . . . // основной цикл for (i = 1; i <= basket.len; i++) { iLineCount++ sClass = iLineCount%2 == 0 ? "lineEven" : "lineOdd" invI = iLineCount invN = basket[i].name invH = basket[i].home invU = basket[i].unit invQ = basket[i].quantity invP = Math.round(basket[i].price * 100) invA = Math.round(invP * invQ) invT += invA strP = "" + invP strP = strP.substring(0,strP.length - 2) + "." + strP.substring(strP.length - 2) strA = "" + invA strA = strA.substring(0,strA.length - 2) + "." + strA.substring(strA.length - 2) o.writeln(" <tr class=\"" + sClass + "\">") o.writeln(" <td width=\"50\" align=\"right\">" + invI + "</td>") . . . // вывод количества для редактирования o.writeln(" <td width=\"50\" align=\"right\"> <a href=\"javascript:void(0)\" onClick=\"BasketEdit(" + i + "," + invQ + ")\">" + invQ + "</a></td>") . . . // вывод завершающих тэгов o.writeln("</table>") o.writeln("</body>") o.writeln("</html>") o.close() }
Здесь следует обратить внимание на то, что динамически формируется полноценная страница, начиная с тэга <HTML> и заканчивая </HTML>. На странице должны присутствовать и таблица стилей CSS, и все необходимые функции JavaScript. Поскольку предполагается разрешить редактирование количества для каждого объекта, то будет использоваться тот же механизм, что и для первоначального выбора товара в «прайс-листе». Напомним, что этот механизм использует pop-up-форму, всплывающую в нужной позиции. Следовательно, потребуются все связанные с подобным решением функции, показанные в соответствующем разделе выше.
Еще одним важным моментом является форматирование и округление сумм до двух знаков после запятой.
И наконец, ключевое решение. Количество товара оформляется в виде элемента управления с событием onClick, вызывающим появление знакомой pop-up-формы ввода в позиции непосредственно под редактируемым числом. В данном случае сами цифры количества играют ту же роль, что и элемент управления «Buy Now!» при работе со страницами «прайс-листов».
На рис. 3 показан результат формирования такого DHTML c активированной процедурой редактирования количества для третьего элемента.
Обратите внимание, что в варианте с фреймами не предусмотрено удаление товара из корзины, как это сделано в варианте без фреймов. Вместо этого здесь предоставлена возможность произвольного изменения количества. Совершенно очевидно, что, присвоив количеству нулевое значение, товар можно считать аннулированным.
Для того чтобы отразить новое состояние корзины после редактирования, достаточно заново сгенерировать страницу. Происходит это точно так же, как и при серверных технологиях, только роль Perl-программы или ASP с успехом выполняет страница в соседнем фрейме, причем делает она это в автономном режиме.
Гибридное решение
Каждый из двух рассмотренных вариантов имеет свои преимущества. Вариант без фреймов позволяет использовать очень выразительные визуальные эффекты и графические приемы, которые в приложениях, связанных с коммерцией, будут весьма кстати. Плавающие объекты могут сделать электронный магазин незабываемым. Вполне вероятно, что пользователи захотят в него возвращаться снова и снова, хотя бы для того, чтобы просто поиграть.
В свою очередь, вариант с фреймами снимает ограничение на количество страниц с товарами и позволяет реализовать простую и эффективную процедуру редактирования покупательской корзины.
Можно совместить лучшие качества обоих вариантов в третьем, гибридном решении. Все преимущества варианта с фреймами обусловлены тем, что коллекция объектов покупательской корзины находится вне страницы с товарами и не уничтожается при выборе нового «прайс-листа». Следовательно, и для варианта без фреймов нужно найти внешнее хранилище информации.
Единственное допустимое регламентом работы браузеров место для хранения информации на компьютере клиента — это файлы Cookie. Однако они имеют жесткое ограничение по размеру. Кроме того, многие пользователи, из страха перед вирусами, включают запрет на запись этих файлов.
Выход может заключаться в использовании «вырожденных» фреймов — то есть в ситуации, при которой для какого-нибудь фрейма указывается нулевой размер. Например, главная страница электронного магазина может выглядеть так:
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=windows-1251"> <title>eStore</title> <meta name="description" content=""> <meta name="keywords" content=""> </head> <frameset cols="0,*" border=0> <frame name="contents" src="control.htm" marginwidth="0" marginheight="0" scrolling="Auto" frameborder="no" noresize> <frame name="main" src="main.htm" marginwidth="0" marginheight="0" scrolling="Auto" frameborder="no" noresize> </frameset> <noframes> <body> <p>This page requires frames. If you are seeing this message, then you do not have a browser capable of viewing this page.</p> </body> </noframes> </frameset> </html>
Ключевым моментом здесь является атрибут COLS="0,*" в тэге <FRAMESET>. Значение "0" относится к первому фрейму и извещает браузер, что для страницы control.htm места на экране отводить не следует. Именно на этой странице можно создать коллекцию объектов корзины и функции генерации DHTML. Страница в скрытом фрейме будет играть роль своеобразного сервера, передаваемого клиенту для полностью автономной работы.
Можно пойти дальше и загрузить в скрытый фрейм всю базу данных. Естественно, в этом случае она должна состоять из структурированных записей. Как уже говорилось, файлы в формате JS обладают минимальной избыточностью. Это позволит очень быстро загрузить базу данных, содержащую тысячи наименований. Вместе с базой данных хранятся и методы доступа. Теперь все отображаемые страницы будут только динамическими. Появляется возможность формировать их по запросу пользователя, точно так, как это делается при серверной технологии, но только автономно.
Формирование счета
Пришло время перейти к заключительному этапу работы электронного магазина. Здесь следует сделать оговорку, что этот этап является следствием описываемой технологии, реализуемой исключительно на клиентской стороне. При серверных технологиях такого этапа просто не существует — ведь большинство необходимых сведений продавец (сервер) получает в процессе выбора товаров. В нашем же случае продавцу не известно ничего. Он пока даже не знает, что кто-то посетил магазин и набрал кучу товаров.
Чтобы связаться с продавцом, необходимо заполнить форму. В этой форме прежде всего должны быть предусмотрены поля для идентификации покупателя и его платежных средств. При серверной технологии тоже необходима подобная форма, однако на этом требования к форме заканчиваются. При клиентской технологии в форму необходимо поместить и все сведения о покупке: коды и наименования товаров, их цены и количества в единицах измерения. Другими словами, к форме для продавца должна быть приложена и вся покупательская корзина.
Генерация формы
Совершенно очевидно, что страница с формой, включающей покупательскую корзину, должна генерироваться динамически. Кроме того, содержание этой страницы практически полностью определяет и ее возможный вид. Это может быть обычный счет-фактура, применяемый при финансовых операциях. Действительно, на странице требуются поля для сведений о покупателе и таблица с перечнем приобретенных товаров. Остается добавить сведения о продавце и итоги, что не составит большого труда, — и получится типичный и хорошо известный бухгалтерский документ. А раз так, то почему бы не предоставить покупателю возможность не только заполнить и отправить форму, но и напечатать для себя «оправдательный» документ. Чтобы обеспечить возможность печати, динамическую страницу следует открывать в отдельном окне браузера, в котором имеется меню с командами печати и установки параметров страницы (а в Netscape Navigator — и с командой предварительного просмотра). То, что при открытии страницы в отдельном окне теряется связь с программной системой, уже несущественно. Никто не собирается предоставлять возможность редактирования списка товаров в окончательном документе. Если покупатель не доволен списком, он может просто вернуться в окно основной страницы и там выполнить изменения, а затем заново сформировать счет для печати и отправки продавцу.
Таким образом, функция генерации счета будет даже проще, чем функция генерации DHTML для просмотра и редактирования покупательской корзины в варианте с фреймами. Ведь в счете не требуются функции JavaScript для отображения pop-up-окон и какие-либо гиперссылки.
Практически все, что требуется для счета, можно позаимствовать в функции ShowBasket и при этом убрать много лишнего, связанного с редактированием количества товаров. Но придется добавить описание формы и поля ввода, заполняемые пользователем. Не следует забывать и о надлежащем оформлении финансового документа. В нем должны присутствовать дата и номер документа и другие общепринятые реквизиты:
function Invoice() { var dDat = new Date() var dYY = dDat.getYear() var dMM = dDat.getMonth() + 1 var dDD = dDat.getDate() var sDat = dDD + "." + dMM + "." + dYY var invNo = Math.round(dDat.getTime()/10000) . . . // открытие окна для вывода DHTML win = window.open("","Invoice", "toolbar=no,width=740,height=420, directories=no,status=no, scrollbars=yes,resizable=yes, menubar=yes,top=10,left=50") o = win.document // начало вывода o.writeln("<html>") o.writeln("<head>") . . . // тело документа o.writeln("<body>") o.writeln("<form name=\"invoice\" ACTION= . . .
Здесь следует сделать остановку и вспомнить о двойственности решаемой задачи. С одной стороны, мы формируем счет-фактуру для печати, а с другой — нам необходимо переправить покупательскую корзину продавцу. В обоих случаях используется одна и та же коллекция объектов — корзина. Но форма представления данных должна быть разной. Не требуется отправлять вместе с формой форматированную таблицу с итогами и вычисляемыми значениями. Вполне достаточно ограничиться кодом и количеством товара. Но самое главное — данные для пересылки должны размещаться в полях <INPUT>, а не в обычных элементах <TD>, как это предусмотрено в функции ShowBasket.
Вставлять в ячейки таблицы товаров поля <INPUT> нет ни желания, ни необходимости. Такое решение порождает массу проблем, связанных с особенностью отображения полей разными браузерами. Да и провоцировать пользователя подкорректировать данные не следует.
Очевидно, что поля <INPUT> для товаров должны иметь атрибут TYPE="HIDDEN". Для их формирования можно организовать отдельный цикл обработки коллекции. Предусмотреть для каждого товара отдельный именованный элемент <INPUT TYPE="HIDDEN"> или поместить все товары в единственное поле — дело вкуса. Фрагмент функции Invoice может быть таким:
for (i = 1; i <= basket.len; i++) { if (basket[i].quantity != 0) { itemField++ strItem = "|" + basket[i].code + "|" + basket[i].name + "|" + basket[i].price + "|" + basket[i].unit + "|" + basket[i].quantity o.writeln(" <input type=\"hidden\" name=\"item" + itemField + "\" value=\"" + strItem + "\">") } }
Здесь для каждого товара генерируется отдельное поле с именем itemN, где N — порядковый номер купленного товара.
Второй просмотр коллекции товаров выполняется уже для генерации таблицы в форматированном виде и практически повторяет цикл в функции ShowBasket, за исключением формирования элементов <A>.
И еще одно обстоятельство отличает циклы в функциях Invoice и ShowBasket. Для просмотра и редактирования выводятся все записи, даже с нулевым количеством. Не исключено, что покупатель передумает и вернет товар в корзину. В счете же все обнуленные элементы пропускаются.
Завершают функцию Invoice стандартные элементы формы — кнопки SUBMIT и RESET:
o.writeln(" <tr align=\"center\">") o.writeln(" <td colspan=\"3\" align=\"center\"> <input type=\"submit\" value=\"Submit\" NAME=\"submit\" class=\"button\"> <input type=\"reset\" value=\"Reset\" NAME=\"reset\" class=\"button\"></td>") o.writeln(" </tr>") o.writeln(" </table>") o.writeln("</form>") o.writeln("</body>") o.writeln("</html>") o.close()
На рис. 4 показан результат работы функции Invoice, DHTML-страница, открытая в отдельном окне браузера.
При разработке функции Invoice необходимо позаботиться и о возможных вариантах получения оплаты. Если в наличии имеется развитая система Internet-банков, то на страницу счета можно поместить гиперссылки для обращения к наиболее популярным из них. Кроме того, можно подготовить реквизиты платежного поручения в виде, удобном для копирования в формы банковской системы. Если продавец имеет возможность получения оплаты с помощью кредитных карточек, то необходимо предусмотреть способы защиты кода карточки при пересылке по Сети. Однако подробное обсуждение вопросов конфиденциальности выходит за рамки настоящей статьи.
Использование Web-сервиса
Вплоть до окончательного формирования счета какого-либо обращения к серверным программам не требовалось. Но теперь необходимо указать имя программы, которая должна принять данные из формы после того, как пользователь нажмет кнопку Submit. Имя этой программы указывается в атрибуте ACTION в элементе <FORM>. Обычно здесь и появляются всевозможные /CGI-BIN/, ASP и PHP.
Если у начинающего предпринимателя нет возможностей разработки собственных серверных приложений, можно использовать многочисленные службы Web-сервиса, предоставляющие бесплатные средства для типичных ситуаций.
В качестве одной из лучших подобных служб можно порекомендовать американскую компанию Bravenet (http://www.bravenet.com). У этой компании отменное качество сервиса, что позволило ей только за один год привлечь более миллиона новых клиентов. Практически это единственная компания, которая в нашей ситуации способна выполнить необходимые действия совершенно незаметно, без всякой рекламы и напоминания о своем посредничестве.
Для того чтобы использовать сервис Bravenet, необходимо зарегистрироваться на сайте компании. Из множества предоставляемых видов сервиса нам потребуется единственный, который называется E-Mail Form. Суть его заключается в следующем. Любая форма, заполненная на страницах клиента, пересылается по заданному e-mail-адресу в виде обычного почтового сообщения. Кроме того, можно задать адрес собственной Web-страницы, которая автоматически будет загружена в то окно, где раньше была страница с почтовой формой. В нашем случае на такой странице можно написать что-нибудь типа: «Спасибо за покупку». Это проинформирует покупателя о том, что его деятельность успешно завершилась.
Почтовые сообщения, формируемые Bravenet, корректно работают с языками. Обычно сообщение приходит в кодировке той Web-страницы, с которой оно было отправлено. В сложных случаях используется система кодирования Unicode UTF-8, так что тексты, содержащие кириллицу, всегда можно будет прочитать.
Чтобы обратиться к этому сервису, в функции Invoice достаточно записать:
o.writeln("<form name=\"invoice\" ACTION=\http://pub12.bravenet.com/emailfwd/ senddata.asp\ METHOD=\"POST\">") o.writeln(" <input type=\"hidden\" name=\"usernum\" value=\"XXXXXXXXXX\"> <input type=\"hidden\" name=\"webpage\" value=\"From_My_Invoice_Page\">")
Здесь XX…X — десятизначный цифровой код, полученный при регистрации сервиса. Можно указать и название страницы, с которой послано сообщение. В приведенном примере это "From_My_Invoice_Page".
Использование почтовой службы клиента
Начиная с версии Internet Explorer 4.x (в Netscape Navigator эта возможность была всегда) в атрибуте ACTION можно указывать URI вида mailto. То есть элемент <FORM> может выглядеть так:
<FORM name="invoice" method="post" action=mailto:you@yourprovider.com?subject=Invoice enctype="text/plain">
Подобная запись приведет к тому, что будет автоматически сформировано сообщение с помощью почтовой службы, имеющейся на компьютере клиента. Текст сообщения будет состоять из пар: "имя_элемента_input=значение". Это очень удобно. Единственная неприятность заключается в том, что перед отправкой сообщения браузер выдаст предупреждение о том, что адрес электронной почты отправителя передается в незащищенное пространство.
Можно даже использовать такую конструкцию:
<FORM name="invoice" method="post" action=mailto:you@yourprovider.com? subject=Your Form Submission&body=See Attachment enctype="multipart/form-data">
В этом случае все поля формы будут переданы в виде отдельного, присоединенного к письму файла (в Netscape Navigator для каждого поля будет сформирован отдельный файл).
Дополнительно Internet Explorer сохранит такое сообщение в папке «отправленные» локальной почтовой программы, что немаловажно для клиента-покупателя.
Приведенное решение можно было бы считать идеальным по двум причинам. Во-первых, вся система становится полностью независимой от каких-либо серверов. А во-вторых, вместе со счетом обязательно пересылается реальный e-mail-адрес покупателя, который обеспечит возможность обратной связи. Для сравнения: при использовании сервиса Bravenet поле e-mail-отправителя клиент может оставить незаполненным, и единственное, что получит продавец, — это IP-адрес покупателя в dot-нотации.
К сожалению, для применения такого варианта необходимо, чтобы на компьютере клиента была правильно установлена и зарегистрирована в браузере почтовая служба. А это заведомо сокращает число потенциальных покупателей, лишая их возможности делать покупки посредством случайного компьютера или компьютеров общего пользования в различных Internet-кафе.
Что предпочесть — максимально широкий круг покупателей или полную независимость от серверов — решать владельцу электронного магазина.
Дополнительные возможности
В Сети постоянно появляются новые виды сервиса. Хочется рассказать об одном из них, который может оказаться исключительно полезным для приложений, связанных с электронной коммерцией. Речь идет о разработке фирмы HumanClick (http://www.humanclick.com). С января 2001 года начинает работать промышленная версия программы, в то время как в прошлом году тестировались только бета-версии.
Система Human Click предназначена для мониторинга собственных Web-страниц и организации прямого диалога с посетителями, оказавшимися на сайте. Система поставляется в виде Windows-приложения, устанавливаемого только на локальном компьютере владельца, и нескольких дополнительных кодов, добавляемых к Web-страницам. Замечательно то, что посетители страниц при этом ничего не замечают. Им не предлагается установить какое-либо дополнительное программное обеспечение. Не используются даже Java-аплеты.
Включения в Web-страницы бывают двух типов. Первый вызывает появление изображения кнопки, на которой отражается текущее состояние владельца страницы (online или offline). Состояние online показывает, что владелец страницы подключен к Cети и с ним можно начать прямой диалог в Chat. Состояние offline позволяет только послать сообщение владельцу по электронной почте. Другими словами, это средство для клиента, с помощью которого он может обратиться к владельцу. Если владелец не желает, чтобы его тревожили, подобную кнопку можно и не помещать на страницу.
Второе включение, наоборот, средство для владельца. Оно вообще никак не отображается на экране:
<!— BEGIN HumanTag Monitor. DO NOT MOVE! MUST BE PLACED JUST BEFORE THE /BODY TAG —> <script language=’javascript’ src=’http://hc2.humanclick.com/hc/XXXXXXXX/x.js? cmd=file&file=chatScript3&site=XXXXXXXX&category=en;0'> </script> <!— END HumanTag Monitor. DO NOT MOVE! MUST BE PLACED JUST BEFORE THE /BODY TAG —>
Здесь XX…X — код зарегистрированного пользователя.
Располагая этими средствами, владелец сайта может в любой момент посмотреть, не находится ли кто-нибудь из посетителей на его страницах, а если посетитель есть — предложить ему пообщаться в Chat.
На рис. 5 показано, как выглядит подобное предложение.
Если посетитель согласен, он нажимает на кнопку и открывается окно Chat Room. В противном случае владельцу поступает сообщение, что посетитель отказывается от общения.
Согласитесь, что это очень напоминает отношения продавца и покупателя в магазине самообслуживания. Продавец замечает покупателя и предлагает свою помощь. Покупатель может либо принять ее, либо отвергнуть. Идеальное решение для электронного магазина. Не правда ли?
Замечательно, что система Human Click, в отличие от популярных систем общения Odigo и ICQ, не требует наличия у каждой из сторон специального программного обеспечения. Эта система по сути своей асимметрична. Владелец сайта располагает мощными средствами, которые вполне компенсируют даже отсутствие полноценных серверных возможностей. Странно только, что разработчики остановились на письменном способе общения. Раз уж контакт между локальными компьютерами установлен, то можно было бы реализовать и голосовое общение. Процесс купли-продажи от этого только бы выиграл.
К сожалению, не обошлось и без «ложки дегтя». В настоящее время система Human Click поддерживает десять европейских языков. Русский язык в это число не входит. И это несмотря на то, что еще осенью 2000 года автор настоящей статьи обращался в компанию-разработчик с конкретными предложениями о том, как нужно модифицировать некоторые элементы, чтобы обеспечить прием и передачу сообщений, содержащих кириллицу.
Остается надеяться, что отечественные программисты сумеют создать систему не хуже.
Заключение
Слово «вариации» в названии настоящей статьи не случайно. Здесь представлены подходы, позволяющие реализовать четыре различных варианта электронного магазина, комбинируя страницы с фреймами и без фреймов, структурированные списки и произвольное представление данных. Намечен и пятый — гибридный вариант. В дополнение к этому можно варьировать и механизмы передачи сообщений, выбирая между Web-сервисом и локальной почтовой службой.
Частично решения, описанные в настоящей статье, можно загрузить в виде законченного приложения из библиотеки разработок Microsoft Office Extensions по адресу: http://www.microsoft.ru/offext/b.asp, код разработки 411.
Предприниматель, решивший реализовать электронный магазин своими силами по предложенной методике, должен учитывать следующее обстоятельство. Кроме функций JavaScript и Web-дизайна требуется значительная работа по «классическому» программированию. Прежде всего нужно разработать и вести собственную базу данных товаров. Затем потребуется разработка утилиты для преобразования базы данных к виду JS либо препроцессора для формирования и включения фрагментов кода в HTML-документы. Для того чтобы воплотить замыслы дизайнера по оригинальному оформлению счета, потребуется разработка специального конвертора, утилиты, преобразующей HTML в DHTML. Подобная утилита должна обеспечивать вставку идентификаторов переменных для полей с изменяемыми значениями и предусматривать циклы.
Наконец, если продажи пойдут успешно, потребуется освоение MAPI для автоматической обработки поступающих заказов.
Однако не следует считать, что чисто клиентская технология способна обеспечивать все необходимое. Например, без серверной технологии не удастся реализовать важные маркетинговые приемы, основанные на учете постоянных покупателей. Если приветствия при повторном посещении магазина еще можно обеспечить с помощью Cookie, то ввести систему скидок для постоянных клиентов уже не удастся.
Однако, несмотря на все это, электронную торговлю можно начинать даже на бесплатных «хостах» типа Narod.ru.
КомпьютерПресс 3'2001