Диалоговое окно — важный элемент в интерфейсах многих приложений. В этой заметке мы посмотрим, как в AngularJS вызвать модальный диалог, передать в него данные и получить результат (то есть действие пользователя) на примере бутстрапных диалогов Modal из ui.bootstrap (по ссылке есть примеры).
Для начала скачаем и подключим к странице скрипт Angular UI Bootstrap целиком, или кастомную сборку (не забудьте включить в нее Modal). Примерно вот так:
<script src="/js/libs/ui-bootstrap/ui-bootstrap-custom-tpls-0.12.1.js"></script>
Теперь подключим модуль к нашему приложению AngularJS:
var myAppWithDialogs = angular.module('myAppWithDialogs', ['ui.bootstrap']);
Чтобы использовать модальные диалоги в каком-нибудь контроллере, нужно внедрить зависимость от сервиса $modal:
myAppWithDialogs.controller('myRowsController', function ($scope, $modal) {
//...
}
Сервис $modal позволяет нам заранее создавать темплейты для диалогов, а потом указывать их при вызове. Создадим темплейт для диалога удаления чего-нибудь:
<div class="modal-header">
<h3 class="modal-title">Delete this item?</h3>
</div>
<div class="modal-body">
Are you sure to delete this item?
<div class="form-group" ng-show="isRecurrent">
<label>
<input type="radio" ng-model="deleteType" value="this">
This occurence
</label><br />
<label>
<input type="radio" ng-model="deleteType" value="future">
Future occurences
</label><br />
<label>
<input type="radio" ng-model="deleteType" value="all">
All occurences
</label><br />
</div>
</div>
<div class="modal-footer">
<button class="btn btn-danger" ng-click="ok()">Delete</button>
<button class="btn btn-default" ng-click="cancel()">Cancel</button>
</div>
Сразу вставим в диалог радиокнопки, чтобы пользователь выбрал, как он хочет удалить item — этот экземпляр, этот и будущие или вообще все. Ну, как будто item — это рекуррентная календарная запись. На примере этих радиокнопок мы увидим, как передать в диалог какую-то информацию, и как забрать из диалога то, что пользователь в нем ввел или выбрал.
Обратите внимание на директивы ng-click у кнопок диалога — функции, указанные в этих директивах, должны быть реализованы в контроллере диалога. Именно они будут закрывать диалог и подтверждать или отменять удаление.
Сохраним этот темплейт где-нибудь, например в tpls/delete-dialog.html относительно корня приложения.
Теперь напишем контроллер:
myAppWithDialogs.controller('modalDialogController', function ($scope, $modalInstance, recurrence) {
$scope.recurrence = recurrence;
$scope.isRecurrent = (recurrence !== null);
$scope.deleteType = 'this';
$scope.ok = function () {
if ($scope.isRecurrent) {
$modalInstance.close($scope.deleteType);
} else {
$modalInstance.close('all');
}
};
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
};
});
В контроллер, помимо $scope, мы внедряем $modalInstance — экземпляр модального окна, с которым будет работать контроллер и передаем значение recurrence, которое указывает является ли наш элемент рекуррентным (т.е. увидит пользователь радиокнопки, или нет).
Затем мы определяем на $scope несколько значений, с которыми связаны элементы диалога (через директивы и ). Так, $scope.isRecurrent связан с тем, видимы наши радиокнопки или нет, а в $scope.deleteType попадет выбор пользователя из трех радиокнопок.
Функция ok(), связанная в темплейте диалога с соответствующей кнопкой, вызывает метод $modalInstance.close(). Этот метод закрывает диалог и позволяет вернуть какое-нибудь значение (в данном случае, выбранную радиокнопку).
Функция cancel() вызывает метод $modalInstance.dismiss(), также закрывающий диалоговое окно с возможностью указать причину.
Разница между close() и dismiss() заключается в том, что promise, возвращаемый при открытии модального окна, в случае вызова close() будет resolved, а в случае dismiss() — rejected.
Теперь допустим у нас есть где-то кнопка «удалить» напротив какой-то строки row (которая должна удалить эту строку). И есть функция deleteItem(row), которая вызывается по клику на эту кнопку. Откроем в ней наш новый диалог:
$scope.deleteItem = function (row) {
var modalInstance = $modal.open({
templateUrl: '/tpls/delete-dialog.html',
controller: 'modalDialogController',
size: 'sm',
resolve: {
recurrence: function () {
return row.recurrence;
}
}
});
}
Что тут происходит? Мы вызываем метод open() из сервиса $modal, который открывает наше диалоговое окно. В качестве аргумента мы передаем объект со следующими настройками:
- templateUrl — путь к html-темплейту диалога;
- controller — контроллер диалога;
- size — размер окна; значение будет подставлено в класс с префиксом «modal-» — например, если size = sm, класс будет modal-sm;
- resolve — тут перечисляются значения, которые будут определены и переданы модальному окну; в данном случае мы передаем значение recurrence, которое у нас является свойством объекта row.
Описание остальных параметров можно найти на странице компонента.
Глядя на параметры templateUrl и controller, нетрудно догадаться, что мы можем использовать разные темплейты с одним контроллером, или один контроллер с разными темплейтами. В общем, сервис $modal достаточно гибок в работе.
Наконец, теперь, когда окно открыто, осталось лишь получить выбор пользователя (т.е. подтверждение или отмену). Это нетрудно сделать в той же функции deleteItem() с помощью возвращенного нам объекта modalInstance:
$scope.deleteItem = function (row) {
//show confirmation dialog
var modalInstance = $modal.open({
templateUrl: '/tpls/delete-dialog.html',
controller: 'modalDialogController',
size: 'sm',
resolve: {
recurrence: function () {
return row.recurrence;
}
}
});
modalInstance.result.then(function (deleteType) {
switch (deleteType) {
case 'this':
//удаляем только этот экземпляр
break;
case 'future':
//удаляем этот и будущие экземпляры
break;
case 'all':
//удаляем все экземпляры
break;
}
});
};
Суть в том, что свойство modalInstance.result — есть не что иное, как обычный promise. Соответственно, мы можем вызвать modalInstance.result.then() и передать 2 коллбэка — для resolved и для rejected. В данном случае мы используем только один коллбэк для resolved, поскольку нам не нужно ничего делать, если пользователь нажал Cancel. А в аргументе коллбэка будет то, что мы передали в контроллере диалога аргументом close(), то есть радиокнопка, выбранная пользователем.
Примерно так можно организовать модальные окна с помощью UI Bootstrap в AngularJS. На первый взгляд решение может показаться несколько громоздким. Но, в силу использования темплейтов и контроллеров, оно довольно гибкое и позволяет относительно компактно определить все нужные нашему приложению диалоги.