Контрол с клиентским сценарием. Быстрый старт

В большинстве случаев при создании контролов возникает необходимость использования клиентских java-скриптов. При их использовании возникает 2 затруднительных момента:

  1. Как их подключать на страницу.
  2. как запустить функцию на клиентской стороне в тот момент когда вся нужная информация загружена.

Второй вопрос отягощается также необходимостью дружить c AJAX системой ASP.NET (ATLAS). Если контрол будет находится на UpdatePanel, то становится совершенно не понятно, что, как и когда подключать.

Загрузка скриптов на клиентскую сторону

На самом деле всё давно продумали за нас доблестные программисты цитадели добра - Микрософт. Для подключения скриптов на клиентскую сторону используется специальный механизм заложенный в самой системе. Корень этого механизма - интерфейс IScriptControl. Интерфейс предоставляет 2 метода, реализация которых обязательна:

  • GetScriptReferences - должен возвращать список зависимостей от клиентских скриптов.
  • GetScriptDescriptors - должен возвращать коллекцию "описаний скриптов", что это такое чуть позднее.

В коллекции зависимостей вы можете указать любое число клиентских скриптов. Включены в страницу они будут в той последовательности в которой вы их указываете. Механизм включения, по идее, исключает дублирование, то есть один и тот же скрипт не должен быть включен дважды. На практике, же для нормальной его работы пути к скриптам не должны различаться регистром. То есть скрипт "jQuery.js" и "jquery.js" будет включен дважды.

Пример реализации GetScriptReferences:

public IEnumerable GetScriptReferences() { String assembly = System.Reflection.Assembly.GetAssembly(this.GetType()).GetName().Name; String name = "Imin.Web.Core.Imin.Web.Forms.ExFieldDate.js";

return new ScriptReference[] { new ScriptReference("/tools/JavaScript/jquery.js"), new ScriptReference("/tools/JavaScript/jquery.watermark.js"), new ScriptReference("/tools/JavaScript/jquery.insert.js"), new ScriptReference("/tools/JavaScript/ui/ui.core.js"), new ScriptReference("/tools/JavaScript/ui/ui.datepicker.js"), new ScriptReference("/tools/JavaScript/ui/i18n/ui.datepicker-ru.js"), new ScriptReference(name, assembly) }; }

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

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

Для включения скрипта в сборку достаточно добавить файл скрипта в проект сборки и в свойствах файла указать Build Action = Embedded Resource.

После чего в каком-либо компилируемом файле, например в файле класса к которому привязывется скрипт указать строку вида:

[assembly: WebResource("Imin.Web.Core.Imin.Web.Forms.ExFieldDate.js", "text/javascript")]

Пример, конечно, не очень показательный. Нужно пояснить, что имя скрипта в параметре WebResource складывается из имени библиотеки, через точку указываются все папки внутри библиотеки и опять через точку имя файла. В примере имя библиотеки было "Imin.Web.Core", имя папки "Imin.Web.Forms" и имя файла "ExFieldDate.js".

Если проделать все эти действие, то можно заметить, что ничего, собственно не произойдет. Все правильно. Потому как общий механизм несколько сложнее. Для того, чтобы метод GetScriptReferences все-таки был вызван необходимо "зарегистрировать" класс его содержащий в текущем ScriptManager. Делается это так:

protected override void OnPreRender(EventArgs e) { base.OnPreRender(e);

if (Scripts.Count > 0) { ScriptManager sm = ScriptManager.GetCurrent(Page); if (sm == null) throw new Exception("A ScriptManager control must exist on the current page."); sm.RegisterScriptControl(this); } }

После чего менеджер скриптов начнет анализ указанных вами зависимостей, включение их в страницу. На его плечи будут также ложиться динамическая их загрузка при загрузке контрола через UpdatePanel.

Таким образом, вполне можно успокоиться на достигнутом, если вас интересует только загрузка скриптов на клиентскую сторону.

Включение скрипта в систему ASP.NET AJAX

Простой передачи скрипта на клиентскую сторону может оказаться недостаточно. Иногда требуется выполнение какого-либо кода на клиентской стороне по загрузке страницы.

С одной стороны проблема решается достаточно просто - достаточно использовать jQuery или просто window.onload. С другой стороны практика показывает нестабильность работы клиентского скрипта при таком подходе. Событие срабатывает не тогда когда нужно. Когда оно срабатывает не все компоненты страницы готовы к работе. А при динамической загрузке и вовсе ничего не работает.

Чтобы не бороться каждый раз со скриптами и не выискивать хитрых ходов достаточно воспользоваться стандартными возможностями ASP.NET. А именно всё тем же интерфейсом IScriptControl, о котором, собственно и идет рассказ всё это время. Выше мы упомянули о методе GetScriptDescriptors который необходимо реализовать.

Этот метод должен возвращать некое описание скрипта. Начнем рассмотрение вопроса, пожалуй, со структуры клиентского сценария, поскольку так будет ясна и сервеная логика, многим более простая. Скрипт для совместного использования с серверным контролом должен быть определенной формы, должен содержать класс с определенным именем, определенными членами. Не вдаваясь в излишние подробности просто приведем его пример:

Type.registerNamespace('IWCtrls');

IWCtrls.FieldDate = function(element) { IWCtrls.FieldDate.initializeBase(this, [element]); this.DateFormat = null; }

IWCtrls.FieldDate.prototype = {

initialize : function() { IWCtrls.FieldDate.callBaseMethod(this, 'initialize'); var elt = this.get_element(); $(elt).watermark(this.DateFormat); }, dispose : function() { $clearHandlers(this.get_element()); IWCtrls.FieldDate.callBaseMethod(this, 'dispose'); } };

IWCtrls.FieldDate.registerClass('IWCtrls.FieldDate', Sys.UI.Control);

Из рассмотрения примера можно вынести несколько заключений.

  1. Функция init определенная в прототипе класса - это та самая функция которая выполняется менеджером скриптов на клиентской стороне, когда все готово е её работе. Внутри этой функции следует размещать всевозможные манипуляции с DOM, каковые впоследствии должен будет видеть пользователь.
  2. функция dispose - это немаловажная функция, которая позволит вам избежать многих глюков и утечек памяти связанныъ с отложенным выполнением кода по setTimeout и setInterval. Ведь если контрол на клиентской стороне уже уничтожен и нет уже того сценария, то что будет выполнять window? А ведь это именно window выполняет отложенные и периодические вызовы.
  3. Вызов this.get_element(); - возвращает объект DOM к которому привязана реализация описанного класса. Очень удобно, так как одинаковых контролов на странице может быть несколько. Поэтому, к стати, класс описывается через прототип.
  4. this.DateFormat = null; Как видно в примере нигде не устанавливается его значение, однако оно используется в init. Это тот самый момент, для чего нам и нужно описание скрипта в GetScriptDescriptors.

В целом, пример содержит минимальную функциональность. При создании собственных сценариев пожалуйста учтите, что IWCtrls как и само имя класса FieldDate выбираются вами самостоятельно. Набор полей типа this.DateFormat = null; так же составляется вами. Вся функциональность примера сводится к строке $(elt).watermark(this.DateFormat).

Да, большой оверхед. Да много "лишнего". Однако, за стабильность надо платить. Ну и кроме того, gzip сжатие на сервере никто не отменял.

Как видно, на клиентскую сторону каким-то образом должен попасть определенный набор данных. Во первых это должен быть ID DOM объекта к которому привязывается скрипт. Тот самый ID  по которому возвращается объект методом this.get_element(). Во вторых это значения полей, в нашем примере  - это this.DateFormat. Для того, чтобы указать менеджеру скриптов какие данные и куда передавать используется метд GetScriptDescriptors. Пример реализации метода:

public IEnumerable GetScriptDescriptors() { ScriptControlDescriptor sd = new ScriptControlDescriptor("IWCtrls.FieldDate", this.ClientID); sd.AddProperty("DateFormat", "dd MM yyyy"); return new ScriptDescriptor[] { sd }; }

 

Обратите внимание на конструктор класса ScriptControlDescriptor. В первом параметре у него указывается имя класса на клиентской стороне (см пример выше). Во втором параметре указывается ID HTML-элемента на клиентской стороне (к которому будет "привязан" скрипт). Учтите, что для правильной работы класс должен быть наследником интерфейса INamingContainer. Наследование этого объекта ни к чему не обязывает, однако, при этом система присвоит вашему объекту уникаьный ID, по которому его будет просто найти на клиентской стороне.

С добавлением свойств, думаю все понятно. При рассмотрении класса ScriptControlDescriptor вы можете заметить, что добавлять объекту клиентского сценария можно не только свойства, но и еще много чего интересного. Как, собственно и скрипт не обязательно должен быть привязан к элементу на странице, но при этом может иметь описание. Можно описывать сразу несколько элементов на странице и еще много чего. Всё это сильно выходит за рамки той статейки, поэтому после освоения описанного приема работы, рекомендую всё же обратиться к документации.

Ну и напоследок. Вам не кажется, что мы что-то забыли? Именно так, ведь мы должны сообщить менеджеру скриптов о необходимости обработать описания скриптов. Делается это аналогично регистрации списка зависимостей:

protected override void OnPreRender(EventArgs e) { base.OnPreRender(e); ScriptManager sm = ScriptManager.GetCurrent(Page); if (sm == null) throw new Exception("No ScriptManager exist!"); sm.RegisterScriptControl(this); sm.RegisterScriptDescriptors(this); }

Заключение

В качестве заключения, хочу еще раз перечислить основные моменты создания контрола с клиентским скриптом:

  • Реализация интерфейса IScriptControl
  • В методе GetScriptReferences описывается список зависимостей от скриптов. Иначе говоря, возвращается массив ссылок на скрипты, которые должны быть загружены на клиентскую сторону
  • В методе GetScriptDescriptors производится привязка скрипта к данным с серверной стороны. Очень удобно, чтобы не выдумывать способов передать на клиентскую сторону данные и не гонять их через хиддены.
  • Чтобы вся эта кухня работала нужно не позднее чем на пре-рендер зарегистрировать ваш класс в скрипт-менеджере.
  • Клиентский скрипт делайте по образцу и не забывайте о возможностях клиентской библиотеки.

Заметьте, кроме интерфейса IScriptControl существует так же и класс ScriptControl, в нем уже реализована вся эта кухня. Однако, он является наследником WebControl. Чтобы не заморачиваться с этим классом, и наследоваться от любого класса который вам нравится лучше использовать интерфейс IScriptControl.

Нет комментариев
Добавить комментарий