Элементы управления с JavaScript-кодом

79

Язык JavaScript играет важную роль при построении многих сложных веб-элементов управления. В идеальном мире разработчику веб-страницы никогда не пришлось бы беспокоиться о применении JavaScript. Вместо этого было бы достаточно запрограммировать изящные объектно-ориентированные элементы управления, визуализирующие те элементы JavaScript, в которых они нуждаются для оптимизации своего внешнего вида и производительности. Этот подход позволяет воспользоваться достоинствами обеих технологий - объектно-ориентированного программирования на сервере и полезных добавления JavaScript клиентской стороны.

С помощью JavaScript и модели HTML DOM можно создать любое количество элементов управления. Обычными примерами служат сложные меню, специализированные деревья и сложные сетки, многие из которых доступны (некоторые бесплатно) на сайте сообщества по адресу www.asp.net. В следующих разделах рассматриваются два специальных элемента управления, использующие JavaScript - генератор всплывающих окон и динамически меняющаяся кнопка.

Всплывающие окна

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

Всплывающее окно достаточно просто отобразить, используя функцию window.open() в блоке JavaScript, например:

window.open('http://www.google.com', 'myWindow',
    'toolbar=0, height=500, width=800, resizable=1, scrollbars=1');
window.focus();

Функция window.open() принимает несколько параметров. В их число входят ссылка на новую страницу и имя фрейма окна (которое важно, если новый документ должен быть загружен в этот фрейм позже посредством другой ссылки). Третий параметр - разделенная запятыми строка атрибутов, которые конфигурируют стиль и размер всплывающего окна. Этими атрибутами могут быть любые из перечисленных ниже:

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

В этих сценариях необходим определенный уровень программного управления всплывающим окном, поэтому целесообразно создать компонент, который содержит все эти сведения. В следующем примере будет разработан элемент управления Popup, играющий описанную роль. Как и для специальных элементов управления код этого элемента управления можно поместить в каталог App_Code, но надежнее скомпилировать его в отдельную сборку библиотеки классов (именно этот подход применяется в примере).

Наследуя этот компонент от класса Control, мы получаем возможность добавления всплывающего окна в панель инструментов и его помещения в веб-форму во время разработки.

Для обеспечения максимальной возможности повторного использования элемент управления Popup предоставляет такие свойства, как PopUnder, Url, WindowHeight, WindowWidth, Resizable и Scrollbars, которые позволяют конфигурировать генерируемый им JavaScript-код. Ниже приведен код свойств элемента управления Popup:

namespace SimpleControls
{
    public class PopUp : Control
    {
        public PopUp()
        {
            PopUnder = true;
            Resizable = false;
            Scrollbars = false;
            Url = "about:blank";
            WindowHeight = 300;
            WindowWidth = 300;
        }

        public bool PopUnder
        {
            get { return (bool)ViewState["PopUnder"]; }
            set { ViewState["PopUnder"] = value; }
        }

        public string Url
        {
            get { return (string)ViewState["Url"]; }
            set { ViewState["Url"] = value; }
        }

        public int WindowHeight
        {
            get { return (int)ViewState["WindowHeight"]; }
            set
            {
                if (value < 1)
                {
                    throw new ArgumentException("Высота окна должна быть больше 0");
                }
                ViewState["WindowHeight"] = value;
            }
        }

        public int WindowWidth
        {
            get { return (int)ViewState["WindowWidth"]; }
            set
            {
                if (value < 1)
                {
                    throw new ArgumentException("Ширина окна должна быть больше 0");
                }
                ViewState["WindowWidth"] = value;
            }
        }

        public bool Resizable
        {
            get { return (bool)ViewState["Resizable"]; }
            set { ViewState["Resizable"] = value; }
        }

        public bool Scrollbars
        {
            get { return (bool)ViewState["Scrollbars"]; }
            set { ViewState["Scrollbars"] = value; }
        }
    }
}

Теперь, когда эти свойства определены в элементе управления, пора их применить в методе Render(), который выводит JavaScript-код на страницу. Прежде всего, необходимо удостовериться, что браузер поддерживает JavaScript. Для этого можно проверить свойство Page.Request.Browser.JavaScript, которое возвращает значение true или false, но этот подход считается устаревшим (поскольку он не позволяет разграничивать уровни поддержки JavaScript и модели HTML DOM). Рекомендуется проверить, что значение Page.Request.Browser.EcmaScriptVersion больше или равно 1 (это означает наличие поддержки JavaScript).

Если JavaScript поддерживается, код использует метод StringBuilder для создания блока сценария. Этот код достаточно понятен. Единственный необычный нюанс заключается в том, что булевские значения Scrollbars и Resizable должны быть преобразованы в целочисленные, а затем в строки. Это объясняется тем, что обязательный синтаксис имеет форму scrollbars=1, а не scrollbars=true (что явилось бы конечным результатом при непосредственном преобразовании значения типа Boolean в строку). Полный код визуализации имеет следующий вид:

namespace SimpleControls
{
    public class PopUp : Control
    {
        // ...

        protected override void Render(HtmlTextWriter writer)
        {
            if (Page.Request == null || Page.Request.Browser.EcmaScriptVersion.Major >= 1)
            {
                StringBuilder javaScriptString = new StringBuilder();
                javaScriptString.Append("<script type='text/javascript'>");
                javaScriptString.Append("\n<!-- ");
                javaScriptString.Append("\nwindow.open('");
                javaScriptString.Append(Url + "', '" + ID);
                javaScriptString.Append("','toolbar=0,");
                javaScriptString.Append("height=" + WindowHeight + ",");
                javaScriptString.Append("width=" + WindowWidth + ",");
                javaScriptString.Append("resizable=" + Convert.ToInt16(Resizable).ToString() + ",");
                javaScriptString.Append("scrollbars=" + Convert.ToInt16(Scrollbars).ToString());
                javaScriptString.Append("');\n");
                if (PopUnder)
                    javaScriptString.Append("window.focus();");
                javaScriptString.Append("\n-->\n");
                javaScriptString.Append("</script>\n");
                writer.Write(javaScriptString.ToString());
            }
            else
            {
                this.Page.Response.Write("Ваш браузер не поддерживает JavaScript");
            }
        }
    }
}

Для использования элемента управления PopUp понадобится зарегистрировать сборку элемента управления и отобразить ее на префикс элемента управления с помощью директивы Register. Затем элемент управления PopUp можно объявить на странице. Ниже приведен пример веб-страницы, где это сделано:

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>
<%@ Register Assembly="SimpleControls" Namespace="SimpleControls" TagPrefix="cc1" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Основы ASP.NET</title>
</head>
<body>
    <form id="form1" runat="server">
        <div>
            <cc1:PopUp ID="PopUp1" runat="server" Url="http://professorweb.ru"
                 Scrollbars="true" Resizable="true" />
        </div>
    </form>
</body>
</html>

Элемент управления Popup в действии показан на рисунке ниже:

Показ всплывающего окна

Обычно специальные элементы управлений регистрируют блоки JavaScript в методе OnPreRender(), вместо того, чтобы записывать их непосредственно в методе Render(). Однако элемент управления Popup пренебрегает этим подходом и принимает на себя непосредственное управление записью блока сценария. Это объясняется тем, что в данном случае обычное поведение, при котором один блок сценария создается независимо от количества элементов управления PopUp, помещаемых на странице, не подходит. Напротив, требуется, чтобы при добавлении более одного элемента управления PopUp страница содержала отдельный блок для каждого элемента управления. Это позволяет создавать страницы, которые выводят на экран несколько всплывающих окон.

Если требуется усовершенствовать компонент Popup, можно добавить дополнительные свойства. Например, можно было бы добавить свойства, позволяющие указывать позицию, в которой окно будет выведено на экран. Некоторые веб-сайты используют рекламные объявления, которые не появляются в течение нескольких секунд. Для рассматриваемого компонента этот подход можно было бы реализовать добавлением таймера JavaScript (и дополнительного свойства, позволяющего указать время ожидания в секундах). Стоит повториться: основная идея состоит в том, чтобы предоставить разработчику страницы аккуратный объект, с помощью которого можно программировать, и возможность использования методов визуализации для генерирования необходимого JavaScript-кода в странице.

Динамически меняющиеся кнопки

Динамически меняющиеся кнопки - еще одна полезная особенность JavaScript, не имеющая аналога в мире ASP.NET. Динамическая меняющаяся кнопка выводит на экран одно изображение, когда появляется впервые, и другое изображение при задержке над ней курсора мыши (а иногда и третье изображение при щелчке на ней).

Для обеспечения этого эффекта динамическая кнопка обычно состоит из дескриптора <img>, который обрабатывает JavaScript-события onclick, onmouseover и onmouseout. Эти события будут вызывать функцию, которая меняет изображения для текущей кнопки, как показано в следующем примере:

function swapImg(id, url) {
    var elm = document.getElementById(id);
    elm.src = url;
}

В этом случае сконфигурированный дескриптор <img> выглядел бы следующим образом:

<img id="img" src="activebtn.png" style="cursor:pointer; margin:120px"
    onmouseover="swapImg('img', 'hoverbtn.png')"
    onmouseout="swapImg('img', 'activebtn.png')" />
<img src="buttonOriginal.jpg"

Динамические кнопки - одни из основополагающих веб-элементов, и их отсутствие в ASP.NET можно довольно легко восполнить с помощью специальных элементов управления. Простейший способ создания класса такого элемента - наследование его от WebControl и использования <img> в качестве базового дескриптора. Чтобы позволить кнопке инициировать серверное событие при щелчке на ней, понадобится также реализовать обработчик IPostBackEventHandler.

Класс RollOverButton предоставляет два свойства - URL-адрес для исходного изображения и URL-адрес для изображения, которое должно быть выведено, когда пользователь перемещает курсор мыши над кнопкой. Ниже приведены определения этих свойств:

namespace SimpleControls
{
    public class RollOverButton : WebControl, IPostBackEventHandler
    {
        public RollOverButton()
            : base(HtmlTextWriterTag.Img)
        {
            ImageUrl = "";
            MouseOverImageUrl = "";
        }

        public string ImageUrl
        {
            get { return (string)ViewState["ImageUrl"]; }
            set { ViewState["ImageUrl"] = value; }
        }

        public string MouseOverImageUrl
        {
            get { return (string)ViewState["MouseOverImageUrl"]; }
            set { ViewState["MouseOverImageUrl"] = value; }
        }
        
        // ...
}

Теперь нужно заставить элемент управления генерировать клиентский код JavaScript, который будет отвечать за смену этих двух изображений. В данном случае весьма вероятно, что на одной странице окажется сразу несколько экземпляров RollOverButton. Это означает, что блок сценария понадобится зарегистрировать со специфичным для элемента управления ключом, чтобы независимо от количества добавляемых кнопок существовал только один экземпляр функции. По соглашению этот блок сценария регистрируется за счет переопределения метода OnPreRender(), который вызывается непосредственно перед началом процесса визуализации, как показано в следующем примере:

public class RollOverButton : WebControl, IPostBackEventHandler
{
        // ...

        protected override void OnPreRender(EventArgs e)
        {

            if (!Page.ClientScript.IsClientScriptBlockRegistered("swapImg"))
            {
                string script =
                    "<script type='text/javascript'> " +
                    "function swapImg(id, url) { " +
                    "var elm = document.getElementById(id); " +
                    "elm.src = url; }" +
                    "</script> ";

                Page.ClientScript.RegisterClientScriptBlock(this.GetType(),
          "swapImg", script);
            }

            base.OnPreRender(e);
        }
        
        // ...

}

С помощью метода IsClientScriptBlockRegistered() этот код явно проверяет, зарегистрирован ли блок сценария. В действительности тестирование этого свойства не обязательно; до тех пор, пока используется один и тот же ключ, ASP.NET визуализирует только один экземпляр блока сценария. Однако методы IsClientScriptBlockRegistered() и IsStartupScriptRegistered() можно использовать для предотвращения выполнения работы, потенциально отнимающей много времени. В этом примере он избавляет от незначительных издержек построения строки блока сценария, если она не нужна.

Чтобы действительно оптимизировать код специального элемента управлений, поместите весь свой код JavaScript в отдельный файл, внедрите тот файл в скомпилированную сборку элемента управления, а затем представьте ее через URL-адрес с помощью атрибута WebResource. В частности, ASP.NET использует этот подход со своими элементами управления проверкой достоверности.

Помните, что поскольку класс RollOverButton является производным от WebControl и использует <img> в качестве базового дескриптора, он уже обладает средствами для вывода дескриптора <img>. Единственные части, которые нужно предоставить - это атрибуты, такие как имя и src. Кроме того, необходимо обработать событие onclick, чтобы выполнить обратную отправку страницы, и события onmouseover и onmouseout, чтобы обеспечить смену изображений. Это можно сделать, переопределив метод AddAttributesToRender() следующим образом:

public class RollOverButton : WebControl, IPostBackEventHandler
{
        // ...

        protected override void AddAttributesToRender(HtmlTextWriter output)
        {
            output.AddAttribute("id", ClientID);
            output.AddAttribute("src", ImageUrl);
            output.AddAttribute("onclick", 
                Page.ClientScript.GetPostBackEventReference(new PostBackOptions(this)));

            output.AddAttribute("onmouseover",
                "swapImg('" + this.ClientID + "', '" +
                MouseOverImageUrl + "');");

            output.AddAttribute("onmouseout",
                "swapImg('" + this.ClientID + "', '" +
                ImageUrl + "');");
            
            output.AddAttribute("class", this.CssClass);
        }
        
        // ...

}

Метод Page.ClientScript.GetPostBackEventReference() возвращает ссылку на клиентскую функцию __doPostBack(). Используя эти сведения, можно построить элемент управления, который инициирует обратную отправку. Понадобится также обязательно указать атрибут идентификатора для своего элемента управления, чтобы сервер мог его идентифицировать как источник обратной отправки.

В заключение необходимо создать метод RaisePostBackEvent(), который требуется интерфейсом IPostBackEventHandler, и использовать его для генерации серверного события, как показано в следующем коде:

public event EventHandler ImageClicked;

public void RaisePostBackEvent(string eventArgument)
{
    OnImageClicked(new EventArgs());
}

protected virtual void OnImageClicked(EventArgs e)
{
    // Проверить наличие, по крайней мере,
    // одного слушателя и затем сгенерировать событие
    if (ImageClicked != null)
        ImageClicked(this, e);
}

На рисунке ниже показана страница с двумя динамическими кнопками:

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>
<%@ Register Assembly="SimpleControls" Namespace="SimpleControls" TagPrefix="cc1" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Основы ASP.NET</title>
    <style>
        .button {
            cursor: pointer;
        }
    </style>
</head>
<body style="background:#333">
    <form id="form1" runat="server">
        <div style="text-align:center; padding-top:85px;">
            <cc1:RollOverButton ID="RollOverButton1" runat="server" CssClass="button"
                ImageUrl="activebtn.png" MouseOverImageUrl="hoverbtn.png" />
            <br /><br />
            <cc1:RollOverButton ID="RollOverButton2" runat="server" CssClass="button"
                ImageUrl="activebtn.png" MouseOverImageUrl="hoverbtn.png" />
        </div>
    </form>
</body>
</html>
Использование динамической кнопки

Один из способов усовершенствования этого элемента управления - добавление предварительной загрузки изображения, чтобы динамическое изображение загружалось при первой визуализации страницы сначала (а не при перемещении курсора мыши над изображением). Без этого может наблюдаться задержка при первом наведении курсора мыши на кнопку.

Простейший способ выполнения предварительной загрузки состоит в создании сценария, который запускается при загрузке страницы. Этот сценарий должен создавать JavaScript-объект Image и устанавливать свойство Image.src для указания изображения, предварительную загрузку которого требуется выполнить. (При наличии нескольких изображений, которые должны быть предварительно загружены, можно просто присвоить свойству src изображения друг за другом.)

Объект Image фактически не используется в странице, но предварительно загруженные файлы изображений будут автоматически сохранены в кэше браузера. При указании этого же URL-адреса в другом месте страницы (например, в функции swapImg()), будет использоваться кэшированная версия.

Ниже приведен код, который потребуется добавить в метод OnPreRender() для реализации предварительной загрузки страницы:

protected override void OnPreRender(EventArgs e)
{
    // ...

    if (!Page.ClientScript.IsStartupScriptRegistered("preload" + this.ClientID))
    {
        string script =
            "<script type='text/javascript'> " +
            "var preloadedImage = new Image(); " +
            "preloadedImage.src = '" + MouseOverImageUrl + "'; " +
            "</script> ";

        Page.ClientScript.RegisterStartupScript(this.GetType(), 
            "preload" + this.ClientID, script);
    }

    base.OnPreRender(e);
}
Пройди тесты
Лучший чат для C# программистов