Главная > Сделай сам, Статьи > Плавающие окошки

Плавающие окошки

Сегодня мы займемся разработкой плавающих окон на вашем сайте, в вашей системе администрирования или просто — в любом месте.

1. Что такое окошко?

Окошко, это тоже самое окошко (по виду), что и у ОС Windows, KDE оболочки UNIX OC, MAC OS и т.п. то есть, сами знаете что :)

2. Как работает окошко?

Если вы когда-нибуть верстая, программируя на HTML делали

или любой другой объект со стилем позиционирования POSITION: absolute|fixed, то, по сути — вы делали окошки :)
Единственное чего Вам не хватало в этом — так это наверное возможности перетаскивания. Так вот, как только вы привяжете в этому диву возможность таскаться мышкой, у Вас получится полноценное окошко :) так как Вам останется дизайнерски привести его к Вашему виду и все.

3. Некоторые нюансы!

Если посмотреть на обычное окошко вашей ОС, то вы заметите, что оно состоит из нескольких элементов:
а) шапка, за которую именно и происходит таскание
б) контент зона, в которой и располагается контент вашего окошка

4. Что смогут окошки, которые я тут опишу:

Первое что смогут, так это выводить обычные ALERT окошки, с любым кол-вом кнопок, которые Вы можете сами регулировать, передавая их кол-во в виде параметров конструктора окна
Второе — это Ваше окошко может получать контент используя AJAX, т.е. Вы сможете динамически подгружать в контент часть окна нужную Вам информацию (динамически)
Третье — ну естественно — таскание окошек

Для эффекта, у наших окошек будет полупрозрачный бордюр (учтите, что IE версии ниже 7й не поддерживает прозрачные PNG, по этому тестировать лучше в FireFox, в Opere не тестировал, не знаю :) )

Итак начнем:

Для начала нам нужно заиметь следующие компоненты:

1. с сайта http://dklab.ru/lib/JsHttpRequest/ берем модуль JsHttpRequest
2. с сайта jQuery.com берем библиотеку jQuery
3. Нам нужен WEB сервер, по этому используем либо Интернет хостинг, либо берем http://www.denwer.ru/ Денвер (я работаю с ним на локальном компьютере)
4. создаем файловую архитектуру следующего вида начиная с папки PLUGINS

pic1

Следующее что нам нужно:

1. В папку /AJAX/ размещаем два файла: JsHttpRequest.php и JsHttpRequest.js, которые переименовываем как jshr_ajax.js (это что бы Вам было понятней, если Вы будете читать мои исходные коды, которые приложу ниже). Их Вы должны взять из модуля JsHttpRequest
2. В папку /JQUERY/ размещаем файл: jquery.js (если он называется не так, переименуйте его по короче как я указал)
3. В папке /WINDOWS/ создайте файл windows.js
4. В папку /WINDOWS/IMAGES/ разместите картинки из архива, который приложен как исходный код к данной статье
5. Рядом с папкой PLUGINS создайте два файла index.php и ajax_window_content.php

Пожалуй пора начать уже писать коды:

1. Начнем, пожалуй, с самого легкого, с файла ajax_window_content.php (начали с конца, но не беда, сделаем дело, и забудем пока про это)

открывай те этот файл, и вставляйте следующий код:


include_once($_SERVER['DOCUMENT_ROOT'].'/plugins/ajax/JsHttpRequest.php');
$JsHttpRequest =& new JsHttpRequest("windows-1251");
$_RESULT['content']='<table width="500" style="height: 500px"><tr><Td align="center">11</td></tr></table>';

Как работает AJAX Можно почитать здесь http://dklab.ru/lib/JsHttpRequest/ или здесь http://ru.wikipedia.org/wiki/Ajax.
Скажу кратко, этот файл генерирует CONTENT для окна, который возвращается при вызове этого файла.

2. Следующее чем займемся, это начнем писать объект окон, открываем файл /PLUGINS/WINDOWS/windows.js:

Куски кода, которые я буду описывать, вставлять в файл по очереди, друг за другом

Объявление типов кнопок:

/* BUTTONS*/

var	MR_OK=5001;

var	MR_CANCEL=5002;

Кто программировал на Win32API сразу улыбнуться :) нашли знакомые буковы)))
Объявили две переменные, отвечающие соответственно за кнопку OK и CANCEL

Объявляем тип окон, пока у нас только 1, Вы потом можете модернизировать все что угодно и как угодно:

/* WINDOWS */

var WS_DIALOG = 1;

Объявляем контейнер для окон. Что такое контейнер? Это массив окон одним словом. Ведь Мы же не хотим радоваться только одному плавающему окошку? Давайте сделаем их мильён :)
Вобщем контейнер — это склад созданных окошек, при создании окна, оно попадает в него, и лежит там до тех пор пока не будет закрыто — аналог панели задач в ОС Windows. Но в данной статье мы не будем делать панель задач, зачем строить велосипед)

/* WINDOW CONTAINER */

var wins_list = winlist;

А вот и дошли до самого контейнера:

/* WINDOW CONTAINER CLASS */

var winlist = {
	list: new Array(),
	/**
	*	Подсчет кол-ва открытых окон
	*/
	count: function() {return this.list.length;},
	/**
	*	title - string: Текст заголовка
	*	content_data - hash {text, url, ajax_callback}  - хеш массив параметров, если только текст - то выводится текст, если указан URL
	*							  то, данные берутся с урла, при необходимости выполняется ajax_callback функция
	*							  по умолчанию выводится текст, можно сделать такой: "Загружается..."
	*/
	create: function (title, content_data, buttons, callback)
		{
		var num = this.list.length+1;
		var id = 'window_item_'+num;
		if (typeof buttons == 'undefined')
			{
			buttons = MR_OK & MR_CANCEL;
			}
		if (typeof callback == 'undefined')
			{
			callback = function() {};
			}
		var win_obj = windows.generate({win_id:id, title:title, content:content_data, type:WS_DIALOG, buttons:buttons, callback:callback});
		this.list.push(win_obj);
		},
	/**
	*	Возвращает окно под номером index
	*/
	get: function (index)
		{
		return this.list[index];
		},
	/**
	*	Закрывает окно под ID
	*/
	close: function(id)
		{
		var newlist = new Array();
		for(>var i=0;i<this.list.length;i++)
			{
			if (this.list[i]==id)
				{
				$('#'+id).remove();
				}
			else

				newlist.push(this.list[i]);
			}
		this.list = newlist;
		},
	};

Рассмотрим что он умеет:

а) свойство list — это массив окон
б) функция count — показывает нам, сколько сейчас окон создано и показано на экране
в) метод create — создает новое окно, и у него есть параметры: — title — не замысловатое понятие — Заголовок окна — content_data — тут уже сложнее, это ХЭШ массив {var1: value1, var2: value2, ..., varN: valueN} — такого типа, в котором есть свои свойства:

text — это текст контента, который висит всегда внутри окна, пока его не заменит контент, полученный через AJAX
url — путь до AJAX данных (в нашем случае будет http://yoursite/ajax_windows_content.php)
ajax_callback — функция JS которая выполнится, когда данные будут полностью получены с помощью AJAX — buttons — кнопки, которые будут в окне.

buttons: MR_OK — будет только кнопка OK
buttons: MR_CANCEL — будет только кнопка CANCEL
buttons: MR_OK & MR_CANCEL — будут обе кнопки
buttons: MR_OK & MR_CANCEL & MR_NEXT — при наличии кнопки NEXT будут показаны все три кнопки в окне — callback — функция с двумя параметрами, обработка нажатия кнопок, следующего наполнения:

function(win_id, result_data)
	{
	if (result_data.result == MR_OK)
		{
		alert('Нажали OK');
		}
	else >if(result_data.result == MR_CANCEL)
		{
		www.close(win_id);
		}
	}

win_id — ID окна, по которому с ним можно сделать что угодно
result_data — данные по окну, в котором содержится код нажатой кнопки, а так же можно туда добавить любую опознавательную на Ваш взгляд информацию

д) get — возвращает окно по ее номеру в массиве
е) close — закрывает окно по win_id и удаляет его из массива окон

Ну вот, с контейнером разобрались, обычные методы, создать, взять, удалить, количество.

давайте перейдем к конструктору окон, который как Вы могли заметить, вызывался из контейнера методом windows.generate.

Начнем с описания класса окна:

/* WINDOW ELEMENT */

var windows = {

Далее следует функция создания окна, с проверкой, подключения Фреймворка jQuery:

	generate: function (params)
		{
		if (typeof $ == 'undefined')
			{
			alert('JQuery не найден');
			return;
			}

Пишем код дизайна окна:

		var border_width = 8;
		var border_height = 8;
		var img_left_top = '<img src="/plugins/windows/images/img_left_top.png" width="'+border_width+'" height="'+border_height+'" alt="" border="0">';
		var img_right_top = '<img src="/plugins/windows/images/img_right_top.png" width="'+border_width+'" height="'+border_height+'" alt="" border="0">';
		var img_left_bottom = '<img src="/plugins/windows/images/img_left_bottom.png" width="'+border_width+'" height="'+border_height+'" alt="" border="0">';
		var img_right_bottom = '<img src="/plugins/windows/images/img_right_bottom.png" width="'+border_width+'" height="'+border_height+'" alt="" border="0">';
		var img_left_right = 'style="background: transparent url(/plugins/windows/images/img_left_right.png) repeat-y; width:'+border_width+'px"';
		var img_top_bottom = '<img src="/plugins/windows/images/img_top_bottom.png" width="100%" height="'+border_height+'" alt="" border="0">';
		var w = '';
		w += '<table cellspacing="0" cellpadding="0" border="0" width="250">';
		w += '<tr class="window_border" style="height: 8px"><td width="8">'+img_left_top+'</td><td>'+img_top_bottom+'</td><td width="8">'+img_right_top+'</td></tr>';
		w += '<tr><td class="window_border" '+img_left_right+'>&nbsp;</td>';
		w += '	<td style="background: white">';
		w += '		<div id="'+params.win_id+'_dd" style="height: 25px; position: fixed;"><span style="font: 18px">&nbsp;</span></div><div style="padding: 3px; font-size: 18px; color: white; background: gray;">'+params.title+'</div>';
		w += '		<div style="padding: 10px" id="'+params.win_id+'_content">'+params.content.text+'</div>';
		w += '		<div style="padding: 10px" id="'+params.win_id+'_buttons"></div>';
		w += '	</td>';
		w += '	<td class="window_border" '+img_left_right+'></td></tr>';
		w += '<tr class="window_border" style="height: 8px"><td width="8">'+img_left_bottom+'</td><td>'+img_top_bottom+'</td><td width="8">'+img_right_bottom+'</td></tr>';
		w += '</table>';

Смотрим на таблицу, она как матрица 3×3, по краям ячейки бордюра, а центральная отвечает за контент зону, в которой в свою очередь лежат 4 DIV-а.
Первый отвечает за заголовок, который будет реагировать на мышку, т.е. за него мы будем таскать наше окно, второй — это сам заголовок с текстом, он лежит под первым дивом, что бы при перетаскивании текст не выделялся, иначе будут не красивые окошки.
Третий — зона текста окна и четвертый — зона кнопок окна

Далее создаем DIV контейнер окна, в который вписываем описанный дизайн:

		var win_obj = document.createElement('DIV');
		win_obj.style.position="fixed";
		win_obj.innerHTML = w;
		win_obj.className= 'window';
		win_obj.id = params.win_id;
		win_obj.style.display='none';
		document.body.appendChild(win_obj);

Говорим, что позиционирование является FIXED, что дает хороший эффект при перетаскивании, если окно уходит за экран, полосы прокрутки не изменяются.
Задаем контент в innerHTML.
Указываем класс window, в будущем он нам очень понадобится.
Задаем уникальный ID окна.
Делаем окно не видимым, пока мы его полностью не оформим.
И новоиспеченное окошко, прикрепляем к нашему документу :)

Далее. Создаем кнопочки:

		/* Buttons */
		if ((params.buttons | MR_OK) == MR_OK)
			this.addWindowButton(params.win_id, 'OK', $('#'+params.win_id+'_buttons'), params.callback, MR_OK);
		if ((params.buttons | MR_CANCEL) == MR_CANCEL)
			this.addWindowButton(params.win_id, 'Отмета', $('#'+params.win_id+'_buttons'), params.callback, MR_CANCEL);

Каждую кнопочку обрабатываем по отдельности, по этому, если у Вас будет еще уникальная кнопка, то для нее нужно будет прописать свои условия добавления по аналогии с тем, что по дефолту.

Позиционируем окошко:

		/* POSITION */
		this.reposWindowByContent(params.win_id);

Вызывается метод, позиционирования, так, чтобы окно показалось в центре рабочей области экрана с правильным уровнем вложенности z-index. Подробнее об этом методе будет рассказано ниже.

Теперь настало время описать методы таскания окон:

		/* DRAG */
		var start_drag = false;
		var x0 = y0 = 0;
		this.resizeWindowByContent(params.win_id);
		$('#'+params.win_id).click(function() {
				$('.window').css('z-index',1000);
				$('#'+params.win_id).css('z-index',10000);
			});
		$('#'+params.win_id+'_dd').mousedown(function(f) {
				var offset = $('#'+params.win_id+'_dd').offset();
				x0 = f.clientX - offset.left + border_width;
				y0 = f.clientY - offset.top + border_height;
				start_drag=true;
			});
		$('#'+params.win_id+'_dd').mouseup(function() {
				start_drag=false

			});
		$(document).mousemove(function(f) {
				if (start_drag)
					{
					$('#'+params.win_id).css('left', f.clientX-x0).css('top',f.clientY-y0);
					}
			});

Первым делом, в новом окне мы говорим ему, что мы его не таскаем: start_drag = false.
Объявляем начальные координаты клика мышки на окне.
Изменяем размер окна, в зависимости от объема его контента.

Далее вступает в роль jQuery:

Берем по win_id DIV окна, навязываем ему функцию клика мышки, при котором, уровень вложенности всех окон изменяется на дефолтное состояние в 1000, а выбранному окну, задается значение 10000, таким образом, мы даем активность выбранному окну, перемещая его выше всех.

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

Событие на отпускание кнопки мышки, говорит окну, что мы закончили передвижение.

А тут самое интересное. Чтобы окно не прыгало при резком движении мышки (т.к. курсор может выпасть из заголовка окна, и потерять метод mousemove), событие на движение мышкой мы привязываем ко всему документу, а так как координаты от мышки — общие, мы реализуем тут движение окна пока нажата кнопка мышки (обычно левая).

Далее, проверяем, если контент должен быть получен AJAXом, выполняется следующий код:

		/* AJAX CONTENT */
		if (typeof params.content.url!='undefined')
			{
			this.doLoad(
					(typeof params.content.element !='undefined' ? params.content.element : ''),
					params.content.url,
					params.win_id+'_content',
					'post',
					'rewrite',
					(typeof params.content.ajax_callback !='undefined' ? params.content.ajax_callback : null)
				);
			}

doLoad — очень мощный метод получения контента, он имеет следующие параметры:

value— этот параметр полезен для передачи целых форм, например есть большая форма, которая включает в себя не только текстовые поля, но и файловые и при этом Вам нужно всю форму отправить через AJAX, вот тут и поможет это поле, в других случаях, как и в нашем — поле пустое (для передачи формы просто укажите document.getElementById('form_id') в качестве этого параметра:

doLoad(document.getElementById('form_id'), '/url/to/ajax/', 'content_id')

url  — путь до AJAX обработчика
target — один или несколько ID полей, в которые будет записан результат работы AJAX

один: 'content_id'
несколько: 'content_id1;content_id2'

для возвращения одного контента: $_RESULT['content'] = 'text';
Для нескольких: $_RESULT = array('content' => array('content1', 'content2'));

method GET|POST по умолчанию POST
write_type — метод записи контента rewrite, insert, append
callback — функция, которая выполнится по завершению получения результата работы AJAX

Закрываем метод создания окна:

		return params.win_id;
		},

Далее, методы класса:

Изменение размера по коненту:

	resizeWindowByContent: function(win_id)
		{
		$('#'+win_id+'_dd').width($('#'+win_id).width()-16);
		},

Позиционирование окна по центру:

	reposWindowByContent: function (win_id)
		{
		var sw = ($(document).width()/2) - ($('#'+win_id).width()/2);
		var sh = ($(document).height()/2) - ($('#'+win_id).height()/2);
		$('#'+win_id).css('left',sw).css('top',sh).show();
		$('.window').css('z-index',1000);
		$('#'+win_id).css('z-index',10000);
		},

Метод AJAX:

	doLoad: function (value, path, target, method, write_type, callback)
		{
		if (method == 'undefined') method=null;
		if (write_type == 'undefined' || !write_type || write_type=='') write_type='rewrite';
		var req = new JsHttpRequest();
		var parent = this;
		req.onreadystatechange = function()
			{
			if (req.readyState == 4 && target.length > 0)
				{
				if (target.indexOf(";") == -1)
					{
					try

						{
						parent.appendData(req.responseJS.content, target, write_type);
						parent.evaluateJs(target);
						}
					catch (e)
						{
						alert(req.responseText+' JSError:'+e.message);
						}
					}
				else

					{
					target = target.split(";");
					for (i in target)
						{
						try

							{
							parent.appendData(req.responseJS.content[i], target[i], write_type);
							parent.evaluateJs(target);
							}
						catch (e)
							{
							alert(req.responseText+' JSError:'+e.message);
							}
						}
					}
				parent.resizeWindowByContent(target.replace('_content',''));
				parent.reposWindowByContent(target.replace('_content',''));
				if (callback && typeof callback!='undefined')
					{
					callback(req.responseJS);
					}
				}
			}
		req.open(method, path, true);
		req.send( { 'data': value } );
		},

По умолчание метод записи — rewrite, т.е. то, что было в контенте окна, будет затерто тем, что вернул AJAX.
Запускаем аякс, если результат вернулся, обрабатываем его, проверяется сколько контент зон нам надо обновить, обновляем их, выполняем все JS скрипты, которые были переданы вместе с результатом AJAXa.
Ресайзим и Позиционируем окно под контент.
Выполняем обратную функцию вызова, если она передана.

При правильной работе, внутри окна вы увидите по центру "Hello World!" (используя исходные коды приложенные ниже)

Три вспомогательных метода для AJAXa:

1. Добавление данных:

	appendData: function (data_str, parent_node, mode)
		{
		//Перезаписываем
		if(mode == 'rewrite')
			{
			$('#'+parent_node).html(data_str);
			}
		// Добавляем в конец
		else >if (mode == 'append')
			{
			var old_text = $('#'+parent_node).html();
			$('#'+parent_node).html(old_text+data_str);
			}
		// Добавляем в начало
		else >if (mode == 'insert')
			{
			var old_text = $('#'+parent_node).html();
			$('#'+parent_node).html(data_str+old_text);
			}
		},

2. Получение JavaScript-ов, переданных вместе с текстом, через AJAX

	evaluateJs: function (obj)
		{
		var scriptTags=document.getElementById(obj).getElementsByTagName('SCRIPT');
		var string='';
		var jsCode='';
		for(>var no=0;no<scriptTags.length;no++)
			{
			if(scriptTags[no].src)
				{
				var head=document.getElementsByTagName("head")[0];
				var scriptObj=document.createElement("script");
				scriptObj.setAttribute("type", "text/javascript");
				scriptObj.setAttribute("src", scriptTags[no].src);
				}
			else

				{
				if(DHTMLSuite.clientInfoObj.isOpera)
					{
					jsCode=jsCode+scriptTags[no].text+'\n';
					}
				else

					jsCode=jsCode+scriptTags[no].innerHTML;
				}
			}
		if(jsCode) this.installScript(jsCode);
		},

3. Запуск полученных скриптов

	installScript: function ( script )
		{
		try

			{
			if (!script) return;
			if (window.execScript)
				{
				window.execScript(script)
				}
			else >if(window.jQuery&&jQuery.browser.safari)
				{
				window.setTimeout(script,0);
				}
			else

				{
				window.setTimeout( script, 0 );
				}
			}
		catch(e)
			{
			}
		},

И наконец — конец.

Добавление кнопок в окно:

Говорим что за коно (win_id),
текст кнопки (name),
parent — само окно, чтобы знать к кому относится кнопка,
callback — функция, которую мы Самую первую рассмотрели — реакция на нажатия кнопок
button_result — код кнопки, который она будет возвращать при клике по ней, для обработки в callback

	addWindowButton: function (win_id, name, parent, callback, button_result)
		{
		var sender = this;
		var button = document.createElement('input');
		button.type = 'button';
		button.value = name;
		button.onclick = function() {
				callback(win_id, {result: button_result})
				};
		$(parent).append(button);
		},
	}

Ну все, подготовку всю сделали, остается, протестировать, как все это работает, но сначала заполним основной файл index.php:

<script type="text/javascript" src="/plugins/windows/windows.js"></script>
<script type="text/javascript" src="/plugins/ajax/jshr_ajax.js"></script>
<script type="text/javascript" src="/plugins/jquery/jquery.js"></script>
<script LANGUAGE="JavaScript">
var www = winlist;
// Обычная кнопка, в заголовке показывается номер созданного окна
// с 2мя кнопками
function AddWindowType1()
	{
	www.create(
			'Новое окно '+www.count()+1,
			{text:'Текст окна'},
			MR_OK & MR_CANCEL,
			function(win_id, result_data)
				{
				if (result_data.result == MR_OK) alert('ok click ID:'+win_id);
				else if(result_data.result == MR_CANCEL) www.close(win_id)
				}
		);
	}
// Окошко с AJAX контентом
function AddWindowType2(_url)
	{
	www.create(
			'Тестовое окошко',
			{
				text: 'Не большой текст окна, для проверки работоспособности калбэк функций',
				url: _url,
				ajax_callback: function() {alert('Load content complite');}
			},
			MR_OK & MR_CANCEL,
			function(win_id, result_data)
				{
				if (result_data.result == MR_OK) alert('Нажали OK');
				else if(result_data.result == MR_CANCEL) www.close(win_id);
				}
		);
	}
function test_count()
{
	alert(www.count());
}
</script>
<input type="button" onclick="AddWindowType1()" value="AddWindowType1">
<input type="button" onclick="AddWindowType2('http://<?=$_SERVER['HTTP_HOST'];?>/ajax_window_content.php')" value="AddWindowType3(URL)">
<input type="button" onclick="test_count()" value="test count">

Сохраните все файлы, зайдите в браузер и наберите: http://yoursite/index.php

должны будете увидеть 3 кнопки, 2 для создания окон, а третья, показывает, сколько окон уже на экране

pic2

Исходный код для данной ститьи: windows.rar

Сделай сам, Статьи , , ,

  1. 04 Март 2009 из 16:13 | #1

    Блин, а че все так сложно? Здесь (www.linkexchanger.su/2008/32.html) то же самое, но кода на порядок меньше...

  2. 04 Март 2009 из 16:14 | #2

    Блин, а че все так сложно? Здесь (linkexchanger.su/2008/32.html) то же самое, но кода на порядок меньше...

  3. 04 Март 2009 из 23:02 | #3

    Ну. я представил свой вариант, у того автора — свой. вобще, есть хороший способ делать окошки через EXTJS.com

  1. Трекбеков пока нет.