Введение
В статье рассматривается пример реализации интерфейса к веб-сервису
через Internet Explorer (далее IE).
Что есть интересного в рассматриваемом решении:
- Использование DHTML компонентов Webservice Behavior и Rowover
Behavior
- Отправка объектов из IE в веб-сервис.
- Отображение данных веб-сервиса в IE.
- Индикация процесса загрузки данных из веб-сервиса.
- Использование островков данных XML в HTML страницах.
- Частичное обновление данных на странице.
Работа с веб-серивсом напрямую через IE вещь экзотическая, но может
таки понадобиться в ситуациях, когда требуется:
- Максимально снизить объем трафика.
- Часто обновлять некоторую информацию на HTML странице.
- Снизить нагрузку на веб сервер
Экономия трафика происходит за счет обмена только данными между IE и
сервисом. Так как данные приходят в известном формате (XML), то их легко
разобрать и вставить в нужные места HTML страницы. Процесс обмена
данными с веб-сервисом значительно упрощается, за счет использования
Webservice Behavior (http://msdn.microsoft.com/workshop/author/webservice/overview.asp)
- DHTML компонента разработанного в Microsoft специально для этих целей.
Введение в DHTML компоненты.
DHTML компонент - это программа определяющая поведение (реакцию на
разные события, например: "onclick", "onmouseover"…) объекта на HTML
странице к которому этот компонент подсоединен. Компонент представляет
собой файл с расширением HTC, программа в нем может храниться, например,
на JavaScript. Так же сам DHTML компонент может вставлять объекты на
страницу и определять поведение этих объектов.
Например, компонент "Coolbar" (http://msdn.microsoft.com/downloads/samples/internet/behaviors/library/coolbutton/coolbar_js.htm)
из коллекции компонентов для IE (http://ie.components.microsoft.com/behaviors/),
создает на странице панельку кнопок и реагирует на нажатия на эти
кнопки. Другой компонент Rowover (http://msdn.microsoft.com/downloads/samples/internet/behaviors/library/rowover/rowover_js.htm)
привязывается к существующей таблице и реагирует на события курсора на
этой таблице.
Упрощенно: DHTML компонент - это сохраненный в файле скрипт, который
привязывается к определенным событиям на HTML странице и/или создает
HTML элементы, на события которых он тоже может реагировать.
Использовать DHTML компоненты стоит когда:
- HTML элементы с определенным поведением (скрипты клиентской
стороны) повторяются у вас на многих страницах.
- К типичным HTML элементам нужно привязать нестандартное
поведение. Пример: указанный ранее компонент "Rowover", который
выделяет цветом строку таблицы, над которой находится курсор.
- На HTML странице требуется объект способный порождать
собственные события, на которые могут подписаться другие объекты на
странице. Пример такого компонента приведен в листинге 1.
Ниже приведен пример DHTML компонента "Updater". Этот компонент
периодически обращается к веб-сервису, получает от него данные и
передает их обработчикам на HTML странице, которые подписаны на событие
"onupdate" этого компонента.
<EVENT NAME="onupdate" ID="update" /> //Событие обновления данных.
<METHOD NAME="GetUpdates" /> //Метод для получения данных от веб-сервиса.
<script language="javascript">
var webServicePath = "services/UpdateService.asmx?WSDL";
var webServiceMethod = "GetUpdates";
var blotterNodeName = "liabilities";
var updateData = new ActiveXObject("MSXML2.DOMDocument");
var intervalID;
var gbLoading;
var wsCallID;
updateData.async = false;
element.attachEvent("onreadystatechange", fnOnReadyStateChange);
function fnOnReadyStateChange()
{
gbLoading = (readyState != "complete")
if (!gbLoading)
{
init();
}
}
function GetUpdates()
{
wsCallID = webService.UpdateService.callService(onWSGetUpdates, webServiceMethod);
}
function init()
{
webService.useService(webServicePath,"UpdateService");
window.setInterval(uniqueID+".GetUpdates()", 5000);
// Раз в 5 секунд получаем обновления с веб сервиса.
}
function onWSGetUpdates(result) {
if (result.error) {
HandleError(result)
} else if(!result.error) {
updateData.loadXML(result.raw.xml);
var updateNode = updateData.selectSingleNode("//GetUpdatesResult");
var oEvent = document.createEventObject(); // Конструируем событие
oEvent.setAttribute("updateXML", updateNode);
// Добавляем полученные от веб-сервиса данные в аттрибуты события
update.fire(oEvent); //Порождаем событие.
}
}
</script>
Листинг 1.
Присоединение к событию различных обработчиков выглядит так:
window.Updater.attachEvent("onupdate", InsertUpdate)
// где InsertUpdate это функция обрабатывающая обновление.
function InsertUpdate()
{
var objUpdateXml = window.event.updateXML;
…….
}
Подключение HTML компонента к странице
Необходимый СSS:
.updater
{
behavior:url(htc/updater.htc);
}
Необходимый HTML.
<div class="updater" id=Updater></div>
Еще один пример HTML компонента есть в форуме
www.gotdotnet.ru - это форма ввода
сообщения.
Внутри себя компонент Webservice Behavior использует для
взаимодействия с веб-сервисами ActiveX объект "Microsoft.XMLHTTP".
Webservice Behavior поддерживает состояние сессии.
Задача
Требуется разработать интерфейс для ввода и отображения заявок на
покупку акций через Интернет. Информацию о заявках можно получить от
веб-сервиса, заявки посылаются и модифицируются через тот же веб-сервис.
Функции интерфейса:
- Отправить заявку.
- Отобразить список заявок.
- Обновить список заявок.
- Отфильтровать и отсортировать список заявок.
- Отредактировать заявку.
Ограничения
У клиентов использующих интерфейс установлен браузер Internet
Explorer не ниже версии 5.0.
Решение
Веб-сервис
На рисунке 1 приведена диаграмма классов описывающая структуру
веб-сервиса используемого для взаимодействия с биржей. Синим цветом
выделены классы, объекты которых будут передаваться между IE интерфейсом
и веб-сервсом.
Рисунок 1
Обмен данными с веб-сервисом
Обмен данными с веб-сервисом происходит, по схеме указанной на
рисунке 2. С помощью Web Service Behavior мы запрашиваем у веб-сервиса
данные, полученный XML вставляем в XML островок (DataSource). При
обновлении этого островка автоматически обновляется привязанный к нему
потребитель данных (таблица или поле ввода). При отправке данных берем
значения соответствующих контролов на странице и отправляем их в
веб-сервис.
Рисунок 2
Инициализация веб-сервиса у клиента
Регистрируем Web Service Behavior на HTML странице. Для этого создаем
CSS класс
.webservice { behavior: url(htc/webservice.htc);
который реализует поведение Web Service Behavior. После чего
добавляем элемент DIV этого класса на страницу. Именно этот элемент и
будет предоставлять возможности общаться в веб-сервисом.
<div id="webService" class="webservice" showProgress="true"></div>
Перед обращением к методам веб-сервиса необходимо проинициализировать
его в Web Service Behavior вызвав метод:
webService.useService("services/OrdersEntryService.asmx?WSDL","Exch");
webService.useService(путь_к_сервису,имя_сервиса)
путь_к_сервису . Пример: "services/OrdersEntryService.asmx?WSDL"
имя_сервиса. Пример: "Exch" , здесь может быть любое
слово. Сделано для возможности использовать несколько веб-сервисов на
странице.
Пример использования веб-сервиса после его инициализации.
wsCallID = webService.Exch.callService(onWSSaveOrder, "AddOrder", order);
В момент инициализации Web Service Behavior получает от веб-сервиса
его описание и на основе этого описания происходит последующее
взаимодействие с сервисом.
Примечание : В описании к Web Service Behavior говорится, что
в качестве веб-сервиса может выступать только сервис, доменная часть URL
которого совпадает с доменной частью URL текущей страницы. Проще говоря,
если адрес сервиса
http://mydomen/OrdersEntryService.asmx?WSDL а адрес странички
использующей сервис
http://yourdomen/page.aspx, то они не будут между собой
взаимодействовать. О том, как это обойти можно прочитать тут:
http://msdn.microsoft.com/workshop/author/webservice/using.asp#Security_Issues
Отправить заявку
Для того, чтобы передать объект из IE интерфейса в веб-сервис
необходимо:
сконструировать объект в IE с помощью JavaScript
var order = new Object();
order.Side = lbxSide.options[lbxSide.selectedIndex].value;;
order.Quantity = window.document.getElementById(´tbQty´).value;
order.Instrument = ddlAssets.options[ddlAssets.selectedIndex].value;;
order.TradingFloor = ddlTradingFloors.options[ddlTradingFloors.selectedIndex].value;;
передать объект с помощью HTML компонента Webservice Behavior.
wsCallID = webService.Exch.callService(onWSSaveOrder, webServiceMethod, order);
//callService(функция, название_веб_метода, параметры_веб_ метода)
функция - ссылка на функцию, которая будет вызвана компонентом
Web Service Behavior в момент возвращения данных веб сервисом (callback
функция). Пример - onWSSaveOrder.
название_веб_метода. Пример - "AddOrder".
параметры_веб_ метода. Параметры веб - метода через запятую.
Пример callback функции.
function onWSSaveOrder(result) {
if (result.error) {
locked = false;
HandleError(result)
} else if(!result.error) {
try
{
returnData.loadXML(result.raw.xml);
var rowsetNode = returnData.selectSingleNode("//OrderID");
alert("Заявка успешно сохранена\n № заявки: "+rowsetNode.text);
}finally
{
locked = false;
}
}
ChangeCursor("default");
}
Примечание: Исходная версия этого компонента от Microsoft не
позволяет отправлять сервису в качестве параметров объекты. Толи это
баг, толи фитча, но после изменения кода компонента в строках 675-677 мы
сможем использовать объекты в качестве параметров сервиса. Исправленная
версия компонента прилагается к статье.
Отобразить список заявок.
Список заявок отображается в таблице, которая привязывается к
островку данных на странице.
<xml id="DataSource" ></xml>
<table datasrc=#DataSource>
При обновлении данных в XML островке обновляются данные и в таблице.
Список заявок запрашивается методом:
webService.Exch.callService(onWSGetBlotter, ´GetOrdersBlotter´, Filter,
sortColumn, sortMode);
Данные возвращаются в функцию onWSGetBlotter:
function onWSGetBlotter(result) {
if (result.error) {
locked = false;
HandleError(result)
} else if(!result.error) {
try
{ //Изменяем XML данные в источнике (XML островке) DataSource.
//При этом пришедшие данные отображаются в табличке.
returnData.loadXML(result.raw.xml);
var reportNode = returnData.selectSingleNode("//GetOrdersBlotterResult");
if(reportNode.hasChildNodes)
{
DataSource.documentElement = reportNode ;
}
}finally
{
locked = false;
ChangeCursor("default");
}
}
}
Обновить список заявок
Обновление списка заявок делается периодическими запросами данных от
веб-сервиса, и обновлением на основе них XML островка. В развитие
приведенной схемы обновлений желательно делать запрос не всех данных, а
только обновлений. Это потребует реализации некоторой функциональности
по сбору и накоплению обновлений для каждого пользователя на серверной
стороне веб-сервиса. Более легкой оптимизацией процесса обновлений может
стать введение для каждой строки таблицы скрытого поля, в котором будет
содержаться хэш-код данных этой строки. При каждом обновлении сравнивать
хэш-код строки пришедшей с сервера и хэш-код текущей строки и в случае
различия обновлять строку.
Отфильтровать и отсортировать список заявок
Фильтрация выполняется с помощью передаваемого в метод
"GetOrdersBlotter" массива структур типа FilterParameter. Опять же,
здесь приходится очень полезной возможность передавать объекты и массивы
объектов в веб сервис.
function SetFilter()
{
var ddlTradingFloors = window.document.getElementById(´ddlTradingFloors´);
var lbxSide = window.document.getElementById(´lbxSide´);
var ddlAssets = window.document.getElementById(´ddlAssets´);
var side = lbxSide.options[lbxSide.selectedIndex].value;
var instrument = ddlAssets.options[ddlAssets.selectedIndex].value;
var tradingFloor = ddlTradingFloors.options[ddlTradingFloors.selectedIndex].value;
locked = true;
Filter = new Array();
try
{
if (side != 0)
{
AddToFilter("Side",side);
}
if (instrument != 0)
{
AddToFilter("Instrument",instrument);
}
if (tradingFloor != 0)
{
AddToFilter("TradingFloor",tradingFloor);
}
}finally
{
locked = false;
}
ClearBlotter();
GetBlotter();
}
function AddToFilter(fieldName, value)
{
var temp = new Object();
temp.Name = fieldName
temp.Value = value;
Filter[Filter.length] = temp;
}
Для сортировки списка в веб сервис передаются имя колонки и
направление сортировки.
Отредактировать заявку
При клике на заявке в списке, она открывается в форме для
редактирования.
Здесь нам помогает использование еще одного DHTML компонента "Rowover
Behavior".
Этот компонент позволяет привязывать свои обработчики к событию click
на строке таблицы, раскрашивать в разные цвета строки таблицы,
отображать курсор на строках таблицы.
Привязывается этот компонент к таблице по аналогии с компонентом
Webservice Behavior.
<table rules="all" onrowclick="onOrder_click();" CLASS="rowover" selectable="true"
STRIPED="true" datasrc=#DataSource>
Элементы формы редактирования заявки связаны с XML островком данных.
<input type="text" id="tbID" size=5 datasrc=#DataSource datafld="OrderID">
<input type="text" id="tbQty" size=5 datasrc=#DataSource datafld="Quantity">
<select id="ddlStatuses" datasrc=#DataSource datafld="Status">
При изменении данных в островке обновляется и содержание формы.
Заключение
В статье описаны возможности использования DHTML компонентов для
связи с веб-сервисами и функции привязки данных в Internet Explorer.
Это еще один способ, как сделать веб интерфейс максимально похожим на
интерфейс десктопный, как снизить трафик и нагрузку на сервер.
Работающий пример доступен по адресу:
http://shelomanov.russia.webmatrixhosting.net/timetracker_cs_vs_1/ .
Все DHTML компоненты, использованные в примере, доработаны автором,
об этом упомянуто в тексте статьи.