Казахстан, Алматы, Гимназия им. Ахмета Байтурсынова №139, 2008 |
Добавление динамических компонент в Интернет-магазин
15.3.2. Вызов веб-службы из клиенсткого сценария
Теперь, когда веб-сервис реализован, можно переделать страницу products/default.aspx, сделав так, чтобы она отображала данные, которые будет возвращать веб-сервис. Чтобы облегчить работу с отображением данных, воспользуемся библиотекой ExtJS, ознакомиться и скачать которую можно по адресу: http://www.extjs.com/
Подключим библиотеку ExtJS к нашему проекту, добавив ссылки на файлы ext-all.css, ext-base.js и ext-all.js в head' е мастера страниц так, как это указано в следующем коде:
<head runat="server"> <meta content="text/html; charset=utf-8" http-equiv="Content-Type" /> <title></title> <link href="styles/style2.css" media="screen" rel="stylesheet" title="CSS" type="text/css" /> <link rel='stylesheet' type='text/css' href='styles/ext-all.css' /> <script type="text/javascript" src="../JS/ext-base.js" ></script> <script type="text/javascript" src="../JS/ext-all.js" ></script> <script type="text/javascript" src="JS/jquery-1_2_6_min.js"></script> </head>
Теперь перейдем к странице products/default.aspx, удалим код, содержащийся в ContentPlaceHolder' ах и серверный код.
Теперь в левый ContentPlaceHolder добавим тег script, где разместим следующий код:
Ext.onReady(function(){ var query = getQueryVariable(); var result = WebProductService.GetProducts (query.category ? query.category : '', query.subCategory ? query.subCategory : '',0, SucceededCallback, FailedCallback); }); function FailedCallback(error) { alert(error.message); } function SucceededCallback(result, eventArgs) { } function getQueryVariable() { //полачаем строку запроса (?a=123&b=qwe) и удаляем знак ? var query = window.location.search.substring(1); //получаем массив значений из строки запроса вида vars[0] = 'a=123'; var vars = query.split("&"); var arr = new Array(); //переводим массив vars в обычный ассоциативный массив for (var i = 0; i < vars.length; i++) { var pair = vars[i].split("="); arr[pair[0]] = pair[1]; } return arr; }
Функцию SucceededCallback на данный момент определять не будем, так как прежде нам потребуется написать вспомогательный код.
В приведенном коде определены 4 функции. Первая функция является анонимной, и выполняется в тот момент, когда библиотека ExtJS закончит инициализацию, что произойдет после полной отрисовки страницы. Вначале будет вызвана вспомогательная функция getQueryVariable, которая позволит определить, какие переменные определены в строке запроса.
Функция getQueryVariable обращается к объекту window.location, который содержит всю строку запроса, после чего обращается в свойству search, содержащему строку параметров в формате ?a=123&b=qwe. Далее у этой строки удаляется первый символ, и она методом split разделяется на множество строк, разделенных знаком &. После этого, каждая полученная строка также разделяется на две, содержащие имя параметра и его значение, и записываются в массив arr, который в данном случае выступает в качестве хэш-таблицы.
Результат вызова функции getQueryVariable помещается в переменную query.
Далее вызывается функция WebProductService.GetProducts, которая представляет собой сгенерированную ScriptManager' ом прокси-функцию, вызывающую одноименный веб-метод. При этом JavaScript устроен так, что вызов самого веб-метода будет отложен до тех пор, пока текущий поток не закончит свою работу. Чтобы узнать, как отработал веб-метод, в функцию WebProductService.GetProducts помимо трех параметров, необходимых для работы, передаются также два метода обратного вызова. Метод FailedCallback будет вызван в случае, если во время выполнения веб-метода произойдет ошибка, и отобразит текст этой ошибки пользователю. Метод SucceededCallback выполнится в случае, если веб-метод успешно завершит свое выполнение. В этом случае переменная result будет содержать результат работы веб-метода.
Уже сейчас можно запустить наш сайт и, если открыть IE Developer Tools и поставить breakpoint в метод SucceededCallback, можно убедиться, что веб-сервис возвращает нужный ответ, изучив переменную result (рис. 15.5).
Однако данные будут бесполезны, если пользователь их не увидит. Для того чтобы отобразить данные, воспользуемся клиентским компонентом Ext.grid.GridPanel, который предоставляет во многом схожую функциональность с той, что дает ASP.NET -компонент GridView, но позволяет выполнять большинство действий без взаимодействия с сервером, не перерисовывая страницу.
Как и для ASP.NET GridView необходимо для Ext.grid.GridPanel указать источник данных, который должен наследоваться от класса Ext.data.Store. Так как разработанный веб-метод возвращает данные в формате JSON, то нам подойдет класс Ext.data.JsonStore. Каждый источник данных в этом подходе состоит из двух важных компонент: DataProxy, который отвечает за извлечение данных, и Reader' а, который умеет полученные при помощи DataProxy данные разбирать. И если стандартный класс JsonReader нас устроит для разбора данный, то ни одна из реализаций DataProxy не позволяет обращаться к веб-сервисам. Поэтому мы разработаем собственный (сильно упрощенный) прокси-класс, который назовем Ext.data.FunctionProxy:
Ext.data.FunctionProxy = function(f) { Ext.data.FunctionProxy.superclass.constructor.call(this); this.func = f; }; Ext.extend(Ext.data.FunctionProxy, Ext.data.DataProxy, { load: function(params, reader, loadCallback, scope, arg) { var proxy = this; this.func({ first: params.start, count: params.limit, sortAsc: params.dir != "DESC", scope: params.scope, totalCount: proxy.totalCount || 0, fields: params.fields }, function(data) { var records = reader.readRecords(data); proxy.totalCount = records.totalRecords; proxy.lastArguments = proxy.arguments; loadCallback.call(scope, records, arg, true); }); }, reload: function() { this.load.apply(this, this.lastArguments); } })
При разработке этого класса используется объектно-ориентированный подход программирования на JavaScript, предлагаемый библиотекой ExtJS. Вначале объявляется конструктор класса Ext.data.FunctionProxy, который принимает на вход переменную-функцию f и который вызывает конструктор базового класса. При этом переменная f сохраняется в поле func текущего объекта.
Далее определяется, что класс Ext.data.FunctionProxy является расширением класса Ext.data.DataProxy, у которого переопределены методы load и reload. Это делается при помощи статического метода Ext.extend.
Функция load вызывает функцию func, передавая параметры в виде объекта arg, поля которого содежат информацию о номере первой извлекаемой запись, количестве записей, направлении сортировки, области видимости, полном количестве записей и наборе извлекаемых полей. Второй передаваемый в функцию func параметр представляет собой функцию обратного вызова, которая сработает, когда данные с сервера будут извлечены (т.е. заменит функцию SucceededCallback ). Эта функция применяет JsonReader для разбора записей и представления их в виде, понятном различным компонентам, включая Ext.grid.GridPanel, а также сохраняет информацию об общем количестве записей и аргументах вызова. Далее вызывается функция loadCallback, которая и сообщит связанным с этим источником данных компонентам, что в него загружены новые данные.
Функция reload просто повторно вызывает функцию load с последними использованными параметрами.
Последнее, что осталось подготовить – это место, где будет отрисовываться таблица. Для этого добавим ASP-панель на страницу products/default.aspx:
<asp:Panel ID="PanelGrig" runat="server" Style="width:100%;" > </asp:Panel>
Теперь приступим к реализации функции SucceededCallback. Для простоты, разобьем ее код на несколько частей:
var storeProducts = new Ext.data.JsonStore({ root: 'Result', remoteSort: true, fields: ['ProductID', 'ProductNumber', 'Name', 'Color', 'ListPrice', 'FullSize', 'Weight'], totalProperty: 'TotalCount', proxy: new Ext.data.FunctionProxy(function(arg, loadCallback) { var query = getQueryVariable(); var query = getQueryVariable(); WebProductService.GetProducts(query.category ? query.category : '', query.subCategory ? query.subCategory : '', arg.first, loadCallback); }) }); storeProducts.load({ params: { start: 0, limit: 10 } });
Здесь мы определяем источник данных storeProducts типа Ext.data.JsonStore, который будем использовать в дальнейшем. При этом указывается, что записи будут храниться в поле Result, а общее количество записей в поле TotalCount возвращаемого веб-сервисом JSON-объекта. Указывается, что сортировка записей проводится на сервере, определяется набор полей, которые будут храниться в источнике данных, и указывается прокси-класс. Здесь мы воспользуемся новым классом Ext.data.FunctionProxy, а передаваемая в него функция, повторяет код анонимной функции, рассмотренной ранее. После этого вызывается функция load источника данных, хотя это не обязательно.
Далее определяется div, в который будет отрисована таблица. Здесь стоит обратить внимание, на то, что в качестве идентификатор при поиске элемента на странице используется не ID ASP-панели, а ее ClientId, которое генерируется ASP.NET автоматически. Данный подход, когда идентификатор "зашит" в код не очень хорош, так как идентификатор может поменяться, если будет изменена сама страница ASP. Но так как у нас всего один ASP-компонент на странице, то этим можно пренебречь:
var el = document.getElementById('ctl00_column_l_placeholder_PanelGrig'); Теперь осталось отобразить саму таблицу: var grid = new Ext.grid.GridPanel({ renderTo: el, stripeRows: true, store: storeProducts, colModel: new Ext.grid.ColumnModel({ defaults: { width: 120, sortable: true }, columns: [ { header: 'Номер продукта', dataIndex: 'ProductNumber' }, { header: 'Название', dataIndex: 'Name' }, { header: 'Цвет', dataIndex: 'Color' }, { header: 'Цена', dataIndex: 'ListPrice' }, { header: 'Размер', dataIndex: 'FullSize' }, { header: 'Вес', dataIndex: 'Weight' } ] }), viewConfig: { forceFit: true }, sm: new Ext.grid.RowSelectionModel({ singleSelect: true }), width: 600, height: 300, frame: true, bbar: new Ext.PagingToolbar( { pageSize: 10, store: storeProducts, listeners: { beforechange: function(sender, e) { sender.store.reload( { params: { start: e.start, limit: e.limit, scope: this } }); return false; } } }) }); }