Цель практического занятия: Целью данного занятия является рассмотрение возможности использования языка
Файлы к практическому занятию Вы можете скачать здесь.
Следующий шаг в развитии сайта заключается в разработке клиентских сценариев, которые сделают интерфейс сайта более интерактивным. Более того, другая возможность, которую нам предоставляют клиентские сценарии – отказ от полной перезагрузки страницы, заменив ее частичной перерисовкой.
Серверные функции
Второй подход – создание клиентского сценария и самостоятельная интеграция его в веб-страницы
Прежде всего, рассмотрим применение технологии form ) необходимо добавить следующий код:
<asp:scriptmanager ID="Scriptmanager1" runat="server"></asp:scriptmanager>
Этот объект будет отвечать за работу
Также добавим еще один ContentPlaceHolder, который будем использовать как контейнер для
<asp:ContentPlaceHolder ID="PlaceHolderScripts" runat="server"> </asp:ContentPlaceHolder>
Теперь в мастере страниц подключим известную (скачать последнюю версию библиотеки и почитать документацию можно по адресу: http://jquery.com/):
<script type="text/javascript" src="JS/jquery-1_2_6_min.js"></script>
Перейдем на главную страницу. Воспользовавшись библиотекой , анимируем основной текст страницы, так чтобы он медленно всплывал, и сделаем так, чтобы надпись "Новинки!" мигала. Для этого добавим в PlaceHolderScripts следующий код:
<asp:Content ID="ContentScript" ContentPlaceHolderID="PlaceHolderScripts" runat="server">
<script type="text/javascript">
jQuery(document).ready(function() {
$('#MainText').toggle(1000);
$('#News').toggleClass('h3').toggle(1000, function() {
animateText();
});
});
function animateText() {
$('#News').toggleClass('h3').toggle(1000, function() {
animateText();
});
}
</script>.
</asp:Content>
Здесь определены две функции. Первая, анонимная функция, сработает в тот момент, когда страница будет сформирована браузером. Так как изначально основному тексту присвоен CSS-класс text-init, который содержит правило display:none, то при загрузке страницы этот текст не отобразится. $('#MainText') найдет элемент с идентификатором MainText, а метод toggle(1000) изменит видимость этого элемента (в данном случае – покажет его), причем процесс отображения текста будет анимирован и займет 1 секунду.
Для элемента с идентификатором News, будет поочередно "включаться" и "выключаться" применение стиля h3, а функция toogle будет скрывать и показывать надпись, создавая эффект мигания. Здесь используется перегрузка функции toggle, которая отработав в течение секунды, вызовет функцию animateText. Эта функция, в свою очередь, снова изменит видимость надписи и вызовет себя.
Результат добавления визуальных эффектов представлен на рис. 15.1.
(рис 15.1) Главная страница сайта с "всплывающим" текстомТеперь перейдем к рассмотрению возможностей технологии , который поддерживается всеми современными браузерами. При это в технологии к серверу называется асинхронным "постбэком" (AsyncPostBack) и так же, как и в случае обычного "постбэка", приводит к восстановлению всей страницы на сервере. Это обеспечивается тем, что содержимое передается на сервер и при асинхронных запросах.
Так как данные при использовании .
При первом рассмотрении сходен в работе с обычным Panel. В результате рендеренга страницы этот компонент также заменяется на HTML-элемент div и служит для группировки и разметки страницы. Особенность же этого компонента заключается в том, что любой запрос, совершенный элементом, расположенным внутри является асинхронным и приводит только к перерисовке его содержимого. Остальная страница не изменяется. Даже если при постбэке пользователь изменит структуру или набор компонент вне , то на этапе Render div' а, соответствующего .
Таким образом, расположив на странице несколько ов (а их можно расположить любое количество, вкладывая, например, один в другой) можно реализовать интерфейс, который никогда не будет перерисовывать страницу и в то же время предоставлять все функциональные возможности сайта.
Иногда бывает не достаточно обновлять только содержимое того а, который вызвал постбэк. Нужно заставить обновиться и другие ы на странице. Это можно сделать двумя способами:
code-behind UpdatePanel указать триггеры ( Triggers ), которые будут приводить к перерисовке содержимого этой панели. В качестве параметров указываются идентификаторы серверных компонент, находящихся вне UpdatePanel , которые должны привести к обновлению его содержимого.code-behind'e можно вызвать метод UpdatePanel .Update(), что приведет к тому, что его содержимое будет ренедрится на сервере и отправляться клиенту.Все это значит, что для внедрения ы и при необходимости указать триггеры:
<asp:UpdatePanel ID="UpdatePanelComments"
runat="server" UpdateMode="Conditional">
<Triggers>
<asp:AsyncPostBackTrigger ControlID="AddReview" />
</Triggers>
<ContentTemplate>
<asp:DataList ID="ReviewList" runat="server" Width="100%"
CellPadding="0" CellSpacing="0">
<ItemTemplate>
<asp:Label ID="Label1" style='font-weight:bold'
Text='<%# DataBinder.Eval(Container.DataItem, "ReviewerName") %>'
runat="server" />
<span class="Normal">говорит... </span>
<asp:Image ID="Image1" runat="server"
ImageUrl='<%# "~/images/ReviewRating" + DataBinder.Eval(Container.DataItem, "Rating") +".gif" %>' />
<br>
<asp:Label ID="Label2" CssClass="Normal"
Text='<%# DataBinder.Eval(Container.DataItem, "Comments") %>'
runat="server" />
</ItemTemplate>
<SeparatorTemplate>
<br>
</SeparatorTemplate>
</asp:DataList>
</ContentTemplate>
</asp:UpdatePanel>
…
<asp:UpdatePanel ID="UpdatePanelNewCommentProperties" runat="server"
UpdateMode="Conditional">
<Triggers><asp:AsyncPostBackTrigger ControlID="AddReview" />
</Triggers>
<ContentTemplate>
…
</ContentTemplate>
</asp:UpdatePanel>
…
<asp:UpdatePanel ID="UpdatePanelCommentButton" runat="server">
<ContentTemplate>
<br>
<span class="NormalBold">Комментарий</span>
<br>
<asp:TextBox ID="Comment" TextMode="multiline"
MaxLength="3850" Rows="7" Width="100%"
runat="server" />
<asp:RequiredFieldValidator ControlToValidate="Comment"
Display="Dynamic" Font-Names="verdana" Font-Size="9pt"
ErrorMessage="Поле 'Комментарий' должно быть заполнено." runat="server"
ID="RequiredFieldValidator3"></asp:RequiredFieldValidator>
<br>
<br>
<asp:Button ID="AddReview" runat="server"
OnClick="OnClick" Text="Добавить свой комментарий" />
<br>
</ContentTemplate>
</asp:UpdatePanel>
Теперь при добавлении нового комментария к продукту, страница не перезагружается, а перерисовываются отдельные области экрана, в чем можно убедиться, запустив сайт.
Теперь, когда мы научились работать с языком , и теперь метод Page.Render отрисовывает не всю странице целиком, а только определенные части. Более того, уменьшился и объем данных, передаваемый от сервера клиенту, опять же за счет того, что передается не вся страница. Второе преимущество, которого нам удалось добиться, заключается в том, что пользователь не наблюдает процесс перезагрузки всей страницы при каждой обратной передаче, что делает пользовательский интерфейс страницы более естественным.
Среди недостатков стоит отметить тот факт, что каждое действие пользователя приводит к отправке на сервер и получение от него большого объема данных в виде . Если же отказаться от использования , то для восстановления страницы на сервере необходимо либо хранить все данные в сессии, либо каждый раз извлекать их из базы данных. Еще один недостаток заключается в том, что при использовании достаточно сложного интерфейса, насыщенного большим количеством сложных компонент, время их рендеринга на сервере сильно возрастает.
Один из подходов к решению этих проблем заключается в использовании
При этом, однако, многократно возрастет нагрузка на клиент. Это может привести к тому, что данный подход станет неприменим в случаях, когда у клиентов слабые компьютеры или на них установлены старые браузеры, плохо поддерживающие
Тем не менее, для сайта, с которыми работает одновременно множество людей, этот подход способен серьезно улучшить производительность приложения.
Для того чтобы добавить
(рис 15.2) Окно добавления нового веб-сервиса в проектВ результате в проект будет добавлено два файла: WebProductService.asmx и WebProductService.cs, причем последний фал будет помещен в директорию App_code.
Чтобы разрешить вызов веб-служб (ASMX) из клиентского сценария на веб-странице . Чтобы определить ссылку на веб-службу, необходимо добавить дочерний элемент asp:ServiceReference к элементу управления . После этого необходимо установить URL-адрес веб-службы в качестве значения атрибута ссылки на сервер path. Объект ServiceReference определяет необходимость создания прокси-класса
Так как в нашем случае определен на мастере страниц, изменим его код следующим образом:
<asp:ScriptManager ID="Scriptmanager1" runat="server">
<Services>
<asp:ServiceReference Path="~/WebProductService.asmx" />
</Services>
</asp:ScriptManager>
Теперь необходимо реализовать код этого сервиса. Для примера сделаем так, чтобы сервис мог возвращать список продуктов с учетом указанной категории и подкатегории и при этом поддерживал постраничный вывод.
Прежде чем приступить к реализации самого сервиса, напишем два вспомогательных класса. Первый класс ProductDTO (
[Serializable]
public class ProductDTO
{
public int ProductID { get; set; }
public string ProductNumber { get; set; }
public string Name { get; set; }
public string Color { get; set; }
public decimal ListPrice { get; set; }
public string FullSize { get; set; }
public string Weight { get; set; }
public ProductDTO(Product p)
{
ProductID = p.ProductID;
ProductNumber = p.ProductNumber;
Name = p.Name;
Color = p.Color;
ListPrice = p.ListPrice;
FullSize = p.FullSize;
Weight = p.Weight + p.WeightUnitMeasureCode;
}
public ProductDTO()
{
}
}
[Serializable] в данном случае необязателен. Он показывает, что объекты этого классы должны поддерживать возможность представляться в виде строки, которую можно передать от одного сервиса к другому так, чтобы получающий сервис смог восстановить передаваемый объект. Сериализовать объект можно в xml -файл, json -объект или в бинарный код.Product не сериализуем. Это связано с тем, что сериализации подвергаются все public свойства, среди которых есть ссылки на объекты, которые также рекурсивно сериализуются. Так, например, Product имеет ссылку ProductSubcategory на свою подкатегорию, которая в свою очередь имеет ссылку Products. В результате при сериализации произойдет зацикливание.Второй класс также будет использоваться для передачи информации клиенту. Он состоит из двух свойств:
Result предназначается для передачи коллекции ProductDTO, которая удовлетворяет текущим критериям поиска;TotalCount содержит общее количество продуктов, которые соответствуют критериям поиска.Этот класс позволит организовать постраничный вывод продуктов. Ниже представлен код этого класса:
public class ResultStructure
{
public object Result{get;set;}
public int TotalCount { get; set; }
}
Теперь, когда все вспомогательные структуры готовы, необходимо определить сервис, который будет обрабатывать запросы:
/// <summary>
/// Summary description for WebProductService
/// </summary>
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
// To allow this Web Service to be called from script, using ASP.NET AJAX, uncomment the following line.
[System.Web.Script.Services.ScriptService]
public class WebProductService : System.Web.Services.WebService {
Этот код будет автоматически добавлен Visual Studio, при создании сервиса. Нам необходимо разкомментировать атрибут ScriptService, так как он позволяет вызвать сервис из
Дальше в самом классе реализуются доступные для вызова веб-методы. Все такие методы помечаются атрибутом WebMethod. В нашем примере мы разработаем только один метод GetProducts, который на вход будет получать текущую категорию, подкатегорию и номер страницы:
[WebMethod]
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
public ResultStructure GetProducts(string category, string subcategory, int skip)
{
DataClassesDataContext dcdc =
new DataClassesDataContext(
"Data Source=localhost;Initial Catalog=AdventureWorks;Integrated Security=True");
var query = from p in dcdc.Products select p;
if (!string.IsNullOrEmpty(subcategory))
{
query = query.Where(p => p.ProductSubcategoryID == Convert.ToInt32(subcategory));
}
else
{
if (!string.IsNullOrEmpty(category))
{
query = query.Where(p => p.ProductSubcategory.ProductCategoryID == Convert.ToInt32(category));
}
}
return new ResultStructure()
{
Result = query.Skip(skip).Take(10).Select(p => new ProductDTO(p)).ToList(),
TotalCount = query.Count()
};
}
Код веб-метода идентичен коду, который разрабатывался на предыдущих занятиях для заполнения GridView данными о продуктах, только теперь метод не привязывает данные к какому-либо серверному компоненту, а возвращает сериализованные данные состоящие из коллекции продуктов и их общего количества. В атрибуте ScriptMethod указан параметр ResponseFormat = ResponseFormat.Json, который указывает, что результат необходимо сериализовать в формате JSON. Мы выбрали этот формат, так как он является основным формат представления данных для языка
Если вызвать наш сервис и передать ему параметры, то будет получен ответ, аналогичный приведенному:
{"d":{"__type":"ResultStructure",
"Result":
[{"ProductID":982,"ProductNumber":"BK-M38S-42",
"Name":"Mountain-400-W Silver, 42",
"Color":"Silver","ListPrice":769.4900,
"FullSize":"42 CM ","Weight":"27,13LB "},
{"ProductID":983,"ProductNumber":"BK-M38S-46",
"Name":"Mountain-400-W Silver, 46",
"Color":"Silver","ListPrice":769.4900,
"FullSize":"46 CM ","Weight":"27,42LB "},
{"ProductID":984,"ProductNumber":"BK-M18S-40",
"Name":"Mountain-500 Silver, 40",
"Color":"Silver","ListPrice":564.9900,
"FullSize":"40 CM ","Weight":"27,35LB"},
{"ProductID":985,"ProductNumber":"BK-M18S-42",
"Name":"Mountain-500 Silver, 42",
"Color":"Silver","ListPrice":564.9900,
"FullSize":"42 CM ","Weight":"27,77LB "},
{"ProductID":986,"ProductNumber":"BK-M18S-44",
"Name":"Mountain-500 Silver, 44",
"Color":"Silver","ListPrice":564.9900,
"FullSize":"44 CM ","Weight":"28,13LB "},
{"ProductID":987,"ProductNumber":"BK-M18S-48",
"Name":"Mountain-500 Silver, 48",
"Color":"Silver","ListPrice":564.9900,
"FullSize":"48 CM ","Weight":"28,42LB "},
{"ProductID":988,"ProductNumber":"BK-M18S-52",
"Name":"Mountain-500 Silver,52",
"Color":"Silver","ListPrice":564.9900,
"FullSize":"52 CM ","Weight":"28,68LB "},
{"ProductID":989,"ProductNumber":"BK-M18B-40",
"Name":"Mountain-500 Black, 40",
"Color":"Black","ListPrice":539.9900,
"FullSize":"40 CM ","Weight":"27,35LB "},
{"ProductID":990,"ProductNumber":"BK-M18B-42",
"Name":"Mountain-500 Black, 42",
"Color":"Black","ListPrice":539.9900,
"FullSize":"42 CM ","Weight":"27,77LB "},
{"ProductID":991,"ProductNumber":"BK-M18B-44",
"Name":"Mountain-500 Black, 44",
"Color":"Black","ListPrice":539.9900,
"FullSize":"44 CM ","Weight":"28,13LB "}],
"TotalCount":32}}
При этом для сервиса будут доступны страницы, на которых будут представлены описание методов, которые предоставляет сервис, а также тестовые страницы, на которых можно вызвать метод, передав параметры. Впрочем, в случае использования JSON-формата данных, вызвать методы не получится, так как это приведет к ошибке. Примеры таких страниц представлены на рис. 15.3 и рис. 15.4.
(рис 15.3) Страница с описанием сервиса
(рис 15.4) Тестовая страница для операции GetProduct сервиса WebProductService
Теперь, когда products/default.aspx, сделав так, чтобы она отображала данные, которые будет возвращать
Подключим библиотеку 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=123b=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 функции. Первая функция является анонимной, и выполняется в тот момент, когда библиотека getQueryVariable, которая позволит определить, какие переменные определены в строке запроса.
Функция getQueryVariable обращается к объекту window.location, который содержит всю строку запроса, после чего обращается в свойству search, содержащему строку параметров в формате ?a=123b=qwe. Далее у этой строки удаляется первый символ, и она методом split разделяется на множество строк, разделенных знаком . После этого, каждая полученная строка также разделяется на две, содержащие имя параметра и его значение, и записываются в массив arr, который в данном случае выступает в качестве хэш-таблицы.
Результат вызова функции getQueryVariable помещается в переменную query.
Далее вызывается функция WebProductService.GetProducts, которая представляет собой сгенерированную ом прокси-функцию, вызывающую одноименный веб-метод. При этом WebProductService.GetProducts помимо трех параметров, необходимых для работы, передаются также два метода обратного вызова. Метод FailedCallback будет вызван в случае, если во время выполнения веб-метода произойдет ошибка, и отобразит текст этой ошибки пользователю. Метод SucceededCallback выполнится в случае, если веб-метод успешно завершит свое выполнение. В этом случае переменная result будет содержать результат работы веб-метода.
Уже сейчас можно запустить наш сайт и, если открыть IE в метод SucceededCallback, можно убедиться, что result (рис. 15.5).
(рис 15.5) Переменная result в IE Developer ToolsОднако данные будут бесполезны, если пользователь их не увидит. Для того чтобы отобразить данные, воспользуемся клиентским компонентом Ext.grid.GridPanel, который предоставляет во многом схожую функциональность с той, что дает GridView, но позволяет выполнять большинство действий без взаимодействия с сервером, не перерисовывая страницу.
Как и для 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);
}
})
При разработке этого класса используется объектно-ориентированный подход программирования на Ext.data.FunctionProxy, который принимает на вход переменную-функцию f и который вызывает конструктор базового класса. При этом переменная f сохраняется в поле func текущего объекта.
Далее определяется, что класс Ext.data.FunctionProxy является расширением класса Ext.data.DataProxy, у которого переопределены методы load и . Это делается при помощи статического метода Ext.extend.
Ext в данном случае позволяет создать подход к программированию, соответствующий традициям ООП. Те же действия можно выполнить, не привлекая объектно-ориентированный код стандартными возможностями Функция load вызывает функцию func, передавая параметры в виде объекта arg, поля которого содежат информацию о номере первой извлекаемой запись, количестве записей, направлении сортировки, области видимости, полном количестве записей и наборе извлекаемых полей. Второй передаваемый в функцию func параметр представляет собой SucceededCallback ). Эта функция применяет JsonReader для разбора записей и представления их в виде, понятном различным компонентам, включая Ext.grid.GridPanel, а также сохраняет информацию об общем количестве записей и аргументах вызова. Далее вызывается функция loadCallback, которая и сообщит связанным с этим источником данных компонентам, что в него загружены новые данные.
Функция просто повторно вызывает функцию 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 возвращаемого Ext.data.FunctionProxy, а передаваемая в него функция, повторяет код анонимной функции, рассмотренной ранее. После этого вызывается функция load источника данных, хотя это не обязательно.
Далее определяется div, в который будет отрисована таблица. Здесь стоит обратить внимание, на то, что в качестве идентификатор при поиске элемента на странице используется не ID ASP-панели, а ее ClientId, которое генерируется
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;
}
}
})
});
}
Отметим только основные моменты этого кода. Компоненту указывается источник данных storeProducts, а также элемент el, в который этот компонент должен быть отрисован. Также указана модель столбцов, в которой определены все колонки, их заголовки и связь с полем источника данных. Также для нижней панели таблицы ( bbar ) определен компонент Ext.PagingToolbar, который обеспечивает возможность переключения страниц. Для этого в параметрах конструктора указан тот же источник данных, что и у таблицы, определен размер страницы, а в поле listeners определен обработчик клиентского события beforechange, в котором у источника данных вызывается метод , но уже для новой страницы (указывается новый стартовый элемент). Строчка return false ; позволяет избежать вызова других обработчиков, прикрепленных к событию beforechange пэйджера.
Теперь все готово, и мы можем запустить наше веб-приложение и убедиться, что все работает. Примеры получившихся страниц представлены на рис. 15.6, рис. 15.7 и рис. 15.8.
(рис 15.6) Первая страница списка всех продуктов
(рис 15.7) 34-ая страница списка всех продуктов
(рис 15.8) Список всех горных велосипедовРассмотренный подход, обладает множеством преимуществ, однако также необходимо указать и недостатки такого подхода. Прежде всего, отметим, что теряется возможность визуально верстать страницу, так как весь код генерируется
Теперь, когда наш проект содержит большое количество
(рис 15.7) Отладка в Developer ToolsКонсоль
В ходе процесса возможна установка контрольных точек автоматической остановки выполнения – для этого нужно выделить нужное место в коде скрипта, после чего нажать на кнопку F9. Есть также и запуск отладчика с пропуском ошибок исполнения (во избежание остановок) – для этого можно или щелкнуть по кнопке "Остановка при ошибке" или же продублировать это действие командой Ctrl+Shift+E. Кроме того, в случае, если установленная контрольная точка становится неактуальной (код на странице поменялся, указан неправильный путь до сценария и так далее), то в отладчике можно задать условия, при которых будет выполняться остановка (устанавливается в свойствах точки остановки). Наконец, в случае, когда выполнение сценария приостановлено, пользователь может вручную продолжить работу отладчика (кнопка F5), остановить процесс (Ctrl+Shift+B), перейти на следующий этап (F11) или предыдущий шаг (F10), повторить последний цикл скрипта до текущего состояния (Shift+F11).
Любая ошибка во время выполнения приводит к тому, что отладчик автоматически останавливается в месте возникновения ошибки. Windows Internet Explorer остановится и выделит строку, вызвавшую ошибку, в окне просмотра исходного кода.
Выбрать место прерывания выполнения можно при помощи установки точки останова. При наличии точки останова исполнение сценария приостанавливается на строчке, находящейся непосредственно перед точкой останова. В окне просмотра исходного кода подсвечивается следующая исполняемая строчка. Точки останова можно устанавливать и после того, как запущен отладчик. Для этого нужно или щелкнуть рядом с номером строчки кода, или вызвать контекстное меню, или нажать клавишу F9.
На вкладке Breakpoints изображен список всех доступных точек останова. На рис. 15.8 показана вкладка Breakpoints, выбранная в правой части панели отладчика.
(рис 15.8) Вкладка "Точки останова" в Developer ToolsВсе точки останова вместе с именами файлов и номерами строк представлены здесь. Дважды щелкнув точку останова в этом списке, можно перейти к исходному коду, в котором она установлена. Сняв флажок рядом с точкой останова, ее можно временно деактивировать, не удаляя из исходного кода. Чтобы удалить точку останова, щелкните ее правой кнопкой мыши и выберите Delete (Удалить).
В любой момент, когда исполнение сценария приостановлено точкой останова, можно просмотреть значения переменных. С помощью средства Locals отладчика можно вывести имена, значения и типы всех переменных, существующих в текущей области видимости сценария. Значения переменных вне области видимости не определены. Область видимости – это интервал, в котором можно обращаться к некоторой переменной. На рис. 15.9 показан вид средства Locals во время отладки.
(рис 15.9) Средства "Локальные" в Developer ToolsС помощью средства Watch можно просматривать переменные из разных областей видимости. Чтобы включить просмотр переменной, нужно найти ее в исходном тексте сценария, щелкнуть правой кнопкой мыши и выбрать Add Watch. Таким образом, будет добавлена точка наблюдения для идентификатора, на котором находился курсор. На рис. 15.10 изображено средство Watch с наблюдаемой переменной.
(рис 15.10) Средства "Смотреть" в Developer ToolsЕще один способ добавить точку наблюдения переменной – щелкнуть текст "Click to add…" в окне Watch и ввести имя переменной.
Заметим, что функция профилирования
Благодаря профилированию сценариев можно улучшить работу веб-сайта посредством идентификации и исправления связанных с производительностью проблем в коде
Режим Profiler позволяет собрать информацию о временных характеристиках работы веб-узла, которая собирается по мере визуализации страниц веб-узла обозревателем Windows Internet Explorer. Данная информация помогает оптимизировать участки кода, на исполнение которых уходит слишком много времени, то есть узкие места.
Чтобы приступить к профилированию, необходимо открыть вкладку "Профили" и начать сеанс профилирования сценария нажатием кнопки "Запуск создания профилей". При этом обработчик сценариев переключается в режим профилирования, и на кнопке появляется текст "Остановка создания профилей". Выполнив сценарий, который необходимо профилировать на веб-странице, после чего щелкните "Остановка создания профилей" для завершения сеанса. Автоматически отображается отчет о только что созданном профиле.
На рис. 15.11 показан основной пользовательский интерфейс вкладки "Профили".
(рис 15.11) Вкладка "Профили" в Developer ToolsОтчеты о профилях можно просматривать в представлении "Функции" или "Дерево вызовов", которое можно выбрать из раскрывающегося списка "Текущее представление".
В отчете показаны функции, использовавшиеся обозревателем Windows Internet Explorer для визуализации URL-адреса. Выводится название функции, число ее вызовов, включительное и исключительно время. Включительно время – это время, затраченное на исполнение самой функции и всех функций, из нее вызванных. Исключительное время – это время, затраченное на исполнение самой функции без учета функций, из нее вызванных.
С помощью собранной профилировщиком информации легко найти узкие места в коде сайта. Если обнаружить и изменить структуру неэффективного кода или медленных алгоритмов, то можно сократить время, которое обозреватель Windows Internet Explorer тратит на визуализацию веб-страниц.
Данные профиля можно экспортировать из текущего отчета в CSV-файл.
Данное практическое занятие рассматривает следующие темы:
Цель практического занятия: Целью данного занятия является рассмотрение возможности использования языка
Файлы к практическому занятию Вы можете скачать здесь.
Следующий шаг в развитии сайта заключается в разработке клиентских сценариев, которые сделают интерфейс сайта более интерактивным. Более того, другая возможность, которую нам предоставляют клиентские сценарии – отказ от полной перезагрузки страницы, заменив ее частичной перерисовкой.
Серверные функции
Второй подход – создание клиентского сценария и самостоятельная интеграция его в веб-страницы
Прежде всего, рассмотрим применение технологии form ) необходимо добавить следующий код:
<asp:scriptmanager ID="Scriptmanager1" runat="server"></asp:scriptmanager>
Этот объект будет отвечать за работу
Также добавим еще один ContentPlaceHolder, который будем использовать как контейнер для
<asp:ContentPlaceHolder ID="PlaceHolderScripts" runat="server"> </asp:ContentPlaceHolder>
Теперь в мастере страниц подключим известную (скачать последнюю версию библиотеки и почитать документацию можно по адресу: http://jquery.com/):
<script type="text/javascript" src="JS/jquery-1_2_6_min.js"></script>
Перейдем на главную страницу. Воспользовавшись библиотекой , анимируем основной текст страницы, так чтобы он медленно всплывал, и сделаем так, чтобы надпись "Новинки!" мигала. Для этого добавим в PlaceHolderScripts следующий код:
<asp:Content ID="ContentScript" ContentPlaceHolderID="PlaceHolderScripts" runat="server">
<script type="text/javascript">
jQuery(document).ready(function() {
$('#MainText').toggle(1000);
$('#News').toggleClass('h3').toggle(1000, function() {
animateText();
});
});
function animateText() {
$('#News').toggleClass('h3').toggle(1000, function() {
animateText();
});
}
</script>.
</asp:Content>
Здесь определены две функции. Первая, анонимная функция, сработает в тот момент, когда страница будет сформирована браузером. Так как изначально основному тексту присвоен CSS-класс text-init, который содержит правило display:none, то при загрузке страницы этот текст не отобразится. $('#MainText') найдет элемент с идентификатором MainText, а метод toggle(1000) изменит видимость этого элемента (в данном случае – покажет его), причем процесс отображения текста будет анимирован и займет 1 секунду.
Для элемента с идентификатором News, будет поочередно "включаться" и "выключаться" применение стиля h3, а функция toogle будет скрывать и показывать надпись, создавая эффект мигания. Здесь используется перегрузка функции toggle, которая отработав в течение секунды, вызовет функцию animateText. Эта функция, в свою очередь, снова изменит видимость надписи и вызовет себя.
Результат добавления визуальных эффектов представлен на рис. 15.1.
(рис 15.1) Главная страница сайта с "всплывающим" текстомТеперь перейдем к рассмотрению возможностей технологии , который поддерживается всеми современными браузерами. При это в технологии к серверу называется асинхронным "постбэком" (AsyncPostBack) и так же, как и в случае обычного "постбэка", приводит к восстановлению всей страницы на сервере. Это обеспечивается тем, что содержимое передается на сервер и при асинхронных запросах.
Так как данные при использовании .
При первом рассмотрении сходен в работе с обычным Panel. В результате рендеренга страницы этот компонент также заменяется на HTML-элемент div и служит для группировки и разметки страницы. Особенность же этого компонента заключается в том, что любой запрос, совершенный элементом, расположенным внутри является асинхронным и приводит только к перерисовке его содержимого. Остальная страница не изменяется. Даже если при постбэке пользователь изменит структуру или набор компонент вне , то на этапе Render div' а, соответствующего .
Таким образом, расположив на странице несколько ов (а их можно расположить любое количество, вкладывая, например, один в другой) можно реализовать интерфейс, который никогда не будет перерисовывать страницу и в то же время предоставлять все функциональные возможности сайта.
Иногда бывает не достаточно обновлять только содержимое того а, который вызвал постбэк. Нужно заставить обновиться и другие ы на странице. Это можно сделать двумя способами:
code-behind UpdatePanel указать триггеры ( Triggers ), которые будут приводить к перерисовке содержимого этой панели. В качестве параметров указываются идентификаторы серверных компонент, находящихся вне UpdatePanel , которые должны привести к обновлению его содержимого.code-behind'e можно вызвать метод UpdatePanel .Update(), что приведет к тому, что его содержимое будет ренедрится на сервере и отправляться клиенту.Все это значит, что для внедрения ы и при необходимости указать триггеры:
<asp:UpdatePanel ID="UpdatePanelComments"
runat="server" UpdateMode="Conditional">
<Triggers>
<asp:AsyncPostBackTrigger ControlID="AddReview" />
</Triggers>
<ContentTemplate>
<asp:DataList ID="ReviewList" runat="server" Width="100%"
CellPadding="0" CellSpacing="0">
<ItemTemplate>
<asp:Label ID="Label1" style='font-weight:bold'
Text='<%# DataBinder.Eval(Container.DataItem, "ReviewerName") %>'
runat="server" />
<span class="Normal">говорит... </span>
<asp:Image ID="Image1" runat="server"
ImageUrl='<%# "~/images/ReviewRating" + DataBinder.Eval(Container.DataItem, "Rating") +".gif" %>' />
<br>
<asp:Label ID="Label2" CssClass="Normal"
Text='<%# DataBinder.Eval(Container.DataItem, "Comments") %>'
runat="server" />
</ItemTemplate>
<SeparatorTemplate>
<br>
</SeparatorTemplate>
</asp:DataList>
</ContentTemplate>
</asp:UpdatePanel>
…
<asp:UpdatePanel ID="UpdatePanelNewCommentProperties" runat="server"
UpdateMode="Conditional">
<Triggers><asp:AsyncPostBackTrigger ControlID="AddReview" />
</Triggers>
<ContentTemplate>
…
</ContentTemplate>
</asp:UpdatePanel>
…
<asp:UpdatePanel ID="UpdatePanelCommentButton" runat="server">
<ContentTemplate>
<br>
<span class="NormalBold">Комментарий</span>
<br>
<asp:TextBox ID="Comment" TextMode="multiline"
MaxLength="3850" Rows="7" Width="100%"
runat="server" />
<asp:RequiredFieldValidator ControlToValidate="Comment"
Display="Dynamic" Font-Names="verdana" Font-Size="9pt"
ErrorMessage="Поле 'Комментарий' должно быть заполнено." runat="server"
ID="RequiredFieldValidator3"></asp:RequiredFieldValidator>
<br>
<br>
<asp:Button ID="AddReview" runat="server"
OnClick="OnClick" Text="Добавить свой комментарий" />
<br>
</ContentTemplate>
</asp:UpdatePanel>
Теперь при добавлении нового комментария к продукту, страница не перезагружается, а перерисовываются отдельные области экрана, в чем можно убедиться, запустив сайт.
Теперь, когда мы научились работать с языком , и теперь метод Page.Render отрисовывает не всю странице целиком, а только определенные части. Более того, уменьшился и объем данных, передаваемый от сервера клиенту, опять же за счет того, что передается не вся страница. Второе преимущество, которого нам удалось добиться, заключается в том, что пользователь не наблюдает процесс перезагрузки всей страницы при каждой обратной передаче, что делает пользовательский интерфейс страницы более естественным.
Среди недостатков стоит отметить тот факт, что каждое действие пользователя приводит к отправке на сервер и получение от него большого объема данных в виде . Если же отказаться от использования , то для восстановления страницы на сервере необходимо либо хранить все данные в сессии, либо каждый раз извлекать их из базы данных. Еще один недостаток заключается в том, что при использовании достаточно сложного интерфейса, насыщенного большим количеством сложных компонент, время их рендеринга на сервере сильно возрастает.
Один из подходов к решению этих проблем заключается в использовании
При этом, однако, многократно возрастет нагрузка на клиент. Это может привести к тому, что данный подход станет неприменим в случаях, когда у клиентов слабые компьютеры или на них установлены старые браузеры, плохо поддерживающие
Тем не менее, для сайта, с которыми работает одновременно множество людей, этот подход способен серьезно улучшить производительность приложения.
Для того чтобы добавить
(рис 15.2) Окно добавления нового веб-сервиса в проектВ результате в проект будет добавлено два файла: WebProductService.asmx и WebProductService.cs, причем последний фал будет помещен в директорию App_code.
Чтобы разрешить вызов веб-служб (ASMX) из клиентского сценария на веб-странице . Чтобы определить ссылку на веб-службу, необходимо добавить дочерний элемент asp:ServiceReference к элементу управления . После этого необходимо установить URL-адрес веб-службы в качестве значения атрибута ссылки на сервер path. Объект ServiceReference определяет необходимость создания прокси-класса
Так как в нашем случае определен на мастере страниц, изменим его код следующим образом:
<asp:ScriptManager ID="Scriptmanager1" runat="server">
<Services>
<asp:ServiceReference Path="~/WebProductService.asmx" />
</Services>
</asp:ScriptManager>
Теперь необходимо реализовать код этого сервиса. Для примера сделаем так, чтобы сервис мог возвращать список продуктов с учетом указанной категории и подкатегории и при этом поддерживал постраничный вывод.
Прежде чем приступить к реализации самого сервиса, напишем два вспомогательных класса. Первый класс ProductDTO (
[Serializable]
public class ProductDTO
{
public int ProductID { get; set; }
public string ProductNumber { get; set; }
public string Name { get; set; }
public string Color { get; set; }
public decimal ListPrice { get; set; }
public string FullSize { get; set; }
public string Weight { get; set; }
public ProductDTO(Product p)
{
ProductID = p.ProductID;
ProductNumber = p.ProductNumber;
Name = p.Name;
Color = p.Color;
ListPrice = p.ListPrice;
FullSize = p.FullSize;
Weight = p.Weight + p.WeightUnitMeasureCode;
}
public ProductDTO()
{
}
}
[Serializable] в данном случае необязателен. Он показывает, что объекты этого классы должны поддерживать возможность представляться в виде строки, которую можно передать от одного сервиса к другому так, чтобы получающий сервис смог восстановить передаваемый объект. Сериализовать объект можно в xml -файл, json -объект или в бинарный код.Product не сериализуем. Это связано с тем, что сериализации подвергаются все public свойства, среди которых есть ссылки на объекты, которые также рекурсивно сериализуются. Так, например, Product имеет ссылку ProductSubcategory на свою подкатегорию, которая в свою очередь имеет ссылку Products. В результате при сериализации произойдет зацикливание.Второй класс также будет использоваться для передачи информации клиенту. Он состоит из двух свойств:
Result предназначается для передачи коллекции ProductDTO, которая удовлетворяет текущим критериям поиска;TotalCount содержит общее количество продуктов, которые соответствуют критериям поиска.Этот класс позволит организовать постраничный вывод продуктов. Ниже представлен код этого класса:
public class ResultStructure
{
public object Result{get;set;}
public int TotalCount { get; set; }
}
Теперь, когда все вспомогательные структуры готовы, необходимо определить сервис, который будет обрабатывать запросы:
/// <summary>
/// Summary description for WebProductService
/// </summary>
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
// To allow this Web Service to be called from script, using ASP.NET AJAX, uncomment the following line.
[System.Web.Script.Services.ScriptService]
public class WebProductService : System.Web.Services.WebService {
Этот код будет автоматически добавлен Visual Studio, при создании сервиса. Нам необходимо разкомментировать атрибут ScriptService, так как он позволяет вызвать сервис из
Дальше в самом классе реализуются доступные для вызова веб-методы. Все такие методы помечаются атрибутом WebMethod. В нашем примере мы разработаем только один метод GetProducts, который на вход будет получать текущую категорию, подкатегорию и номер страницы:
[WebMethod]
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
public ResultStructure GetProducts(string category, string subcategory, int skip)
{
DataClassesDataContext dcdc =
new DataClassesDataContext(
"Data Source=localhost;Initial Catalog=AdventureWorks;Integrated Security=True");
var query = from p in dcdc.Products select p;
if (!string.IsNullOrEmpty(subcategory))
{
query = query.Where(p => p.ProductSubcategoryID == Convert.ToInt32(subcategory));
}
else
{
if (!string.IsNullOrEmpty(category))
{
query = query.Where(p => p.ProductSubcategory.ProductCategoryID == Convert.ToInt32(category));
}
}
return new ResultStructure()
{
Result = query.Skip(skip).Take(10).Select(p => new ProductDTO(p)).ToList(),
TotalCount = query.Count()
};
}
Код веб-метода идентичен коду, который разрабатывался на предыдущих занятиях для заполнения GridView данными о продуктах, только теперь метод не привязывает данные к какому-либо серверному компоненту, а возвращает сериализованные данные состоящие из коллекции продуктов и их общего количества. В атрибуте ScriptMethod указан параметр ResponseFormat = ResponseFormat.Json, который указывает, что результат необходимо сериализовать в формате JSON. Мы выбрали этот формат, так как он является основным формат представления данных для языка
Если вызвать наш сервис и передать ему параметры, то будет получен ответ, аналогичный приведенному:
{"d":{"__type":"ResultStructure",
"Result":
[{"ProductID":982,"ProductNumber":"BK-M38S-42",
"Name":"Mountain-400-W Silver, 42",
"Color":"Silver","ListPrice":769.4900,
"FullSize":"42 CM ","Weight":"27,13LB "},
{"ProductID":983,"ProductNumber":"BK-M38S-46",
"Name":"Mountain-400-W Silver, 46",
"Color":"Silver","ListPrice":769.4900,
"FullSize":"46 CM ","Weight":"27,42LB "},
{"ProductID":984,"ProductNumber":"BK-M18S-40",
"Name":"Mountain-500 Silver, 40",
"Color":"Silver","ListPrice":564.9900,
"FullSize":"40 CM ","Weight":"27,35LB"},
{"ProductID":985,"ProductNumber":"BK-M18S-42",
"Name":"Mountain-500 Silver, 42",
"Color":"Silver","ListPrice":564.9900,
"FullSize":"42 CM ","Weight":"27,77LB "},
{"ProductID":986,"ProductNumber":"BK-M18S-44",
"Name":"Mountain-500 Silver, 44",
"Color":"Silver","ListPrice":564.9900,
"FullSize":"44 CM ","Weight":"28,13LB "},
{"ProductID":987,"ProductNumber":"BK-M18S-48",
"Name":"Mountain-500 Silver, 48",
"Color":"Silver","ListPrice":564.9900,
"FullSize":"48 CM ","Weight":"28,42LB "},
{"ProductID":988,"ProductNumber":"BK-M18S-52",
"Name":"Mountain-500 Silver,52",
"Color":"Silver","ListPrice":564.9900,
"FullSize":"52 CM ","Weight":"28,68LB "},
{"ProductID":989,"ProductNumber":"BK-M18B-40",
"Name":"Mountain-500 Black, 40",
"Color":"Black","ListPrice":539.9900,
"FullSize":"40 CM ","Weight":"27,35LB "},
{"ProductID":990,"ProductNumber":"BK-M18B-42",
"Name":"Mountain-500 Black, 42",
"Color":"Black","ListPrice":539.9900,
"FullSize":"42 CM ","Weight":"27,77LB "},
{"ProductID":991,"ProductNumber":"BK-M18B-44",
"Name":"Mountain-500 Black, 44",
"Color":"Black","ListPrice":539.9900,
"FullSize":"44 CM ","Weight":"28,13LB "}],
"TotalCount":32}}
При этом для сервиса будут доступны страницы, на которых будут представлены описание методов, которые предоставляет сервис, а также тестовые страницы, на которых можно вызвать метод, передав параметры. Впрочем, в случае использования JSON-формата данных, вызвать методы не получится, так как это приведет к ошибке. Примеры таких страниц представлены на рис. 15.3 и рис. 15.4.
(рис 15.3) Страница с описанием сервиса
(рис 15.4) Тестовая страница для операции GetProduct сервиса WebProductService
Теперь, когда products/default.aspx, сделав так, чтобы она отображала данные, которые будет возвращать
Подключим библиотеку 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=123b=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 функции. Первая функция является анонимной, и выполняется в тот момент, когда библиотека getQueryVariable, которая позволит определить, какие переменные определены в строке запроса.
Функция getQueryVariable обращается к объекту window.location, который содержит всю строку запроса, после чего обращается в свойству search, содержащему строку параметров в формате ?a=123b=qwe. Далее у этой строки удаляется первый символ, и она методом split разделяется на множество строк, разделенных знаком . После этого, каждая полученная строка также разделяется на две, содержащие имя параметра и его значение, и записываются в массив arr, который в данном случае выступает в качестве хэш-таблицы.
Результат вызова функции getQueryVariable помещается в переменную query.
Далее вызывается функция WebProductService.GetProducts, которая представляет собой сгенерированную ом прокси-функцию, вызывающую одноименный веб-метод. При этом WebProductService.GetProducts помимо трех параметров, необходимых для работы, передаются также два метода обратного вызова. Метод FailedCallback будет вызван в случае, если во время выполнения веб-метода произойдет ошибка, и отобразит текст этой ошибки пользователю. Метод SucceededCallback выполнится в случае, если веб-метод успешно завершит свое выполнение. В этом случае переменная result будет содержать результат работы веб-метода.
Уже сейчас можно запустить наш сайт и, если открыть IE в метод SucceededCallback, можно убедиться, что result (рис. 15.5).
(рис 15.5) Переменная result в IE Developer ToolsОднако данные будут бесполезны, если пользователь их не увидит. Для того чтобы отобразить данные, воспользуемся клиентским компонентом Ext.grid.GridPanel, который предоставляет во многом схожую функциональность с той, что дает GridView, но позволяет выполнять большинство действий без взаимодействия с сервером, не перерисовывая страницу.
Как и для 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);
}
})
При разработке этого класса используется объектно-ориентированный подход программирования на Ext.data.FunctionProxy, который принимает на вход переменную-функцию f и который вызывает конструктор базового класса. При этом переменная f сохраняется в поле func текущего объекта.
Далее определяется, что класс Ext.data.FunctionProxy является расширением класса Ext.data.DataProxy, у которого переопределены методы load и . Это делается при помощи статического метода Ext.extend.
Ext в данном случае позволяет создать подход к программированию, соответствующий традициям ООП. Те же действия можно выполнить, не привлекая объектно-ориентированный код стандартными возможностями Функция load вызывает функцию func, передавая параметры в виде объекта arg, поля которого содежат информацию о номере первой извлекаемой запись, количестве записей, направлении сортировки, области видимости, полном количестве записей и наборе извлекаемых полей. Второй передаваемый в функцию func параметр представляет собой SucceededCallback ). Эта функция применяет JsonReader для разбора записей и представления их в виде, понятном различным компонентам, включая Ext.grid.GridPanel, а также сохраняет информацию об общем количестве записей и аргументах вызова. Далее вызывается функция loadCallback, которая и сообщит связанным с этим источником данных компонентам, что в него загружены новые данные.
Функция просто повторно вызывает функцию 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 возвращаемого Ext.data.FunctionProxy, а передаваемая в него функция, повторяет код анонимной функции, рассмотренной ранее. После этого вызывается функция load источника данных, хотя это не обязательно.
Далее определяется div, в который будет отрисована таблица. Здесь стоит обратить внимание, на то, что в качестве идентификатор при поиске элемента на странице используется не ID ASP-панели, а ее ClientId, которое генерируется
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;
}
}
})
});
}
Отметим только основные моменты этого кода. Компоненту указывается источник данных storeProducts, а также элемент el, в который этот компонент должен быть отрисован. Также указана модель столбцов, в которой определены все колонки, их заголовки и связь с полем источника данных. Также для нижней панели таблицы ( bbar ) определен компонент Ext.PagingToolbar, который обеспечивает возможность переключения страниц. Для этого в параметрах конструктора указан тот же источник данных, что и у таблицы, определен размер страницы, а в поле listeners определен обработчик клиентского события beforechange, в котором у источника данных вызывается метод , но уже для новой страницы (указывается новый стартовый элемент). Строчка return false ; позволяет избежать вызова других обработчиков, прикрепленных к событию beforechange пэйджера.
Теперь все готово, и мы можем запустить наше веб-приложение и убедиться, что все работает. Примеры получившихся страниц представлены на рис. 15.6, рис. 15.7 и рис. 15.8.
(рис 15.6) Первая страница списка всех продуктов
(рис 15.7) 34-ая страница списка всех продуктов
(рис 15.8) Список всех горных велосипедовРассмотренный подход, обладает множеством преимуществ, однако также необходимо указать и недостатки такого подхода. Прежде всего, отметим, что теряется возможность визуально верстать страницу, так как весь код генерируется
Теперь, когда наш проект содержит большое количество
(рис 15.7) Отладка в Developer ToolsКонсоль
В ходе процесса возможна установка контрольных точек автоматической остановки выполнения – для этого нужно выделить нужное место в коде скрипта, после чего нажать на кнопку F9. Есть также и запуск отладчика с пропуском ошибок исполнения (во избежание остановок) – для этого можно или щелкнуть по кнопке "Остановка при ошибке" или же продублировать это действие командой Ctrl+Shift+E. Кроме того, в случае, если установленная контрольная точка становится неактуальной (код на странице поменялся, указан неправильный путь до сценария и так далее), то в отладчике можно задать условия, при которых будет выполняться остановка (устанавливается в свойствах точки остановки). Наконец, в случае, когда выполнение сценария приостановлено, пользователь может вручную продолжить работу отладчика (кнопка F5), остановить процесс (Ctrl+Shift+B), перейти на следующий этап (F11) или предыдущий шаг (F10), повторить последний цикл скрипта до текущего состояния (Shift+F11).
Любая ошибка во время выполнения приводит к тому, что отладчик автоматически останавливается в месте возникновения ошибки. Windows Internet Explorer остановится и выделит строку, вызвавшую ошибку, в окне просмотра исходного кода.
Выбрать место прерывания выполнения можно при помощи установки точки останова. При наличии точки останова исполнение сценария приостанавливается на строчке, находящейся непосредственно перед точкой останова. В окне просмотра исходного кода подсвечивается следующая исполняемая строчка. Точки останова можно устанавливать и после того, как запущен отладчик. Для этого нужно или щелкнуть рядом с номером строчки кода, или вызвать контекстное меню, или нажать клавишу F9.
На вкладке Breakpoints изображен список всех доступных точек останова. На рис. 15.8 показана вкладка Breakpoints, выбранная в правой части панели отладчика.
(рис 15.8) Вкладка "Точки останова" в Developer ToolsВсе точки останова вместе с именами файлов и номерами строк представлены здесь. Дважды щелкнув точку останова в этом списке, можно перейти к исходному коду, в котором она установлена. Сняв флажок рядом с точкой останова, ее можно временно деактивировать, не удаляя из исходного кода. Чтобы удалить точку останова, щелкните ее правой кнопкой мыши и выберите Delete (Удалить).
В любой момент, когда исполнение сценария приостановлено точкой останова, можно просмотреть значения переменных. С помощью средства Locals отладчика можно вывести имена, значения и типы всех переменных, существующих в текущей области видимости сценария. Значения переменных вне области видимости не определены. Область видимости – это интервал, в котором можно обращаться к некоторой переменной. На рис. 15.9 показан вид средства Locals во время отладки.
(рис 15.9) Средства "Локальные" в Developer ToolsС помощью средства Watch можно просматривать переменные из разных областей видимости. Чтобы включить просмотр переменной, нужно найти ее в исходном тексте сценария, щелкнуть правой кнопкой мыши и выбрать Add Watch. Таким образом, будет добавлена точка наблюдения для идентификатора, на котором находился курсор. На рис. 15.10 изображено средство Watch с наблюдаемой переменной.
(рис 15.10) Средства "Смотреть" в Developer ToolsЕще один способ добавить точку наблюдения переменной – щелкнуть текст "Click to add…" в окне Watch и ввести имя переменной.
Заметим, что функция профилирования
Благодаря профилированию сценариев можно улучшить работу веб-сайта посредством идентификации и исправления связанных с производительностью проблем в коде
Режим Profiler позволяет собрать информацию о временных характеристиках работы веб-узла, которая собирается по мере визуализации страниц веб-узла обозревателем Windows Internet Explorer. Данная информация помогает оптимизировать участки кода, на исполнение которых уходит слишком много времени, то есть узкие места.
Чтобы приступить к профилированию, необходимо открыть вкладку "Профили" и начать сеанс профилирования сценария нажатием кнопки "Запуск создания профилей". При этом обработчик сценариев переключается в режим профилирования, и на кнопке появляется текст "Остановка создания профилей". Выполнив сценарий, который необходимо профилировать на веб-странице, после чего щелкните "Остановка создания профилей" для завершения сеанса. Автоматически отображается отчет о только что созданном профиле.
На рис. 15.11 показан основной пользовательский интерфейс вкладки "Профили".
(рис 15.11) Вкладка "Профили" в Developer ToolsОтчеты о профилях можно просматривать в представлении "Функции" или "Дерево вызовов", которое можно выбрать из раскрывающегося списка "Текущее представление".
В отчете показаны функции, использовавшиеся обозревателем Windows Internet Explorer для визуализации URL-адреса. Выводится название функции, число ее вызовов, включительное и исключительно время. Включительно время – это время, затраченное на исполнение самой функции и всех функций, из нее вызванных. Исключительное время – это время, затраченное на исполнение самой функции без учета функций, из нее вызванных.
С помощью собранной профилировщиком информации легко найти узкие места в коде сайта. Если обнаружить и изменить структуру неэффективного кода или медленных алгоритмов, то можно сократить время, которое обозреватель Windows Internet Explorer тратит на визуализацию веб-страниц.
Данные профиля можно экспортировать из текущего отчета в CSV-файл.
Данное практическое занятие рассматривает следующие темы:
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.