690
 

Туториал: Создание окон на Mootools

Различные всплывающие окошки, выполненные на JavaScript (не путать с pop-up окнами, вызываемыми при window.open), становятся все более распространенными. Чаще всего они используются для динамического отображения подсказок, диалогов, да и просто рекламы (что зачастую раздражает :) ). Сегодня я расскажу то, как можно сделать такие окна при помощи популярной библиотеки Mootools. Конечный результат будет выглядеть следующим образом:

Окна на 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));
});

Посмотреть как все это работает можно на демо примере, где показано создание множество окон по щелчку на ссылку.
На сегодня это все. Буду признателен за конструктивную критику :)
Удачи!



смотреть демоскачать

Добавить в закладки:
Maklay.com - Большой каталог товаров для спорта и активного отдыха

Комментарии на “Туториал: Создание окон на Mootools”

  1. Конструктивную критику говорите :) ну что ж,посмотрим…в любом случае уже выражаю уважение за работу,сейчас скачаю демку,посмотрю)

  2. Отличненько =) Спасибо за отличный полезненький скриптец))

  3. Кнопочки свернуть-развернуть не хватает.

  4. @Serg_pnz
    Вы угадали )). Это тема одного из следующих постов.

  5. Ну тогда уж и построение каскадом и т.п.))

    Немного не в тему: насколько сложно с 1.11 перейти на 1.2?

  6. По моему несложно, уже приходилось этим заниматься. Я делал это так: просто работал со скриптом и смотрел в фаербаге, какие он ошибки выбрасывает, далее исправлял ошибки заменяя устаревшие функции на новые.
    Есть еще скрипт для автоматической совместимости, но я пока его не пробовал использовать:
    compat-script

  7. если туда загрузить фрейм, то окошко двигается с трудом((( в ie7

  8. ИЕ вообще тяжко с этой библиотекой работает, но всё равно она мне нравится и пользуюсь только ей.
    Пример чата на mootools http://www.jcase.ru/chat/

  9. @Serg_pnz
    У меня ощущение, что ИЕ со всеми либами одинаково тормознуто работает. А мутулз мне больше всех нравится своей простотой. Кроме того, там можно на ООП писать виджеты.

  10. Автор конечно молодец и продел большую работу, но как это зачастую бывает изобретаем велосипеды… WinDoo есть такая штука, кто не в курсе

    http://windoo.110mb.com/

  11. @alekciy
    Вообще-то, это туториал.

  12. “А слона то я и не заметил” (с) Впрочем не удивительно, в каментах народ обсуждает это как работающий проект и ожидает новых фич. Так что кому просто нужны окна могут идти по ссылке )

  13. alekciy, Вы не правы.
    И прежде, чем идти по ссылке, нужно хоть немного представлять как этот мутулз работает.
    Может и есть подобные окна, но не приятнее ли сделать что-то своими руками, понимая что ты делаешь!?
    А то обвешаешь фреймовиками сайт… Причем разными или дважды одним и тем же…
    Умнее, alekciy, Вам было бы промолчать. Крестик у браузера еще никто не отменял…
    Опять же ссылка, которую Вы дали… Нифига там не понятно. И если уж куда ссылаться по поводу окон, так вот сюда http://mochaui.com/demo/

  14. @Serg_pnz
    Это приятно делать и главное полезно только на стадии обучения. А когда ты работаешь с нативным кодом так же легко как и с любым фрейворком, то это бесполезная трата времени.

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

    А если не понятно… ну что же, значит парашютный спорт еще не для вас.

  15. Нет, конечно обсуждение политики партии и личностных харрактеристик отдельно взятых пользователей – это хорошо…

    Но насчёт примера:
    При изменении размера, если по горизонтали мышь уходит за минимальный допустимый размер – то по вертикали уже не изменяется и наоборот. Интуитивно предполагается что такого поведения быть не должно.

  16. @Чеш
    Я тоже не сторонник обсуждать пользователей. Насчет примера вы абсолютно правы, это баг.

  17. Огнелис 3.5 не перестает удивлять, скорее всего скрипт неправильно определяет размеры видимой части страницы.

  18. 2 admin: удалите, пожалуйста, лишние сообщения. Пример по прежнему не хочет корректно отображаться. суть в том, чтобы делать проверку по каждой оси отдельно.
    для одной выглядит так:

    if (w >= that.options.minWidth && (W + targetPos.x) <= (window.getScrollLeft() + window.getWidth()))
    {
    that.dragParams.target.setStyle(’width’, w);
    that.handle.setStyle(’width’, W);
    }

  19. При притаскивании и изменении размеров – если движение курсора слишком быстрое – он покидает зону управляемого элемента и начинаются неприятности.

    как этого избежать?
    Вариант напрашиваются, только думаю как реализовать-то:
    с перемещением: при начале перетаскивания определить элементом за который можно таскать всё окно.

Оставить комментарий

JSToolbox создан на основе WordPress