Рубрика:
Веб /
Веб
|
Facebook
Мой мир
Вконтакте
Одноклассники
Google+
|
Кирилл Сухов
Библиотека Prototype – ваш путь в Web 2.0
Требования к веб-разработке постоянно растут. Если для какого-нибудь программиста примером идеального пользовательского интерфейса является сайт lib.ru, то обычный пользователь такой аскетизм скорее всего не оценит, и самое страшное – не оплатит. Однако работа, направленная на повышение удобства и практичности пользовательского интерфейса, зачастую отнимает много времени, которое хотелось бы потратить на разработку бизнес-логики приложения. Разработчики настольных систем имеют в своём арсенале такие средства, как MFC, Gtk, Qt и другие библиотеки. Чем же может воспользоваться веб-программист?
WEB 2.0 – маркетинг на марше?
Если говорить откровенно, когда в мире веб-разработчиков случился некий «AJAX-бум», я был настроен довольно скептически. В чём, собственно, революция? В возможности направлять запрос к серверу и получать ответ без перезагрузки страницы? Но это и так уже было. Выучить четыре метода объекта XMLHttpRequest и стать разработчиком WEB 2.0? Звучит несерьёзно.
Со временем мне пришлось поменять свою точку зрения. Я понял, что WEB 2.0 – это не только маркетинговый термин, это новый подход к веб-программированию, позволяющий рассматривать результат труда не как html-страницы, а как полноценные приложения.
Впрочем, не будем на этом останавливаться, на эту тему уже сломано немало копий. Я просто хочу рассказать об инструменте, помогающем перейти к WEB 2.0 вполне безболезненно.
Новый подход диктует новые требования, в частности, язык программирования JavaScript, который многие разработчики считали чем-то вторичным и не заслуживающим внимания, вышел на первый план. Библиотеки, облегчающие работу с ним, начали создаваться со времени его возникновения, но именно сейчас они как никогда актуальны.
Я хочу рассказать о библиотеке, основное предназначении которой – обеспечить комфортную жизнь веб-разработчику, снабдить его новыми возможностями.
Знакомьтесь – Prototype
Что собой представляет Prototype? Это свободно распространяемая JavaScript-библиотека, значительно облегчающая жизнь разработчика. Она написана Сэмом Стефенсоном (Sam Stephenson) и доступна по адресу http://prototype.conio.net. Последняя версия (на момент написания статьи) пронумерована как 1.5.1.
Сама библиотека занимает всего 72 Кб и распространяется по свободной лицензии (MIT-style license).
Её установка не вызывает проблем, просто поместите на вашу страничку тег:
<script src=prototype.js></script>
и вся мощь библиотеки к вашим услугам.
Что мы получим в результате? Совсем не набор компонентов или каких-либо «виджетов», а коллекцию объектов и их методов, из которых можно конструировать свои библиотеки и расширения. Prototype – это библиотека для построения собственных библиотек, именно поэтому она используется, например, в Ruby On Rails, Rico и других framework-средах.
Приятные мелочи
С них обычно начинают описание библиотеки. Пусть они принципиально не важны, но очень удобны. Должен заметить, что к этому удобству, как и вообще ко всему хорошему, быстро привыкаешь.
Прежде всего это упрощение синтаксиса обращения к DOM-элементам и их свойствам посредством функций $() и $F(). Первая из них является заменой привычной конструкции document.getElementById(), но имеет несколько больший функционал. Она может принимать в качестве аргумента как id элемента, так и сам элемент. Аргументов может быть более одного, и в этом случае функция возвращает массив элементов.
allNodes = $("firstDiv", "secondDiv");
for(i = 0; i < allNodes.length; i++) {
alert(allNodes[i].innerHTML);
}
Функция $F(id) возвращает значение полей ввода, принимая аргументом идентификатор поля. Возможно, эта миссия покажется не очень принципиальной, но применение $() и $F() по крайней мере освобождают время для более глобальных задач. Кроме того, эта конструкция также может принимать в качестве аргумента не только идентификатор, но и сам DOM-элемент.
Следующая функция из этой компании – document.get ElementsByClassName. Вообще использовать имя класса для получения DOM (HTML) элемента, приём неновый, но требующий некоторых телодвижений, вроде перебора всех подобных элементов, поиска нужного класса в сложной структуре документа. Теперь мы избавлены от этой нудной работы.
Ниже представлен пример использования этой функции:
<head>
<script>
document.getElementsByClassName('foo');
// -> [HTMLElement, HTMLElement] (div#one, div#two)
document.getElementsByClassName('thud');
// -> [HTMLElement, HTMLElement, HTMLElement] (div#two, li#item_one, li#item_two);
document.getElementsByClassName('thud', $('list'));
// -> [HTMLElement, HTMLElement] (li#item_one, li#item_two)
</script>
</head>
<body>
<div id="one" class="foo">Single class name</div>
<div id="two" class="foo bar thud">Multiple class names</div>
<ul id="list">
<li id="item_one" class="thud">List item 1</li>
<li>List item 2</li>
<li id="item_two" class="thud">List item 3</li>
</ul>
</body>
Функция $A() представляет собой удобный конструктор массивов, преобразуя к этому типу данных любую коллекцию объектов, принимаемую в качестве аргумента.
Функция $W() выполняет вполне банальную, но часто встречающуюся задачу – приведение строки к массиву. Тут всё просто:
w('apples bananas kiwis')
// возвращает массив ['apples', 'bananas', 'kiwis']
Функция $H() предназначена для работы с хэш-массивами. Она возвращает объект класса Hash и предоставляет методы для работы с ним.
Прежде всего это Hash.merge() – добавляющий новые ключ/значение, each() – перебирающий все ключи/значения массива, а также методы toJSON и toQueryString, назначения которых вполне ясно по названиям.
Ниже представлен пример работы функции $H и объекта Prototype.Hash.
var h = $H({ name: 'Prototype', version: 1.5 });
var h = new Hash({ ... });
h.keys().sort().join(', ')
h.merge({ version: '1.5 final', author: 'Sam Stephenson' });
h.each(function(pair) {
alert(pair.key + ' = "' + pair.value + '"'); });
$H({ action: 'ship', order_id: 123, fees: ['fee1', 'fee2'] }).toQueryString()
Кроме того, у объекта Hach есть исключительно полезный метод inspect(), позволяющий получить в браузере «читабельное» представление хэш-массива.
К приятным мелочам (хотя это, наверное, уже нечто по крупнее) я бы отнёс ещё и целый объект – PeriodicalExecuter, предоставляющий возможность периодического вызова заданной функции через указанный интервал. Его «родственник» – объект Ajax.PeriodicalUpdater выполняет такие же действия, с запросом к серверу, но об этом – позже.
Попытка за попыткой
Вообще процесс кроссбраузерной JavaScript-разработки всегда представлял некоторые трудности по причине неоднозначной реакции разных обозревателей на ошибки сценариев. В Prototype предусмотрен объект (функция) Try.these(), который сильно облегчает эту и другие задачи. Фактически он исполняет функции паттерна проектирования «Фасад». Он перебирает возможные варианты обработки и возвращает результат того обработчика, который оказался успешным. Пример его использования:
<script>
function getXmlNodeValue(xmlNode){
return Try.these(
function() {return xmlNode.text;},
function() {return xmlNode.textContent;)
);
}
</script>
В данном случае варианты перебора касаются различий браузеров в интерпретации DOM-модели, но аналогичный механизм может быть использован в обработке всех возможных ошибок.
Расширение объектов JavaScript
Основное предназначение Prototype – расширение возможностей для javascript-разработчика. Для этой благой цели стандартные объекты JavaScript обрели новые возможности (если быть формальным – методы). Полный список расширений можно получить, ознакомившись с документацией по библиотеке. Обозначим только некоторые из них.
Прежде всего «тюнингу» подвергся объект Event. С точки зрения скептика, конечно, использование Prototype не добавляет каких-либо новых, принципиальных возможностей в работу с этим объектом, но, безусловно, делает удобными и самое главное кроссбраузерными уже существующие.
Например, метод observe(element, name, observer, useCapture) добавляет обработчик события DOM-элементу, с учётом различий объектной модели браузеров.
Метод stopObserving(element, name, observer, useCapture) убирает это событие. Фактически эти методы дают реализацию паттерна проектирования «Обозреватель» в JavaScriot.
Метод findElement(event, tagName) находит в дереве DOM-объектов первый тег, совпадающий с параметром tagName, начиная с объекта, породившего событие.
Метод stop(event) останавливает распространение события.
Кроме этого, расширенный объект Event теперь имеет свойства, соответствующие нажатию специальных клавиш (таких, как <Tab> или <Esc>), а также статические методы обработки нажатия кнопки мыши и положения курсора на странице.
К классу Object были применены не совсем обычные расширения. Во-первых, это метод extend(object), позволяющий реализовать наследование (если, конечно, для объектов JavaScript этот термин вообще применим), а во-вторых, тот же метод с двумя аргументами – extend(destination, source), позволяющий копировать все свойства и методы объекта source в объект destination).
Впрочем, манипуляции с объектами (не слишком укладывающимися в классическую концепцию ООП), тема для отдельного разговора.
Класс Function дополнен методами, позволяющими связать данный экземпляр функции с объектами bind(object) и bindAsEventListener(object).
Не остался без внимания и класс String. Он дополнен методами, широко распространёнными в веб-программировании, но применяемыми на стороне сервера. Это escapeHTML()/unescapeHTML(), отвечающие за экранирование и восстановление HTML-разметки, и stripTags(), просто убирающим любые HTML- и XML-теги.
Обработка форм
Для облегчения этой часто встречающейся задачи в библиотеке предусмотрен объект Form с рядом полезных методов:
Form.getElements(form) возвращает массив значений элементов формы вне зависимости от их типа:
llNodes = Form.getElements("myform");
for(i = 0; i < allNodes.length; i++) {
//операции над каждым элементом
}
getInputs(form [, typeName [, name]]) возвращает массив значений элементов Input, причём с помощью необязательных элементов можно уточнить тип и имя элемента.
Функционал методов findFirstElement(form), focusFirst-Element(form), а также disable(form) и reset(form) вполне ясен из их названий.
Замечательный метод Form.serialize() помогает передавать данные на сервер, возвращая элементы формы в виде строки, параметров HTTP-запроса:
'field1=value1&field2=value2&field3=value3'
что очень удобно как при отправке сложных форм, так и для работы с AJAX.
Расширения класса Form, такие как класс Form.Element, Form.Observer, предоставляют дополнительные возможности для работы, но о них особый разговор.
Работа с JSON
JSON (JavaScript Object Notation), формат, который в настоящее время стал одним из стандартов обмена информацией между объектами javascript и в особенности в клиент-серверном взаимодействии. В некоторых случаях он удобней XML и гораздо экономичней, а следовательно – быстрее в работе.
В этом формате данные определяются коллекцией пар ключ/значение, которые можно представить, как хэш или ассоциативный массив.
Ниже приведён пример представления данных в нотации JSON. По-моему, довольно удобно и читаемо:
var g_books = [
{
id: 1,
name: 'Code Generation in Action',
first: 'Jack',
last: 'Herrington',
publisher: 'Manning'
}, {
id: 2,
name: 'PHP Hacks',
first: 'Jack',
last: 'Herrington',
publisher: 'O\'Reilly'
}, {
id: 3,
name: 'Podcasting Hacks',
first: 'Jack',
last: 'Herrington',
publisher: 'O\'Reilly'
}
];
Для работы с этим форматом Prototype даёт нам не только функции приведения к данной нотации хэш-массива (объект Hash) или получение/передачу в JSON обычной строки (объект String). В библиотеке присутствует очень полезная функция evalJSON (разумеется, объекта String), облегчающая работу с ответом сервера, сгенерированным в этой нотации.
Пример:
var person = '{ "name": "Violet", "occupation": "character" }'.evalJSON();
person.name;
person = 'grabUserPassword()'.evalJSON(true);
person = '/*-secure-\n{"name": "Violet", "occupation": "character"}\n*/'.evalJSON()
person.name;
Соответственно существует и обратное преобразование. Это метод unfilterJSON. Пример его работы:
'/*-secure-\n{"name": "Violet", "occupation": "character", "age": 25}\n*/'.unfilterJSON()
// получаем на выходе: '{"name": "Violet", "occupation": "character", "age": 25}'
С помощью расширения стандартного класса Object можно легко автоматизировать приведение к данной нотации:
var Person = Class.create();
Person.prototype = {
initialize: function(name, age) {
this.name = name;
this.age = age;
},
toJSON: function() {
return ('My name is ' + this.name +
' and I am ' + this.age + ' years old.').toJSON();
}
};
var john = new Person('John', 49);
Object.toJSON(john);
А как же Ajax?
Возможно, сейчас для многих разработчиков – это основная причина использования JavaScript-библиотек. Основная проблема обычно заключается в унификации запросов и получении ответов сервера, а также преодолении различий между браузерами.
Для работы с объектом XMLHttpRequest в Prototype существует объект Ajax, классы, наследуемые от которого стоит подробно рассмотреть.
В первую очередь это класс Ajax.Base, служащий основой для других классов, определённых в Ajax. Основным инструментом для формирования запросов к серверу является его наследник – класс Ajax.Request. Пример его использования:
<script>
function someAjax($url)
{
var myAjax = new Ajax.Request(
url,
{method: 'get',
parameters: {
type:'care',
name:'volga’
},
onLoaded:function(){
alert(‘test’)
}
onComplete: someFunction}
);
}
function someFunction (req)
{
$('tagert').value = req.responseText;
}
</script>
<div id
<br><textarea id="tagert" cols=100 rows=10 ></textarea>
<input type=button onlick="somrAjax">
Ничего не понятно? Сейчас разберёмся. Аргументы, передаваемые Ajax.Request, обозначены в нотации JSON (JavaScript Object Notation).
Первый параметр, который получает при создании Ajax.Request, это URL ресурса, к которому идёт запрос.
Второй – это параметры запроса, записанные в вышеупомянутой нотации JSON (это только один вариант, они могут быть переданы методу и в качестве строки, сформированной как URL).
Третий параметр, в данном случае функция onLoaded(), выполняемая при получении ответа сервера объекта, но на её месте мог быть и массив нескольких обработчиков с функциями, заданными пользователем:
var myOpts = {onComplete: showResponse,
onLoaded: registerLoaded};
Остальные параметры этого метода:
- requestHeader – список HTTP-заголовков в виде ассоциативного массива, где ключами выступают названия заголовков.
- onSuccess/onFilure – значениями этих полей являются пользовательские функции, выполняемые при успешном или неудачном выполнении запроса.
- Asynchronous – булевый индикатор асинхронности AJAX-запроса. По умолчанию имеет значение TRUE.
- postBody – содержит данные, передаваемые в теле HTTP-запроса.
Остальные ещё более интересные возможности содержатся в следующем классе, наследуемом от Ajax.Request, это класс Ajax.Udapter, который решает задачу по вставке возвращённого сервером HTML-кода в заданный DOM-объект. Ajax.Udapter очень удобен для динамического изменения содержания страницы. В дополнение к свойствам родительского класса он содержит и некоторые дополнительные.
Во-первых, это insertion (Object, String) – функция, которая вызывается для вставки полученного текста в объект, вызываемая с двумя аргументами – объект, в который будет производиться вставка текста и непосредственно сам текст, полученный в результате запроса.
Второе свойство – evalScripts определяет, будут ли выполняться скрипты, полученные в HTTP- ответе.
Конструктор этого объекта – Ajax.Updater (container, url, options) содержит новый аргумент – container, значение которого является идентификатором DOM-элемента, в который будет произведена вставка текста, полученного в результате запроса.
Ajax.PeriodicalUpdater, наследуемый от Ajax.Base, идёт ещё дальше. В вызове его конструктора используется аргумент container, являющийся идентификатором, или самим DOM-элементом, принимающим текст ответа на запрос. Кроме того, такие его методы, как start(), stop(), updateComplete(), и другие позволяют контролировать выполнение периодических задач в процессе исполнения запроса.
Для «низкоуровневой» работы с XMLHttpRequest в базовом классе Ajax предусмотрен метод getTransport(), который просто возвращает объект XMLHttpRequest. Разумеется, учитывая особенности используемого браузера.
Позиционирование
Размещать или перемещать в некотором заданном пространстве DOM (в частности HTML) элементы, уже давно вполне решаемая задача. Проблемы, правда, присутствуют – это опять пресловутое различие между браузерами и дикое количество кода (зачастую немотивированно повторяющегося), используемого в целях реализации сложного позиционирования. Для решения этих проблем библиотека предлагает объект Position. Его методы позволяют определить смещение любого DOM-элемента с учётом прокрутки и смещения родительских элементов. С позиционированием тесно связан класс Insertion, управляющий вставкой текста или DOM-элемента, в HTML-страницу. Названия его методов – After(), Before(), Bottom(), Top() уже говорят об их назначении.
Библиотеки на основе Prototype
Как уже говорилось, Prototype – это скорее не продукт для конечного пользователя, а framework для построения JavaScript-библиотек. Перечислю лишь некоторые из них. Библиотека Rico (http://openrico.org) предназначена для построения сложных визуальных и анимационных эффектов. На демо-странице проекта можно познакомиться и «поиграться» с её основными объектами. На рис. 1 показан компонент Rico.Accordion. Каждая горизонтальная панель раскрывает или закрывает свою вкладку с независимым содержимым. Фактически он помещает набор DOM-элементов в заданную разработчиками область с возможностью независимого доступа. На рис. 2 – компонент Rico.Color. Его работа, мне кажется, в пояснениях не нуждается.
Рисунок 1. Компонент Rico.Accordion. Вкладки на горизонтальной панели раскрывают независимое содержимое
Рисунок 2. Компонент Rico.Color. Цвет бара меняется в зависимости от введённых цифр
Библиотека script.aculo.us (http://script.aculo.us) также реализует многочисленные визуальные эффекты. Это перетаскивание (drag-and-drop) объектов по странице, динамическая сортировка, автозаполнение форм и т. д. Сайт библиотеки (см. рис. 3) – прекрасный образец её возможностей. В её составе три основных объекта: Sortable, обеспечивающий динамическое перемещение объектов, Controls, реализующий интерактивные элементы управления и Effect – набор самых различных визуальных эффектов, таких как Effect.Pulsate(), заставляющих видимый объект вибрировать, или Effect.Puff(), представляющий собой красивый способ растворить объект в воздухе. Полезна также функция Parallel(), позволяющая одновременную работу нескольких визуальных эффектов. Жалко, что страница журнала не поддерживает DOM-модель и не даёт возможности проиллюстрировать вышесказанное, поэтому рекомендую посетить сайт этого проекта.
Рисунок 3. Главная страница сайта библиотеки script.aculo.us
Можно упомянуть ещё небольшую библиотеку Prototype window. Это дополнительный класс, позволяющий создавать симпатичные окошки, которые вы видите на рис. 4 и некоторые другие объекты (модальные диалоги, динамические визуальные эффекты).
Рисунок 4. Окошки, созданные с помощью библиотеки Prototype window
При разработке приложения для получения подобных окон нужно написать всего две-три строки кода:
var win = new Window({className: "spread", title: "Ruby on Rails", top:70, left:100, width:300, height:200, \
url: "http://www.rubyonrails.org/", showEffectOptions: {duration:1.5}}) win.show();
Наконец, Prototype используют такие мощные framework, как Ruby On The Rails и Symfony (популярный web application framework, для PHP5 http://www.symfony-project.com).
Что дальше?
Честно говоря, описав возможности библиотеки (или framework) Prototype, я совсем «забыл» сказать о самом главном – возможности создания собственных полноценных объектов и гибкого управления ими. Об этом – в следующий раз.
В статье была использована документация библиотеки Prototype (http://www.prototypejs.org/api), а также неофициальное руководство разработчика, составленное Sergio Pereira (http://www.sergiopereira.com/articles/prototype.js.html) и доступное теперь на русском языке в переводе, выполненным Виктором Кроллом (http://kropp.spb.ru/docs/prototype).
Facebook
Мой мир
Вконтакте
Одноклассники
Google+
|