Рубрика:
Программирование /
Веб-программирование
|
Facebook
Мой мир
Вконтакте
Одноклассники
Google+
|
Кирилл Сухов
Библиотека Prototype – ваш путь в Web 2.0
Часть 2: практика
Библиотека Prototype предоставляет огромные возможности веб-разработчику. Описывать все её классы и методы довольно длительное занятие, лучше попробовать освоить их на практике.
В первой части статьи (см. №7 за 2007 г.) я провел краткий обзор основных функций библиотеки Prototype, к сожалению, недостаточно полный. Во второй части я уже собирался наверстать упущенное, но первые же читатели обратили внимание на тот факт, что в статье не было приведено ни одного внятного примера работы библиотеки. Так что сейчас мы будем рассматривать конкретную программу, созданную на её основе.
Для иллюстрации я выбрал Window Prototype – библиотеку, уже упомянутую в первой части. Конечно, можно было попробовать написать собственное приложение, но боюсь, что его объём значительно увеличит статью. В данном случае я могу не приводить полные варианты кода – читатель всегда может ознакомиться с первоисточником. Те функции Prototype, которые остались за рамками обзора, будут освещаться по мере развития событий.
Итак, наше приложение должно создавать окна, причём окна полноценные – со всеми элементами управления, с заданным контентом, которым может служить как произвольный URL, так и какое-либо сгенерированное сообщение. Кроме того, мы будем писать библиотеку, с понятным и расширяемым интерфейсом (какой и является Window Prototype).
Теперь начнём
Сначала главная HTML-страница нашего Prototype-приложения:
<html>
<head>
<title>Окошки</title>
<script type="text/javascript" src="/javascripts/prototype.js"> </script>
<script type="text/javascript" src="/javascripts/win.js"> </script>
<link href="/themes/default.css" rel="stylesheet" type="text/css">
<link href="/themes/alphacube.css" rel="stylesheet" type="text/css">
</head>
<body>
<script type="text/javascript">
function open_window(id){
win1 = new Window(id, {className: "alphacube", title: "Window1", width:200, height:150, top:200, left:100});
}
</script>
<center><a href="#" onclick="win1.show();">open window</a></center>
</body>
</html>
Сначала подключается JavaScript-файл с самой библиотеки Prototype (prototype.js), затем файл с нашим сценарием, который ещё предстоит написать. При клике по ссылке «open window» будет вызван сценарий, открывающий желаемое окно. Он создаёт объект Window со свойствами, заложенными в аргументах нового объекта. Первый из них это идентификатор создаваемого объекта, второй – записанный в формате JSON массив его свойств.
Подключаемые файлы каскадных таблиц стилей (default.css и spread.css) были взяты также из Window Prototype и имеют смысл только для наглядности, визуализации создаваемых объектов. Их код я здесь приводить не буду. Теперь осталась сущая ерунда – создать класс, на основе которого создаётся объект, и описать его поведение. Вперёд, препарируем библиотеку и создаём сценарий в файле win.js.
Начнём с создания нашего объекта:
var Window = Class.create();
Тут необходимы пояснения. Объект Class используется для объявления пользовательских классов библиотеки. По аналогии с Java он имеет метод initailize(), используемый как конструктор, cамо объявление класса осуществляется методом create(), также знакомым по высокоуровневым ООП языкам программирования.
Теперь определим реализацию класса:
Window.prototype = {initialize: function() {
var optionIndex = 0;
if (arguments.length > 0) {
if (typeof arguments[0] == "string" ) {
id = arguments[0];
optionIndex = 1;
}
else
id = arguments[0] ? arguments[0].id : null;
}
...............
Тут, мне кажется, всё должно быть понятно – мы просто оставляем возможность опустить первый аргумент (идентификатор). Это понадобится для того, чтобы создавать произвольное количество объектов. Впрочем, без id всё равно не обойтись, и он будет сгенерирован:
if (!id)
id = "window_" + new Date().getTime();
Далее перечисляем желаемые свойства объекта:
this.options = Object.extend({
className: "dialog",
blurClassName: null,
minWidth: 100,
minHeight: 20,
resizable: true,
closable: true,
minimizable: true,
maximizable: true,
draggable: true,
userData: null,
showEffect: (Window.hasEffectLib ? Effect.Appear : Element.show),
hideEffect: (Window.hasEffectLib ? Effect.Fade : Element.hide),
showEffectOptions: {},
hideEffectOptions: {},
effectOptions: null,
parent: document.body,
title: " ",
url: null,
onload: Prototype.emptyFunction,
width: 200,
height: 300,
opacity: 1,
recenterAuto: true,
wiredDrag: false,
closeCallback: null,
destroyOnClose: false,
gridX: 1,
gridY: 1
}, arguments[optionIndex] || {});
Мы сразу задали все свойства окна, некоторые из них в нашей конкретной задаче точно не понадобятся, но давайте не забывать, мы пишем не приложение, а библиотеку. Надо заметить, что JavaScript, довольно демократичный язык, допускающий многие «вольности». В числе таковых неопределённое количество аргументов функции. Использовав объект options, мы создали массив свойств нашего окна, которые используются, если явно не указаны при его иницмализации.
Далее задаём область, в которой будет создаваться окно (по умолчанию это document.body):
if (this.options.parent != document.body)
this.options.parent = $(this.options.parent);
Теперь создаём окно:
this.element = this._createWindow(id);
this.element.win = this;
Далее в конструкторе стоит задать некоторые методы, но это отложим, пока установим только размеры и заголовок:
if (this.width && this.height)
this.setSize(this.options.width, this.options.height);
this.setTitle(this.options.title)
На этом конструктор пока закончим:
Windows.register(this);
},
Смысл последней строчки будет ясен позже, объект Windows мы ещё опишем.
Теперь очередь метода _createWindow(). Тут всё довольно просто. Создаётся объект, представляющий собой HTML-код (три таблицы), каждому элементу которого присвоено стандартное имя класса (производное от базового):
_createWindow: function(id) {
var className = this.options.className;
var win = document.createElement("div");
win.setAttribute('id', id);
win.className = "dialog";
var content;
if (this.options.url)
content= "<iframe frameborder=\"0\" name=\"" + id + "_content\" id=\"" + id + "_content\" src=\"" + this.options.url + "\"> </iframe>";
else
content ="<div id=\"" + id + "_content\" class=\"" +className + "_content\"> </div>";
var closeDiv = this.options.closable ? "<div class='"+ className +"_close' id='"+ id +"_close' onclick='Windows.close(\""+ id +"\", event)'> </div>" : "";
var minDiv = this.options.minimizable ? "<div class='"+ className + "_minimize' id='"+ id +"_minimize' onclick='Windows.minimize(\""+ id +"\", event)'> </div>" : "";
var maxDiv = this.options.maximizable ? "<div class='"+ className + "_maximize' id='"+ id +"_maximize' onclick='Windows.maximize(\""+ id +"\", event)'> </div>" : "";
var seAttributes = this.options.resizable ? "class='" + className + "_sizer' id='" + id + "_sizer'" : "class='" + className + "_se'";
var blank = "../themes/default/blank.gif";
win.innerHTML = closeDiv + minDiv + maxDiv + "\
<table id='"+ id +"_row1' class=\"top table_window\">\
<tr>\
<td class='"+ className +"_nw'></td>\
<td class='"+ className +"_n'><div id='"+ id +"_top' class='"+ className +"_title title_window'>"+ this.options.title +"</div></td>\
<td class='"+ className +"_ne'></td>\
</tr>\
</table>\
<table id='"+ id +"_row2' class=\"mid table_window\">\
<tr>\
<td class='"+ className +"_w'></td>\
<td id='"+ id +"_table_content' class='"+ className +"_content' valign='top'>" + content + "</td>\
<td class='"+ className +"_e'></td>\
</tr>\
</table>\
<table id='"+ id +"_row3' class=\"bot table_window\">\
<tr>\
<td class='"+ className +"_sw'></td>\
<td class='"+ className +"_s'><div id='"+ id +"_bottom' class='status_bar'><span style='float:left; width:1px; height:1px'></span></div></td>\
<td " + seAttributes + "></td>\
</tr>\
</table>\
";
Event.observe($(id + "_content"), "load", this.options.onload);
return win;
},
Структура окна, конечно, может быть другой, но я хочу обратить внимание на ключевые моменты:
- все элементы окна помещены в один (родительский) элемент div, снабженный уникальным идентификатором;
- ко всем элементам управления (стандартные «свернуть», «развернуть», «закрыть», «перетащить») добавлены обработчики событий (да, да их ещё предстоит создать);
- окно может отображать, как заданный URL, так и сгенерированный контент.
Результат мы можем уже наблюдать (см. рис. 1), но он пока не очень впечатляет. В самом деле, зачем было писать столько строк кода, когда то же самое можно получить простейшими DHTML-приёмами? На самом деле все нормально. Кода придется написать ещё втрое больше, но в прототипе мы сразу же заложили возможности для любых манипуляций с объектом-окном. Правда, окно мы не собираемся создавать в одном экземпляре (а какой смысл?). Для работы с множеством окон мы создадим ещё один объект, который разработчики Windows Prototipe назвали не слишком оригинально.
Рисунок 1. Окошко появилось
Итак, объект Windows:
var Windows = {
windows: [],
observers: [],
focusedWindow: null,
maxZIndex: 0,
overlayShowEffectOptions: {duration: 0.5},
overlayHideEffectOptions: {duration: 0.5},
addObserver: function(observer) {
this.removeObserver(observer);
this.observers.push(observer);
},
removeObserver: function(observer) {
this.observers = this.observers.reject( function(o) { return o==observer });
},
...
}
Задача данного объекта хранить сведения о коллекции объектов Window и переадресовывать события к обработчику для заданного окна. Как вполне ясно из кода, в массиве windows сохраняются все созданные объекты-окошки, в массиве observers – заданные обработчики.
Методы addObserver и removeObserver оперируют с обработчиками событий, добавляя или убирая их у объектов.
Сюда же добавляем два необходимых для манипуляций с объектами метода:
notify: function(eventName, win) {
this.observers.each( function(o) {if(o[eventName]) o[eventName](eventName, win);});
},
getWindow: function(id) {
return this.windows.detect(function(d) { return d.getId() ==id });
},notify: function(eventName, win) {
this.observers.each( function(o) {if(o[eventName]) o[eventName](eventName, win);});
},
getWindow: function(id) {
return this.windows.detect(function(d) { return d.getId() ==id });
},
Последний метод, как вы помните, вызывается конструктором при создании окна, реализация getId() и других вспомогательных манипуляций в данном случае принципиально не важна.
Осталось задать поведение создаваемых объектов, прописав все необходимые методы в объекте Windows и их реализацию в объекте Window. Естественно, мы это не будем делать в полном объёме (тем более что разработчики Windows Prototipe давно обо всём позаботились), но кое-что попробуем.
Оживляем объект
Прежде всего нужно связать составляющие созданного объекта с событиями (наведением мыши, перетаскиванием, кликами). Опять-таки это будет несколько сложнее, чем в традиционном DHTML, но зато и возможностей будет больше. Сначала добавим в конструктор следующие строки:
this.eventMouseDown = this._initDrag.bindAsEventListener(this);
this.eventMouseUp = this._endDrag.bindAsEventListener(this);
this.eventMouseMove = this._updateDrag.bindAsEventListener(this);
this.eventOnLoad = this._getWindowBorderSize.bindAsEventListener(this);
this.eventMouseDownContent = this.toFront.bindAsEventListener(this);
this.eventResize = this._recenter.bindAsEventListener(this);
this.topbar = $(this.element.id + "_top");
this.bottombar = $(this.element.id + "_bottom");
this.content = $(this.element.id + "_content");
Event.observe(this.topbar, "mousedown", this.eventMouseDown);
Event.observe(this.bottombar, "mousedown", this.eventMouseDown);
Event.observe(this.content, "mousedown", this.eventMouseDownContent);
Event.observe(window, "load", this.eventOnLoad);
Event.observe(window, "resize", this.eventResize);
Event.observe(window, "scroll", this.eventResize);
Event.observe(this.options.parent, "scroll", this.eventResize);
Всё, теперь можно описывать поведение объектов. Для примера возьмём «сворачивание» окна при нажатии мышкой на соответствующую кнопку.
Для достижения этого эффекта сначала пропишем соответствующий метод объекта Windows:
minimize: function(id, event) {
var win = this.getWindow(id)
if (win && win.visible)
win.minimize();
Event.stop(event);
},
Затем его реализацию в объекте Window:
minimize: function() {
if (this.resizing)
return;
var r2 = $(this.getId() + "_row2");
if (!this.minimized) {
this.minimized = true;
var dh = r2.getDimensions().height;
this.r2Height = dh;
var h = this.element.getHeight() - dh;
if (! this.useTop) {
var bottom = parseFloat(this.element.getStyle('bottom'));
this.element.setStyle({bottom: (bottom + dh) + 'px'});
}
}
else {
this.minimized = false;
var dh = this.r2Height;
this.r2Height = null;
if (this.useLeft && this.useTop && Window.hasEffectLib && Effect.ResizeWindow) {
new Effect.ResizeWindow(this, null, null, null, this.height + dh, {duration: Window.resizeEffectDuration});
}
else {
var h = this.element.getHeight() + dh;
this.height += dh;
this.element.setStyle({height: h + "px"})
r2.show();
}
if (! this.useTop) {
var bottom = parseFloat(this.element.getStyle('bottom'));
this.element.setStyle({bottom: (bottom - dh) + 'px'});
}
this.toFront();
}
},
Я понимаю, что выглядит всё не очень прозрачно, но тем не менее описывать методы вроде show(), getHeight() или toFront() не буду (их реализацию читатель всегда может посмотреть в исходниках). Объект Effect и метод ResizeWindow являются специфичными для Windows prototype, я бы с удовольствием разобрал их работу, но объём статьи при этом возрастёт в несколько раз. Чтобы воспользоваться нашим объектом, нужно только всего ничего – прописать все остальные методы, задающие его поведение.
Работаем с библиотекой
Теперь представим, что мы прописали все методы и свойства и можем пожинать плоды нудной работы (пусть и не нашей, но всё же). Сначала чуть модифицируем вызов нашего окошка:
function open_window(id){
win1 = new Window(id, {className: "alphacube", title: "Window1", width:200, height:150, top:200, left:100});
win1.getContent().innerHTML = "<h1>Preved</h1>";
win1.show();
}
Теперь окно живёт – его можно свернуть, развернуть на весь экран, перетащить, наконец, закрыть. Все свойства окна можно установить посредством вызова методов, что и делаем, наполняя окно чрезвычайно полезным контентом (см. рис. 2).
Рисунок 2. Hellow Word
Теперь создадим окно на основе другого класса (см. рис. 3):
function next_window(){
win2 = new Window( {className: "spread", title: "Window2",opacity:0.5, width:200, height:150, top:200, left:100});
win2.getContent().innerHTML = "<h1>Preved</h1>";
win2.show();
}
Рисунок 3. Меняем стиль, прозрачность
Поскольку мы не задали жёстко идентификатор объекта, создать таких окон можно сколько угодно и сколько угодно издеваться над ними (см. рис. 4, 5).
Рисунок 4. Размножаемся
Рисунок 5. Трансформация окон
В конце концов создадим самое правильное окно:
win2 = new Window( {className: "spread", title: "samag",opacity:0.5, width:200, height:150, top:200, left:100, url=”http://samag.ru”});
Результат представлен на рис. 6.
Рисунок 6. Загрузка правильного URL
Документация
Несмотря на то, что библиотека Prototype создана и активно используется довольно давно, до самого последнего времени она не имела ни официальной документации, ни даже полноценной домашней страницы. К счастью, сейчас эти проблемы решены – на сайте библиотеки http://www.prototypejs.org/api доступно описание классов и методов.
Джонатан Снук (Jonathan Snook, http://www.snook.ca) проделал колоссальную работу, собрав элементы библиотеки в единую диаграмму, отражающую связь и иерархию объектов Prototype. Рекомендую использовать её для изучения структуры библиотеки (см. рис. 7).
Рисунок 7. Связь и иерархия объектов библиотеки Prototype
Приложения на JavaScript?
В первой части этого опуса я упомянул о нескольких библиотеках, созданных на основе prototype. Они впечатляют своими возможностями, но всё же это библиотеки – инструменты разработчика, до которых нет дела конечному пользователю.
В качестве иллюстрации готового JavaScript-приложения я собирался привести несколько примеров, но в процессе подготовки наткнулся на один, самый эффектный на настоящее время, которым решил и ограничиться. Знакомьтесь – Cumulate Draw (см. рис. 8).
Рисунок 8. Работа сервиса Cumulate Draw
Честно говоря, будь я разработчиком этого сервиса, я непременно брал бы деньги за его использование. Это вполне достойная замена среды MSVisio прямо в окне браузера. Конечно, сказать, что данное приложение целиком сделано на основе Prototype, нельзя, скорее всего, оно создано с использованием этой библиотеки. Тем не менее серверная часть составляет в ней явно не так много, основная задача по реализации функционала приходится на интерфейс, в чём можно убедиться, посмотрев исходники на JavaScript, которые вполне доступны для изучения.
Я думаю, наличие таких приложений не оставляет сомнения в возможностях и библиотеки Pritotype.
- Библиотека Window Prototype – http://prototype-window.xilinus.com/index.html.
- Сервис Cumulate Draw – http://www.cumulatelabs.com/draw/draw.html?release=0.4.7.
Facebook
Мой мир
Вконтакте
Одноклассники
Google+
|