Различные всплывающие окошки, выполненные на JavaScript (не путать с pop-up окнами, вызываемыми при window.open), становятся все более распространенными. Чаще всего они используются для динамического отображения подсказок, диалогов, да и просто рекламы (что зачастую раздражает
). Сегодня я расскажу то, как можно сделать такие окна при помощи популярной библиотеки Mootools. Конечный результат будет выглядеть следующим образом:
Как видите дизайн окон несколько позаимствован из ExtJS.
Итак, начнем с основы - определимся с HTML разметкой окна. Схематически она показана следующем рисунке:
Основным контейнером для окна будет <div class=”dWindow”>, для которого установлено абсолютное позиционирование (чтобы можно было перетаскивать окно). Строка заголовка будет также выполнена в виде div-а, а вот содержимое окна и его строка состояния будут сделаны в виде элементов таблицы. На мой взгяд, таким образом легче создать границы (кромки) окна.
Теперь, определившись со структурой окна, можно писать код. Поскольку нам нужно создавать не одно окно, а множество, то реализуем это в виде класса:
var dWindow = new Class({
options: {
minWidth : 150,
minHeight : 150,
width : 200,
height : 200,
top : 0,
left : 0,
resizable : true,
statusBar : true,
content : '',
id : ''
},
// конструктор класса
initialize: function(options){
this.setOptions(options);
}
Implements : [Options, Events]
});
Это стандартная структура класса в Mootools. Перечисленные опции options определяют свойства окна - размеры, положение, и др. Теперь нам нужно сделать функцию, которя будет создавать окно и добавлять его в нужный нам элемент. Делаем её следующим образом (функция _create()):
var dWindowZIndex = 100;
var dWindow = new Class({
options: {
minWidth : 150,
minHeight : 150,
width : 200,
height : 200,
top : 0,
left : 0,
resizable : true,
statusBar : true,
content : '',
id : ''
},
handle : null,
drag : null,
dragParams : {},
initialize: function(options){
this.setOptions(options);
},
_create : function(id){
var target = $(id);
if (!target)
return;
this.handle = new Element('div', {
'class':'dWindow',
'id' : this.options.id,
styles : {
'width':this.options.width + 2,
'left':this.options.left,
'top':this.options.top,
'z-index':dWindowZIndex
}
});
dWindowZIndex++;
this.handle.addEvent('mousedown', this.up.bind(this));
var bar = new Element('div', {'class':'topBar'});
var closeBtn = new Element('div', {'class':'closeBtn'});
closeBtn.addEvent('click', this.close.bind(this));
closeBtn.injectInside(bar);
var table = new Element('table',{
'class':'dContainer',
'cellpadding':0,
'cellspacing':0,
'border':0
});
var tbody = new Element('tbody');
// первый ряд (содержимое окна, левая и правая границы)
var row = new Element('tr');
var leftBorder = new Element('td', {'rowspan':2,'class':'leftBorder'});
var rightBorder = new Element('td', {'rowspan':2,'class':'rightBorder'});
var center = new Element('td');
var content = new Element('div',{'class':'centralArea', styles : {'width':this.options.width, 'height':this.options.height}});
content.set('html',this.options.content);
center.adopt(content);
row.adopt([leftBorder, center, rightBorder]);
// второй ряд (строка состояния)
var row2 = new Element('tr');
var statusBar = new Element('td',{'class':'statusBar'});
var brResize = new Element('div', {'class':'resize'});
brResize.injectInside(statusBar);
statusBar.injectInside(row2);
// третий ряд (с нижней кромкой)
var row3 = new Element('tr');
var bottomBorder = new Element('td', {'colspan':3,'class':'bottomBorder'});
bottomBorder.injectInside(row3);
row.injectInside(tbody);
row2.injectInside(tbody);
row3.injectInside(tbody);
tbody.injectInside(table);
this.handle.adopt([bar, table]);
this.handle.injectInside(target);
},
Implements : [Options, Events]
});
Функция _create() довольно таки незатейлива (пока) и все что она делает, это создает все элементы окна при помощи класса Element и добавляет их в основной контейнер окна (функции injectInside() и adopt()), создаваемый тут же. Помимо этого кнопке "Закрыть" назначается обработчик close() (будет создан позже), который будет закрывать окно. В конце концов, в последней строке функции, созданное окно добавляется в элемент target, имеющийся на странице, ссылка на который должна передаваться функции в качестве аргумента.
Также обратите внимание на глобальную переменную dWindowZIndex, которая нужна для хранения текущего максимального значения z-index окна. Каждый раз, когда пользователь будет кликать внутри окна, это значение будет увеличиваться на единицу и назначаться текущим значением z-index окна. Обработчик, который будет выполнять эту операцию - функция up() - назначается событию mousedown основного контейнера окна. Все эти действия направлены на то, чтобы дать пользователю возможность выбрать окно, переместив его поверх всех других при щелчке на нем.
Итак, на данном этапе, функция _create() создает окно, которое имеет следующую структуру:
<div class="dWindow" style="width: 262px; left: 236px; top: 74px; z-index: 106; position: absolute;"> <div class="topBar"><div class="closeBtn"/></div> <table cellspacing="0" cellpadding="0" border="0" class="dContainer"> <tbody> <tr> <td rowspan="2" class="leftBorder"></td> <td> <div class="centralArea" style="width: 260px; height: 260px;"></div> </td> <td rowspan="2" class="rightBorder"></td> </tr> <tr> <td class="statusBar"><div class="resize"/></td> </tr> <tr> <td colspan="3" class="bottomBorder"></td> </tr> </tbody> </table> </div>
Теперь дополним наш класс недостающими функциями:
_destroy : function(){
if ($type(this.handle) == 'element'){
this.handle.destroy();
}
this.handle = null;
},
open : function(elem){
if (!this.handle)
this._create(elem);
},
close : function(){
this._destroy();
},
up : function(){
this.handle.setStyle('z-index', ++dWindowZIndex);
}
Здесь указаны упомянутые выше функции up() и close(), а также функция open(), которая проверят, создано ли окно или нет, ежели нет, то оно создается функцией _create(). Функция _destroy(), противоположная последней, закрывает окно, уничтожая экземпляр класса dWindow.
Следующим этапом нашей разработки будет создание таблиц стилей для окна. Не особо углубляясь в детали реализации CSS верстки, ибо она довольно таки тривиальна, я просто приведу окончательный её результат:
.dWindow {
background: #aaa;
position:absolute;
z-index:10000;
}
.dWindow .topBar{
height: 20px;
background: #bad0ee url('white-top-bottom.gif') repeat-x;
border-left: 1px solid #84a0c4;
border-right: 1px solid #84a0c4;
}
.dWindow .closeBtn {
position: absolute;
top: 3px;
right: 3px;
width: 16px;
height: 16px;
cursor: pointer;
background: url('close.gif') no-repeat;
}
.dWindow td.leftBorder {
width:2px;
background:#fff url('border-left.gif') repeat-y;
cursor:w-resize;
}
.dWindow td.rightBorder {
width:2px;
background:#fff url('border-right.gif') repeat-y;
cursor:e-resize;
}
.dWindow td.bottomBorder {
height:2px;
background: #fff url('border-bottom.gif') repeat-x;
cursor:s-resize;
}
.dWindow .centralArea {
width: 200px;
height: 200px;
background: #fafafa;
vertical-align: top;
overflow: auto;
}
.dWindow td.statusBar {
position: relative;
height: 16px;
background: #d5e4f5;
}
.dWindow .resize {
position: absolute;
width: 12px;
height: 12px;
right:2px;
bottom:2px;
background: #d5e4f5 url('resize.gif') no-repeat bottom right;
cursor: se-resize;
font-size:1px;
line-height:0;
}
.dragging {
cursor: move;
}
Теперь мы приступаем к последнему шагу нашего туториала - добавим возможность перетаскивать окна и изменять их размер. В Mootools есть классы Drag и Drag.Move, которые предназначены для создания перетаскиваемых элементов. Ими мы и воспользуемся.
Класс Drag.Move, помимо прочего, отличается от Drag тем, что он позволяет ограничивать перетаскивание объекта рамками некоторого элемента. Используя это свойство, мы будем использовать Drag.Move для главного контейнера окна для того, чтобы ограничить перетаскивание пределами страницы.
Итак, создаём новый объект Drag.Move, указав для него главный контейнер окна this.handle (делаем это все в функции _create()):
this.drag = new Drag.Move(this.handle, {
snap: 0,
handle: bar,
container: target,
onSnap: function(el){
el.addClass('dragging');
},
onComplete: function(el){
el.removeClass('dragging');
}
});
В качестве параметра handle используем строку заголовка окна (переменная bar), ибо, именно за него окно и будет перетаскиваться. В параметр container передаем указатель на элемент страницы, который будет ограничивать перетаскивание окна (если передать сюда body, то перетаскивание будет ограничиваться размерами страницы). Стандартные для Drag.Move обработчики onSnap и onComplete используем для того, чтобы изменять вид курсора при перетаскивании окна.
Закончив с перетаскиванием окна, поработаем с его границами, перетаскиванием которых можно будет изменять размеры окна. Это будет несколько более сложно сделать, поскольку при каждом движении границы, нужно будет пересчитывать размеры окна. Для этой цели идеально подходят события onSnap (захват мышкой за кромку) и onDrag (шаг при перетаскивании) класса Drag, который мы будем использовать для границ. Реализация изменения размера по вертикали (нижняя граница) показана в следующем фрагменте:
var that = this;
this.bDrag = new Drag(bottomBorder, {
onSnap : function(elem){
that.dragParams.h = content.getSize().y;
that.dragParams.H = that.handle.getSize().y;
that.dragParams.target = content;
},
onDrag : function(elem){
var delta = this.mouse.now.y - this.mouse.start.y;
var h = that.dragParams.h + delta;
var H = that.dragParams.H + delta;
var targetPos = that.dragParams.target.getPosition();
if (h < that.options.minHeight || (H + targetPos.y) > (window.getScrollTop() + window.getHeight())) {
return;
}
that.dragParams.target.setStyle('height', h);
that.handle.setStyle('height', H);
}
});
Для изменения размера окна по вертикали, нам необходимо изменять высоту окна (<div class=”dWindow”> - переменная handle) и размер div-а для содержимого окна (<div class=”centralArea”> - переменная content). В обработчике onSnap мы получаем высоту этих двух элементов, а в onDrag определяем, на какое расстояние сместился курсор мыши (delta), и меняем высоту элементов на полученное значение. Условный оператор в onDrag добавлен для ограничения изменения размера в соответствии со значением minHeight (минимальная высота) и с размерами страницы.
Остальные границы делаем в том же ключе, за небольшим исключением левой границы, где помимо изменения ширины окна, нужно также изменять и положение окна на странице (left):
var that = this;
// левая граница
this.lbDrag = new Drag(leftBorder, {
onSnap : function(elem){
that.dragParams.w = content.getSize().x;
that.dragParams.W = that.handle.getSize().x;
that.dragParams.pos = content.getPosition();
that.dragParams.target = content;
},
onDrag : function(elem){
var delta = this.mouse.now.x - this.mouse.start.x;
var w = that.dragParams.w - delta;
var W = that.dragParams.W - delta;
var L = this.mouse.start.x + delta;
if (w < that.options.minWidth || L < target.getPosition().x) {
return;
}
that.dragParams.target.setStyle('width', w);
that.handle.setStyle('width', W);
that.handle.setStyle('left', L);
}
});
// правая граница
this.rbDrag = new Drag(rightBorder, {
onSnap : function(elem){
that.dragParams.w = content.getSize().x;
that.dragParams.W = that.handle.getSize().x;
that.dragParams.target = content;
},
onDrag : function(elem){
var delta = this.mouse.now.x - this.mouse.start.x;
var w = that.dragParams.w + delta;
var targetPos = that.dragParams.target.getPosition();
if (w < that.options.minWidth || (W + targetPos.x) > (window.getScrollLeft() + window.getWidth())) {
return;
}
var W = that.dragParams.W + delta;
that.dragParams.target.setStyle('width', w);
that.handle.setStyle('width', W)
}
});
Ну и последний элемент, это ползунок в правом нижнем углу, который позволяет одновременно изменять размер окна и по вертикали и по горизонтали. Принцип действия тот же самый что и для правой и нижней границы, только выполняется одновременно:
var that = this;
this.resizeDrag = new Drag(brResize, {
preventDefault: true,
style: false,
onSnap : function(elem){
that.dragParams.h = content.getSize().y;
that.dragParams.H = that.handle.getSize().y;
that.dragParams.w = content.getSize().x;
that.dragParams.W = that.handle.getSize().x;
that.dragParams.target = content;
},
onDrag : function(){
var deltay = this.mouse.now.y - this.mouse.start.y;
var deltax = this.mouse.now.x - this.mouse.start.x;
var h = that.dragParams.h + deltay;
var H = that.dragParams.H + deltay;
var w = that.dragParams.w + deltax;
var W = that.dragParams.W + deltax;
var targetPos = that.dragParams.target.getPosition();
if (h < that.options.minHeight || (H + targetPos.y) > (window.getScrollTop() + window.getHeight())
|| w < that.options.minWidth || (W + targetPos.x) > (window.getScrollLeft() + window.getWidth())) {
return;
}
that.dragParams.target.setStyle('height', h);
that.handle.setStyle('height', H);
that.dragParams.target.setStyle('width', w);
that.handle.setStyle('width', W);
}
});
Вот собственно и все, окно готово. Создание экземпляра окна выглядит так:
// содержимое окна
var sText = '<p class="para">Lorem ipsum dolor sit amet, consectetuer adipiscing elit.</p>';
window.addEvent('domready', function(){
// создаем окно
var w = new dWindow({
width:250,
height:180,
minWidth:170,
minHeight:100,
content: sText
});
// добавляем его на страницу
w.open($(document.body));
});
Посмотреть как все это работает можно на демо примере, где показано создание множество окон по щелчку на ссылку.
На сегодня это все. Буду признателен за конструктивную критику
Удачи!