Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Скачиваний:
107
Добавлен:
02.05.2014
Размер:
6.34 Mб
Скачать

156 Часть II. Основные подходы к разработке приложений

Функция assignKeys () вызывается в составе обработчика window. onload. (Если определить window.onload непосредственно, это ограничит переносимость приложения.) Мы находим элемент keyboard по его уникальному идентификатору, а затем используем функцию getElementsByTagName () для перебора всех дочерних элементов div. Для этого необходимо иметь представление о структуре страницы, зато дизайнер получает возможность перемещать элементы div, соответствующие клавишам, любым удобным для него способом.

Каждый элемент DOM, представляющий клавишу, возвращает посредством свойства className строку символов. Для преобразования строки

вмассив мы используем встроенную функцию String.split, после чего проверяем, принадлежит ли элемент классу musicalButton. Затем мы читаем оставшуюся часть строки, которая представляет ноту, и связываем ее с узлом DOM посредством дополнительного свойства. Это свойство будет прочитано

вобработчике события.

Воспроизводить звуки посредством Web-браузера затруднительно, поэтому в данном примере мы лишь отображаем ноты на "консоли" под изображением клавиатуры. На рис. 4.4 показано действие клавиатуры. В данном случае нам удалось довольно удачно разделить роли. Если дизайнер переместит элементы DIV, соответствующие клавиатуре и консоли, в другое место документа, приложение будет по-прежнему работать и риск случайно изменить логику обработки событий сводится к минимуму. По сути, HTMLстраница выполняет функции шаблона, в состав которого мы включаем переменные и логику выполнения. Это позволяет отделить логику от представления. Мы обработали данный пример вручную, чтобы детально продемонстрировать наши действия. При работе над реальным приложением целесообразно использовать для получения того же результата библиотеки независимых производителей.

Библиотека Rico (http://www.openrico.org/) поддерживает объекты Behavior и позволяет реализовать интерактивные возможности поддерева DOM. Компонент Rico Accordion рассматривался в разделе 3.5.2. Разделить элементы разметки HTML и интерактивные функции можно посредством библиотеки Behaviour (см. ссылку в конце данной главы), созданной Беном Ноланом (Ben Nolan). Данная библиотека позволяет связывать с элементами DOM код, предназначенный для обработки событий, используя селекторы CSS (см. главу 2). В предыдущем примере функция assignKeys () выбирала элемент с идентификатором keyboard, а затем извлекала все содержащиеся в нем элементы div. Используя селектор, мы можем выразить то же правило следующим образом:

•keyboard div

Применяя CSS, мы можем связать стиль со всеми элементами keyboard посредством данного селектора. Библиотека Behaviour.js также позволяет использовать обработчики событий следующим образом:

var myrules={ 'tkeyboard div1 : function(key){

var classes=(key.className).split(" " ) ;

Глава 4. Web-страница в роли приложения 157

Рис. 4.4. Клавиатура "музыкального инструмента", представленная посредством браузера. Закрашенные области в верхней части окна соответствуют различным нотам. При перемещении указателя мыши над "клавишами" названия нот отображаются в области, расположенной ниже и выполняющей функции консоли

if (classes && classes.length>=2

&&

classes[l]=='musicalButton') { var note=classes[0]; key.note=note; key.onmouseover=playNote;

)

}

};

Большая часть логики совпадает с предыдущим примером, но использование селекторов CSS представляет собой удобную альтернативу рассмотренному ранее решению, в особенности в тех случаях, когда надо одновременно реализовать несколько вариантов поведения.

В данном случае логика отделена от представления, однако, как мы вскоРе увидим, остается возможность включения средств представления в состав логики.

4.2.2. Отделение представления от логики

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

158 Часть 11. Основные подходы к разработке приложений

документа. В частности, в HTML-коде задается порядок следования клавиш. Каждая клавиша определена посредством отдельного дескриптора div, и дизайнер может случайно удалить некоторые из них.

Если расположение клавиш — вопрос функционирования приложения,

ане его оформления, то имеет смысл генерировать некоторые элементы DOM

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

Листинг 4.4. Файл musical_dyn_keys. js

var notes=new Array("do","re","mi","fa","so","la","ti","do"); function assignKeys(){

var candidates=document.getElementsByTagName("div"); if (candidates){

for(var i=O;i<candidates.length;i++){ var candidate=candidates[i];

if (candidate.className.indexOf('musicalKeys')>=0){ makeKeyboard(candidate);

, '

}

} function makeKeyboard(el){ for(var i=0;i<notes.length;i++){

var key=document.createElement("div"); key.className=notes[i]+" musicalButton"; key.note=notes[i]; key.onmouseover=playNote; el.appendChild(key);

}

}

function playNote(event){ var note=this.note;

var console=document.getElementByld('console'); if (note && console){

console.innerHTML+«note+" . ";

} )

J

Ранее мы определяли клавиши в HTML-коде. Теперь мы задаем их в глобальном массиве JavaScript. Метод assignKeys () анализирует в документе все дескрипторы div, принадлежащие верхнему уровню, и находит те из них, свойство className которых содержит значение musicalKeys. Если такой элемент найден, предпринимается попытка заполнить его элементами div, соответствующими клавишам. Для этого используется функция makeKeyboard(). Она создает новые узлы DOM, а затем выполняет с ними такие же действия, которые ранее выполнялись с DOM-узлами, объявленными в HTML-коде. Функция обратного вызова playNote () действует так же, как и ранее.

Глава 4. Web-страница в роли приложения 159

Поскольку мы заполнили пустые элементы div элементами, представляющими клавиши, создание второго набора клавиш не представляет труда. Соответствующее решение представлено в листинге 4.5.

Листинг 4.5. Файл mus±cal_dyn_keys .html

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtmll/DTD/xhtmll-strict.dtd">

<html> <title>Two Keyboards</title> <head> <link rel=*stylesheet1 type-'text/ess1

href-'musical_dyn_keys.ess'/> <script

t y p e = ' t e x t / j a v a s c r i p t' src=•musical_dyn_keys.j s'> </script>

<script type='text/javascript'> window.onload=assignKeys; </script>

</head>

<body> <div id='keyboard-top1 class='toplong musicalKeys'X/div> <div id='keyboard-side1 class='sidebar musicalKeys'X/div> <div id='console' class='console•> </div> </body> </html>

Для того чтобы добавить на страницу вторую клавиатуру, достаточно включить в HTML-файл одну дополнительную строку. В данном случае мы не собираемся располагать клавиатуры одну под другой, поэтому стили, определяющие расположение, мы удалим из класса musicalKeys и оформим в виде отдельных классов. Модифицированные таблицы стилей показаны в листинге 4.6.

^Листинг 4.6. Модифицированные стили в файле musical_dyn_keys.css

/* Общие стили для клавиатуры */

.musicalKeys{

background-color: #ffe0d0; border: solid maroon 2px; position: absolute; overflow: auto;

margin: 4px;

}/* Размеры и расположение первой клавиатуры */

.toplong{ width: 536рх; height: 68рх; top: 24рх; left: 24рх;

}/* Размеры и расположение второй клавиатуры */

.sidebar{ width: 48px; height: 400px; top: 24px; left: 570px;

160 Часть II. Основные подходы к разработке приложений

Рис. 4.5. Модифицированное приложение позволяет дизайнеру задавать несколько клавиатур. Используя CSS и средства воспроизведения в составе браузера, мы обеспечиваем горизонтальное и вертикальное расположение клавиш, не создавая для этого дополнительный JavaScript-код

В классе musicalKeys остались стили, общие для обеих клавиатур. Классы toplong и sidebar лишь задают их расположение.

Выполнив таким образом реструктуризацию, мы создали условия для повторного использования кода. Внешний вид клавиатуры частично определяется JavaScript-кодом; ее формированием занимается функция makeKeyboard(). Как видно на рис. 4.5, одна клавиатура располагается по горизонтали, а другая — по вертикали. Как мы достигли этого?

Функция makeKeyboard () позволяет без труда вычислить размер каждого элемента и определить его расположение. Если бы мы поступили так, то нам пришлось бы предпринимать меры для размещения клавиш по горизонтали или по вертикали. Специалисты, имеющие опыт разработки пользовательских интерфейсов для Java-программ и применявшие диспетчеры компоновки LayoutManager, могут склониться именно к такому решению. Если мы поступим подобным образом, контролировать внешний вид компонентов будут не дизайнеры, а программисты, в результате конфликты станут неизбежны.

Таким образом, принято решение, согласно которому функция makeKey-j board () влияет только на структуру документа. Размещение клавиш осу-! ществляется средствами браузера с учетом таблиц стилей; в данном случае используется стиль float. Важен тот факт, что размещение элементов кон-

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

чаях, например, при организации дерева, бывает трудно обеспечить желае-

Глава 4. Web-страница в роли приложения 161

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

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

4.3. Контроллер в составе Ajax-приложения

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

4.3.1. Классические JavaScript-обработчики

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

Классическая модель поддержки событий достаточно проста и используется с момента появления JavaScript. В большинстве элементов DOM предусмотрены свойства, позволяющие связывать с ними функции обратного вызова. Например, для того, чтобы присоединить функцию, которая будет вызываться по щелчку на элементе myDomElement, мы должны написать следующее выражение:

myDomElement.onclick=showAnimatedMonkey

Здесь myDomElement — любой элемент DOM, к которому мы имеем доступ из программы. Функция showAnimatedMonkey () — это обычная функция JavaScript, которая определяется следующим образом:

162

Часть II. Основные подходы к разработке приложений

Таблица 4.1. Свойства, позволяющие связывать обработчики событий

с элементами DOM

 

Свойство

Описание

onmouseover

Активизируется, когда курсор мыши попадает в область,

 

 

занимаемую элементом

onmouseout

Активизируется, когда курсор мыши покидает область,

 

 

занимаемую элементом

onmousemove

Активизируется при каждом перемещении курсора мыши

 

 

в пределах области, занимаемой элементом

onclick

 

Активизируется щелчком в области, занимаемой элементом

onkeypress

Активизируется щелчком мышью при условии, что элемент, над

 

 

которым находится курсор, обладает фокусом ввода

onfocus

 

Активизируется, когда видимый элемент получает фокус ввода

onblur

 

Активизируется, когда видимый элемент теряет фокус ввода

f u n c t i o n showAnimatedMonkey(){

 

// Сложный

код для поддержки движущихся изображений }

Заметьте, что при связывании обработчика события мы не вызываем функцию, а передаем объект Function, поэтому круглые скобки после имени не указываются. Ниже приведен пример распространенной ошибки.

myDomElement.onclick=showAnimatedMonkey();

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

myDomElement.onclick=showAnimatedMonkey;

В результате ссылка на функцию обратного вызова присваивается элементу DOM, сообщая ему о том, что функция должна быть вызвана щелчком на элементе. Элемент DOM может содержать несколько свойств, предназначенных для связывания обработчиков событий. Свойства, часто используемые при создании графического пользовательского интерфейса, описаны в табл. 4.1. Встречавшиеся ранее свойства XMLHttpRequest.onreadystate и window. onload также предназначены для связывания обработчиков.

Механизм обработки событий имеет одну особенность, которую часто не учитывают программисты, использующие JavaScript. Эту особенность необходимо знать всем, кто собирается приступать к работе над клиентскими программами Ajax.

Глава 4. Web-страница в роли приложения 163

Предположим, что мы связали с элементом DOM функцию обратного вызова, используя для этого свойство onclick. Эта функция получает управление по щелчку мышью на элементе DOM. Однако контекст функции (т.е. значение, получаемое переменной this) соответствует узлу DOM, который получаем событие (объекты Function в языке JavaScript обсуждаются в приложении Б). Контекст зависит от того, как была объявлена функция. Этот эффект может привести к возникновению ошибки.

Рассмотрим пример. Предположим, что мы определили класс, представляющий объект кнопки. В нем указан узел DOM, обработчик обратного вызова и значение, которое отображается после щелчка мышью на кнопке. Все экземпляры кнопки одинаковым образом реагируют на щелчок, потому мы определяем обработчик как метод класса. Теперь обратимся к JavaScriptкоду. Конструктор кнопки имеет следующий вид:

function Button(value,domEl){ this.domEl=domEl; this.value=value;

this.domEl.onclick=this.clickHandler;

}

Определим обработчик в составе класса Button.

Button.prototype.clickHandler=function(){ alert(this.value);

}

Такое решение выглядит достаточно просто, но полученный результат — совсем не тот, которого можно было бы ожидать. В окне с сообщением отображается не значение, которое было передано конструктору, а сообщение undefined. Это происходит по следующей причине. Браузер вызывает функцию clickHandler () по щелчку на элементе DOM, поэтому задает контекст элемента DOM, а не JavaScript-объекта Button. Таким образом, this.value ссылается на свойство value элемента DOM, а не объекта Button. Это трудно предположить, глядя на определение обработчика события, не правда ли?

Разрешить данную проблему можно, модифицировав конструктор следующим образом:

function Button(value,domEl){ this.domEl=domEl; this.value=value; this.domEl.buttonObj =this ;

this.domEl.onclick=this.clickHandler;

}

Элемент DOM по-прежнему не имеет свойства value, но содержит ссылку на объект Button, который используется для получения значения. Кроме того, нам надо изменить обработчик события.

Button.prototype.clickHandler=function(){ var buttonObj=this.buttonObj;

var value=(buttonObj && buttonObj.value) ? buttonObj.value : "unknown value";

alert(value);

}

164 Часть II. Основные подходы к разработке приложений

Узел DOM ссылается на объект Button, который ссылается на свойство value, и наш обработчик получает требуемое значение. Мы можем присвоить значение непосредственно узлу DOM, но, связывая ссылку с базовым объектом, мы получаем возможность работать с элементами произвольной сложности. Следует заметить, что в данном случае был реализован "минивариант" архитектуры MVC, согласно которой элемент DOM, выполняющий функции просмотра, отделен от объекта поддержки, являющегося моделью.

Мы рассмотрели классическую модель обработки событий. Основной недостаток этой модели состоит в том, что она допускает для элемента наличие лишь одной функции обработки. При обсуждении образа разработки Observer в главе 3 мы говорили о том, что может потребоваться связать с элементом Observable любое количество элементов Observer. Указанное здесь ограничение не является серьезным препятствием в работе над простым сценарием, но при переходе к более сложным клиентам Ajax оно может существенно помешать нам. Этот вопрос будет подробно рассмотрен в разделе 4.3.3, а пока перейдем к новой модели обработки событий.

4.3.2. Модель обработки событий W3C

Модель поддержки событий, предложенная W3C, обеспечивает большую степень гибкости, но она сложна в использовании. Согласно этой модели с элементом DOM может быть связано произвольное количество обработчиков. Более того, если действие происходит в области пересечения нескольких элементов, обработчики каждого из них имеют шанс получить управление и запретить остальные вызовы из стека событий. Этот эффект принято называть "проглатыванием" (swallowing) событий. Согласно спецификации осуществляются два прохода по стеку событий: первый — от внешнего элемента к внутреннему (т.е. от элемента, представляющего документ, к элементам, содержащимся в нем), в ходе которого происходит заполнение стека, а второй — от внутреннего элемента к внешнему. На практике различные браузеры реализуют подобное поведение по-разному.

В браузерах Mozilla и Safari функции обратного вызова, предназначенные для обработки событий, связываются с помощью функции addEventListener(), а для разрыва связи осуществляется обращение к removeEventListener (). Браузер Internet Explorer предоставляет для этих целей функции attachEvent () и detachEvent (). Объект xEvent, разработанный Майком Фостером (Mike Foster) и входящий в состав библиотеки х, предпринимает попытку реализовать средства обработки событий, маскирующие различия между браузерами. Для этой цели он создает единую точку входа в соответствии с образом разработки Fagade (см. главу 3).

Существуют также различия между браузерами, касающиеся обращений к обработчикам обратного вызова. В браузерах Mozilla, как и в классической модели обработки, контекст вызываемой функции определяется элементом DOM, получившим событие. В Internet Explorer контекст всегда определяется объектом Window, и в результате невозможно выяснить, какой элемент DOM вызвал функцию обработки. При написании обработчиков необходимо учитывать указанные различия, даже в случае применения объекта xEvent.

Глава 4. Web-страница в роли приложения

165

Следует также заметить, что ни одна из реализаций не

предостав-

ляет удобных средств получения списка всех обработчиков,

связанных

с элементом.

 

По этой причине мы не рекомендуем вам использовать новую модель обработки событий. Главный недостаток классической модели — отсутствие поддержки нескольких обработчиков — можно устранить, как вы узнаете далее, с помощью образов разработки.

4.3.3. Реализация гибкой модели событий в JavaScript

Из-за несоответствия средств поддержки новой модели событий в различных браузерах гибкие средства обработки действий пользователя пока нельзя считать доступными. В главе 3 мы описывали образ разработки Observer, который соответствует основным требованиям разработчиков и позволяет добавлять к источнику событий объекты Observer и удалять их. Таким образом обеспечивается необходимая степень гибкости. Очевидно, что специалисты W3C сделали все возможное, чтобы устранить недостатки классической модели, но производители браузеров не смогли обеспечить совместимость своих продуктов и, более того, в некоторых случаях допускали ошибки при реализации модели. Несмотря на то что классическая модель обработки событий не соответствует образу разработки Observer, мы постараемся исправить ситуацию, написав собственный код.

Управление несколькими функциями обратного вызова

Прежде чем реализовывать собственное решение, постараемся лучше понять проблему, а для этого рассмотрим простой пример. В листинге 4.7 приведен код документа, в котором элемент div реагирует на перемещение курсора мыши, выполняя два действия.

Листинг4.7. Файл mousemat.html

<html> <head> <link rel='stylesheet' type='text/ess' href='mousemat.ess1 /> <script type='text/javascript'> var cursor=null; window.onload=function(){

var mat=document.getElementById('mousemat') ; mat.onmousemove=mouseObserver;

cursor=document.getElementByld('cursor' ); } function mouseObserver(event){

var e=event || window.event; writeStatus(e); drawThumbnail(e);

}

function writeStatus(e){ window.status=e.clientX+","+e.clientY;

}

function drawThumbnail(e){

cursor.style.left=((e.clientX/5)-2)+"px"; cursor.style.top=((e.clientY/5)-2)+"px";

} </script> </head> <body> <div class='mousemat1 id='mousemat'X/div> <div class='thumbnail'