Angular alert/messaging service
Rewrite existing Horizon alerts in Angular. To use, inject 'toastService' as dependency, and call service methods, e.g. toastService.add('success', 'User successfully updated.'). Refactor patch to follow. Partially Implements: blueprint launch-instance-redesign Co-Authored-By: Cindy Lu <clu@us.ibm.com> Change-Id: I7d3f0897d1064830865fa9077342b6575c3e9ff7
This commit is contained in:
parent
3ef270b940
commit
6dfa100d77
|
@ -0,0 +1,6 @@
|
|||
<div ng-repeat="toast in toast.get()">
|
||||
<div class="alert alert-{$ ::toast.type $}">
|
||||
<a class="close" ng-click="toast.close($index)"><span class="fa fa-times"></span></a>
|
||||
<div><strong>{$ toast.typeMsg $}: </strong>{$ toast.msg $}</div>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,161 @@
|
|||
/*
|
||||
* Copyright 2015 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
(function() {
|
||||
'use_strict';
|
||||
|
||||
/**
|
||||
* @ngdoc overview
|
||||
* @name hz.widget.toast
|
||||
* description
|
||||
*
|
||||
* # hz.widget.toast
|
||||
*
|
||||
* The `hz.widget.toast` module provides pop-up notifications to Horizon.
|
||||
* A toast is a short text message triggered by a user action to provide
|
||||
* real-time information. Toasts do not disrupt the page's behavior and
|
||||
* typically auto-expire and fade. Also, toasts do not accept any user
|
||||
* interaction.
|
||||
*
|
||||
*
|
||||
* | Components |
|
||||
* |--------------------------------------------------------------------------|
|
||||
* | {@link hz.widget.toast.factory:toastService `toastService`} |
|
||||
* | {@link hz.widget.toast.directive:toast `toast`} |
|
||||
*
|
||||
*/
|
||||
angular.module('hz.widget.toast', [])
|
||||
|
||||
/**
|
||||
* @ngdoc service
|
||||
* @name toastService
|
||||
*
|
||||
* @description
|
||||
* This service can be used to display user messages, toasts, in Horizon.
|
||||
* To create a new toast, inject the 'toastService' module into your
|
||||
* current module. Then, use the service methods.
|
||||
*
|
||||
* For example to add a 'success' message:
|
||||
* toastService.add('success', 'User successfully created.');
|
||||
*
|
||||
* All actions (add, clearAll, etc.) taken on the data are automatically
|
||||
* sync-ed with the HTML.
|
||||
*/
|
||||
.factory('toastService', function() {
|
||||
|
||||
var toasts = [];
|
||||
var service = {};
|
||||
|
||||
/**
|
||||
* Helper method used to remove all the toasts matching the 'type'
|
||||
* passed in.
|
||||
*/
|
||||
function clear(type) {
|
||||
for (var i = toasts.length - 1; i >= 0; i--) {
|
||||
if (toasts[i].type === type) {
|
||||
toasts.splice(i, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* There are 5 types of toasts, which are based off Bootstrap alerts.
|
||||
*/
|
||||
service.types = {
|
||||
danger: gettext('Danger'),
|
||||
warning: gettext('Warning'),
|
||||
info: gettext('Info'),
|
||||
success: gettext('Success'),
|
||||
error: gettext('Error')
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a toast object and push it to the toasts array.
|
||||
*/
|
||||
service.add = function(type, msg) {
|
||||
var toast = {
|
||||
type: type === 'error' ? 'danger' : type,
|
||||
typeMsg: this.types[type],
|
||||
msg: msg,
|
||||
close: function(index) {
|
||||
toasts.splice(index, 1);
|
||||
}
|
||||
};
|
||||
toasts.push(toast);
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove a single toast.
|
||||
*/
|
||||
service.close = function(index) {
|
||||
toasts.splice(index, 1);
|
||||
};
|
||||
|
||||
/**
|
||||
* Return all toasts.
|
||||
*/
|
||||
service.get = function() {
|
||||
return toasts;
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove all toasts.
|
||||
*/
|
||||
service.clearAll = function() {
|
||||
toasts = [];
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove all toasts of type 'danger.'
|
||||
*/
|
||||
service.clearErrors = function() {
|
||||
clear('danger');
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove all toasts of type 'success.'
|
||||
*/
|
||||
service.clearSuccesses = function() {
|
||||
clear('success');
|
||||
};
|
||||
|
||||
return service;
|
||||
|
||||
})
|
||||
|
||||
/**
|
||||
* @ngdoc directive
|
||||
* @name hz.widget.toast.directive:toast
|
||||
*
|
||||
* @description
|
||||
* The `toast` directive allows you to place the toasts wherever you
|
||||
* want in your layout. Currently styling is pulled from Bootstrap alerts.
|
||||
*
|
||||
* @restrict EA
|
||||
* @scope true
|
||||
*
|
||||
*/
|
||||
.directive('toast', ['toastService', 'basePath', function(toastService, path) {
|
||||
return {
|
||||
restrict: 'EA',
|
||||
templateUrl: path + 'toast/toast.html',
|
||||
scope: {},
|
||||
link: function(scope) {
|
||||
scope.toast = toastService;
|
||||
}
|
||||
};
|
||||
}]);
|
||||
|
||||
})();
|
|
@ -0,0 +1,153 @@
|
|||
/*
|
||||
* Copyright 2015 IBM Corp
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
(function(){
|
||||
'use strict';
|
||||
|
||||
describe('hz.widget.toast module', function() {
|
||||
it('should have been defined', function () {
|
||||
expect(angular.module('hz.widget.toast')).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('toast factory', function() {
|
||||
|
||||
var $compile,
|
||||
$scope,
|
||||
$element,
|
||||
service;
|
||||
|
||||
var successMsg = "I am success.";
|
||||
var dangerMsg = "I am danger.";
|
||||
var infoMsg = "I am info.";
|
||||
|
||||
beforeEach(module('templates'));
|
||||
beforeEach(module('hz'));
|
||||
beforeEach(module('hz.widgets'));
|
||||
beforeEach(module('hz.widget.toast'));
|
||||
beforeEach(inject(function ($injector, toastService) {
|
||||
service = toastService;
|
||||
$scope = $injector.get('$rootScope').$new();
|
||||
$compile = $injector.get('$compile');
|
||||
}));
|
||||
|
||||
it('should create different toasts', function() {
|
||||
service.add('danger', dangerMsg);
|
||||
expect(service.get().length).toBe(1);
|
||||
expect(service.get()[0].type).toBe('danger');
|
||||
service.add('success', successMsg);
|
||||
expect(service.get().length).toBe(2);
|
||||
expect(service.get()[1].type).toBe('success');
|
||||
service.add('info', infoMsg);
|
||||
expect(service.get().length).toBe(3);
|
||||
expect(service.get()[2].msg).toBe(infoMsg);
|
||||
});
|
||||
|
||||
it('should provide a function to clear all toasts', function() {
|
||||
service.add('success', successMsg);
|
||||
service.add('success', successMsg);
|
||||
service.add('info', infoMsg);
|
||||
expect(service.get().length).toBe(3);
|
||||
service.clearAll();
|
||||
expect(service.get().length).toBe(0);
|
||||
});
|
||||
|
||||
it('should provide a function to clear all error toasts', function() {
|
||||
service.add('danger', dangerMsg);
|
||||
service.add('success', successMsg);
|
||||
service.add('danger', dangerMsg);
|
||||
expect(service.get().length).toBe(3);
|
||||
service.clearErrors();
|
||||
expect(service.get().length).toBe(1);
|
||||
expect(service.get()[0].type).toBe('success');
|
||||
});
|
||||
|
||||
it('should provide a function to clear all success toasts', function() {
|
||||
service.add('success', successMsg);
|
||||
service.add('success', successMsg);
|
||||
service.add('info', infoMsg);
|
||||
expect(service.get().length).toBe(3);
|
||||
service.clearSuccesses();
|
||||
expect(service.get().length).toBe(1);
|
||||
expect(service.get()[0].type).toBe('info');
|
||||
});
|
||||
|
||||
it('should provide a function to clear a specific toast', function() {
|
||||
service.add('success', successMsg);
|
||||
service.add('info', infoMsg);
|
||||
service.close(1);
|
||||
expect(service.get().length).toBe(1);
|
||||
expect(service.get()[0].type).not.toEqual('info');
|
||||
});
|
||||
});
|
||||
|
||||
describe('toast directive', function () {
|
||||
|
||||
var $compile,
|
||||
$scope,
|
||||
$element,
|
||||
service;
|
||||
|
||||
var successMsg = "I am success.";
|
||||
var dangerMsg = "I am danger.";
|
||||
var infoMsg = "I am info.";
|
||||
|
||||
function toasts() {
|
||||
return $element.find('.alert');
|
||||
}
|
||||
|
||||
beforeEach(module('templates'));
|
||||
beforeEach(module('hz'));
|
||||
beforeEach(module('hz.widgets'));
|
||||
beforeEach(module('hz.widget.toast'));
|
||||
beforeEach(inject(function ($injector, toastService) {
|
||||
$scope = $injector.get('$rootScope').$new();
|
||||
$compile = $injector.get('$compile');
|
||||
service = toastService;
|
||||
|
||||
var markup = '<toast></toast>';
|
||||
$element = $compile(markup)($scope);
|
||||
$scope.$digest();
|
||||
}));
|
||||
|
||||
it('should create toasts using ng-repeat', function() {
|
||||
service.add('danger', dangerMsg);
|
||||
service.add('success', successMsg);
|
||||
service.add('info', infoMsg);
|
||||
$scope.$apply();
|
||||
expect(toasts().length).toBe(3);
|
||||
});
|
||||
|
||||
it('should have the proper classes for different toasts types', function() {
|
||||
service.add('danger', dangerMsg);
|
||||
service.add('success', successMsg);
|
||||
service.add('info', infoMsg);
|
||||
$scope.$apply();
|
||||
expect(toasts().length).toBe(3);
|
||||
expect(toasts().eq(0).hasClass('alert-danger'));
|
||||
expect(toasts().eq(1).hasClass('alert-success'));
|
||||
expect(toasts().eq(2).hasClass('alert-info'));
|
||||
});
|
||||
|
||||
it('should be possible to remove a toast by clicking close', function() {
|
||||
service.add('success', successMsg);
|
||||
$scope.$apply();
|
||||
expect(toasts().length).toBe(1);
|
||||
toasts().eq(0).find('.close').click();
|
||||
$scope.$apply();
|
||||
expect(toasts().length).toBe(0);
|
||||
});
|
||||
});
|
||||
})();
|
|
@ -15,7 +15,8 @@
|
|||
'hz.widget.action-list',
|
||||
'hz.widget.metadata-tree',
|
||||
'hz.widget.metadata-display',
|
||||
'hz.framework.validators'
|
||||
'hz.framework.validators',
|
||||
'hz.widget.toast'
|
||||
])
|
||||
.constant('basePath', '/static/angular/');
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
{% load i18n %}
|
||||
<div class="messages">
|
||||
<toast></toast>
|
||||
{% for message in messages %}
|
||||
{% if "info" in message.tags %}
|
||||
<div class="alert alert-info alert-dismissable fade in">
|
||||
|
@ -26,4 +27,4 @@
|
|||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
|
@ -58,6 +58,7 @@
|
|||
<script src='{{ STATIC_URL }}angular/magic-search/magic-search.js'></script>
|
||||
<script src='{{ STATIC_URL }}angular/validators/validators.js'></script>
|
||||
<script src='{{ STATIC_URL }}angular/workflow/workflow.js'></script>
|
||||
<script src='{{ STATIC_URL }}angular/toast/toast.js'></script>
|
||||
|
||||
<script src='{{ STATIC_URL }}horizon/lib/jquery/jquery.quicksearch.js'></script>
|
||||
<script src="{{ STATIC_URL }}horizon/lib/jquery/jquery.tablesorter.js"></script>
|
||||
|
|
|
@ -44,6 +44,7 @@ class ServicesTests(test.JasmineTests):
|
|||
'angular/wizard/wizard.js',
|
||||
'angular/workflow/workflow.js',
|
||||
'angular/metadata-display/metadata-display.js',
|
||||
'angular/toast/toast.js',
|
||||
'horizon/js/angular/filters/filters.js',
|
||||
]
|
||||
specs = [
|
||||
|
@ -66,6 +67,7 @@ class ServicesTests(test.JasmineTests):
|
|||
'angular/workflow/workflow.spec.js',
|
||||
'angular/metadata-tree/metadata-tree.spec.js',
|
||||
'angular/metadata-display/metadata-display.spec.js',
|
||||
'angular/toast/toast.spec.js',
|
||||
'horizon/js/angular/filters/filters.spec.js',
|
||||
]
|
||||
externalTemplates = [
|
||||
|
@ -83,4 +85,5 @@ class ServicesTests(test.JasmineTests):
|
|||
'angular/metadata-tree/metadata-tree.html',
|
||||
'angular/metadata-tree/metadata-tree-item.html',
|
||||
'angular/metadata-display/metadata-display.html',
|
||||
'angular/toast/toast.html',
|
||||
]
|
||||
|
|
Loading…
Reference in New Issue