Блог веб-разработчика v 1.0.0
Symfony2, AngularJS, React, Gulp, PhpStorm и много других страшных слов

Всплывающие окна на AngularJS

3 года назад
12625 просмотров
AngularJS JavaScript Полезности

Все не раз видели "крутые" модальные окна на jQuery. Под этот фреймворк существует огромное количество плагинов для такой банальной функции как всплывающее окно с контентом. Но попробуйте найти что-то подобное для AngularJS и вы сильно удивитесь.

В первую очередь поиск приведет к AngularUI - огромному, тяжеловесному набору модулей для реализации всяких UI плюшек на AngularJS. Я ни в коем случае не оспариваю эти модули: все сделано потрясающе и так же работает, но когда вам нужно "просто всплывающее окно" вы невольно задумаетесь, а нужно ли вообще все это подключать, а еще и тянуть за собой половину Twitter Bootstrap. Почему бы не написать самостоятельно эти несчастные всплывающие окошки, а?

iDialog - ай да всплывающие окна

Если кому-то лень читать, то можно скачать все в готовом виде. Модуль уже работает и давно оформлен на GitHub: https://github.com/IAkumaI/iDialog

Установить очень просто. Можно через bower:

bower install idialog

А можно просто скопировав к себе файлы и подключив их в вашу страницу:

<script src="/src/js/idialog.js"></script>
<link rel="stylesheet" href="/src/css/style.css">

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

<a href="" idialog="tpl-name">Show dialog</a>
<script type="text/ng-template" id="tpl-name">
    This is a dialog content.
</script>

На самом деле эта маленькая библиотека умеет загружать шаблоны по ссылке и вызывать диалоги прямо из JS и многое другое о чем можно узнать из документации, но статья немного не об этом.

Суть всплывающего окна

Какой бы темной магией ни казались всплывающие окна, на самом деле это обычный HTML элемент в абсолютным (либо fixed) позиционированием, который изначально спрятан и показывается по клику или другому событию. Так же, если окно модальное (т.е. оно блокирует весь остальной экран), то фон обычно затенняется полупрозрачным фоном. Исходя из этих двух соображений, смастерим простейшие стили для нашей библиотеки:

.idialog {position:fixed; left:50%; top:50%; z-index:100; background:#fff; min-width:400px; max-width:1000px; min-height:200px;}
    .idialog > .w {padding:16px 20px;}
.idialog-overlay {position:fixed; top:0; right:0; bottom:0; left:0; background:#000; z-index:90; opacity:0.5;}
    .idialog-overlay .l {display:block; width:160px; height:24px; position:fixed; left:50%; top:50%; margin:-12px 0 0 -80px; z-index:91; content:'Загрузка...';}

Ничего сложного, просто fixed блок с ограничениями на минимальный размер, и внутренним слоем для отступа. А так же слой перекрытия экрана. И тут же мы добавили небольшой элемент .l - это наш прелоадер на случай, если контент всплывающего окна будет загружаться удаленно.

Да будет JS

Поскольку довольно неудобно писать код частями, то придется вам прочитать комментарии, там очень подробно расписано почему мы сделали "именно так". Серьезно.

/**
* Определяем новый модуль AngularJS, я надеюсь вы знаете как это делать
* поэтому не заостряюсь на этот моменте
*/
angular.module('idialog', [])
/**
* Нашему всплывающему окну нужен какой-то шаблон.
* По умолчанию диалог скрыт, поэтому добавляем класс ng-hide
* Так же помним про стандартную директиву ng-show
* и заставляем ее показывать наше окно по флагу visible
* Далее добавляем тело нашего окна, тут и начинается темная магия.
* ng-include позволит включить любой шаблон Angular или автоматически подгрузит
* его по ссылке. Т.е. по сути эта директива сделает за нас всю работу.
* Атрибут onload позволит показывать окно когда контент полностью загружен.
* ng-init пригодится для отображения индикатора загрузки, об этом чуть позже.
*/
.run(['$templateCache', 'idialogWindowTpl', function($templateCache, idialogWindowTpl) {
$templateCache
.put(idialogWindowTpl, '<div class="idialog ng-hide" ng-show="visible">'
+ '<div class="w" ng-include="template" ng-init="startLoading()" onload="show()"></div>'
+ '</div>');
}])

/**
* Здесь определяем имя шаблона диалога на случай, если нам вдруг захочется его поменять.
*/
.value('idialogWindowTpl', 'idialogWindowTpl')

/**
* Сервис вызова всплывающих окон. Нужен чтобы была возможность вызывать любые всплывающие окна прямо из JS кода.
* В наш сервис нужно будет передать имя шаблона окна, либо ссылку на него.
*/
.service('$idialog', ['$compile', '$timeout', '$rootScope', function($compile, $timeout, $rootScope) {
return function(template) {
/**
* Для каждого нового окна создаем новый элемент,
* который содержит нужную нам директиву описанную ниже
*/
var $dialog = angular.element('<div idialog-window="'+template+'"></div>');
angular.element(document.body).append($dialog);

/**
* Показывать диалог нужно в другом потоке. Это связано с работой директивы ng-include.
* Хотя тут я могу ошибаться и, возможно, все это исправили в новых версиях AngularJS
*/
$timeout(function() {
// Для каждого окна создаем новый $scope, чтобы ничего не пересекалось
var newScope = $rootScope.$new(true);
$compile($dialog)(newScope);
});
};
}])

/**
* Директива кнопки для вызова всплывающих окон.
* По сути кнопка просто будет вызывать вышеописанный сервис и ничего более
*/
.directive('idialog', ['$idialog', function($idialog) {
return {
restrict: 'A',

link: function($scope, $element, attrs) {
$element.on('click', function(e) {
e.preventDefault();
$idialog(attrs.idialog);
});
}
}
}])

/**
* И наконец директива самого всплывающего окна.
* Как видим, мы заставляем ангуляр заменить весь шаблон
* на указанный нами ранее, это позволит средствами фрейморка
* подгрузить и скомпилировать весь контент всплывающего окна.
*/
.directive('idialogWindow', ['$timeout', '$compile', 'idialogWindowTpl', function($timeout, $compile, idialogWindowTpl) {
return {
restrict: 'A',
scope: true,
templateUrl: idialogWindowTpl,
replace: true,

link: function($scope, $element, attrs) {
// Видимость нашего окна
$scope.visible = false;

// Окно в данный момент еще загружается?
$scope.loading = false;

// Шаблон контента нашего окна
$scope.template = attrs.idialogWindow;

// Набор событий для пере-позиционирования нашего окна
angular.element(document).on('ready', $scope.relocate);
angular.element(window).on('load', $scope.relocate);
angular.element(window).on('resize', $scope.relocate);
$timeout($scope.relocate, 100);

// Шаблон темной подложки для перекрытия всего
// остального контента помимо всплывающего окна
$scope.$overlay = angular.element('<div class="idialog-overlay ng-hide" ng-show="visible || loading"><s class="l ng-hide" ng-show="loading"></s></div>');
angular.element(document.body).append($scope.$overlay);
$compile($scope.$overlay)($scope);
},

controller: function($scope, $element) {
/**
* Функция для позиционирования нашего окна.
* Не вдаюсь в подробности как это работает, скажу только,
* что она позволяет показывать окно по центру экрана либо же
* скроллить его на узких экранах.
*/
$scope.relocate = function() {
if (document.body.clientHeight < $element[0].clientHeight) {
var doc = document.documentElement, body = document.body;
var top = (doc && doc.scrollTop || body && body.scrollTop || 0) + 15;
top = parseInt(top, 10) + 'px';

var left = document.body.clientWidth > 1000 ? -$element[0].clientWidth / 2 : -$element[0].clientWidth / 2 + 25;
left = parseInt(left, 10) + 'px';

$element.css({
position: 'absolute',
marginLeft: left,
marginTop: '0px',
top: top
});
} else {
var left = document.body.clientWidth > 1000 ? -$element[0].clientWidth / 2 : -$element[0].clientWidth / 2 + 25;
left = parseInt(left, 10) + 'px';

var top = -$element[0].clientHeight / 2;
top = parseInt(top, 10) + 'px';

$element.css({
position: 'fixed',
marginLeft: left,
marginTop: top
});
}
};

/**
* Функция показа нашего окна
* На самом деле вы только лишь переставляем флаги.
* Ничего сложного, правда?
*/
$scope.show = function() {
$scope.visible = true;
$scope.loading = false;
$scope.relocate();
$timeout($scope.relocate);
$timeout($scope.relocate, 100);
};

/**
* Отложенный показ индикатора загрузки
*/
$scope.startLoading = function() {
$timeout(function() {
if (!$scope.visible) {
$scope.loading = true;
}
}, 300);
}
}
}
}])
;

Вот и все. Вся темная магия позади, сейчас уже можно пользоваться нашим модулем для всплывающих окон. Конечно, мой проект чуть более функциональный. Например, там есть кнопки закрытия :) Посмотреть и воспользоваться можно тут: https://github.com/IAkumaI/iDialog/

Что еще почитать
От AngularJS к React
2 года назад
8916 просмотров
Краткий обзор фич, которые сподвигли меня использовать React в качестве JS фреймворка.