var app = angular.module('plunker', ['simpleAlert']);
app.controller('MainCtrl', function($scope,simpleAlertFactory) {
simpleAlertFactory.setDefault({'closeIconClasses':'glyphicon glyphicon-heart'});
//for testing callbacks:
var saySomething = function(params){console.log(params)};
// simplest use case:
$scope.basicTest = function(){
simpleAlertFactory.show({message:'simple test.',clickToClose:'true'});
// no callbacks, no title, just a simple closable message
};
// demoing fully defined cancel and continue options.
$scope.sendWarning = function(){
simpleAlertFactory.show({
/* NOTE: no id is required for default alert target
(provided a simple-alert directive exists in HTML with no id) */
/* NOTE: no type is required for warning style alert */
title:"Warning:",
message:'Holy Moly Spiccoli, a link---> <a class="alert-link link-warning" href="http://google.com">LOOK OUT!</a>',
classes:'pink force-top',
cancelLabel:"Cancel",
cancelCallback:saySomething,
cancelCallbackParamsArray:["default warning alert was canceled!"],
okLabel:"Continue",
callback:saySomething,
callbackParamsArray:["default warning alert was closed!"]}
);
};
// sending 'warning' style alert with title to a specific ('custom') alert target
// with success callback defined
$scope.sendWarningToCustom = function(){
simpleAlertFactory.show({
id:"custom",
/* notice no type is required for alert-warning */
title:"No close icon!",
closeIcon:false,
clickToClose:true,
message:'Click anywhere on alert',
callback:saySomething,
callbackParamsArray:["custom warning alert was closed!"]});
};
// sending 'info' style alert to default alert target
// with success callback defined
$scope.sendInfo = function(){
simpleAlertFactory.show({
/* notice no id is required for default */
type:"info",
title:"Here's a tip...",
message:'Remember to tip your waiter.',
callback:saySomething,
callbackParamsArray:["default info alert was closed!"]});
};
// sending 'error' style alert to custom alert target
// with success callback defined
$scope.sendErrorToCustom = function(){
simpleAlertFactory.show({
id:"custom",
type:'danger',
title:"Oh Yes",
message:'You are in danger.',
callback:saySomething,
callbackParamsArray:["custom error alert was closed!"]});
};
// sendAutoCloseToCustom
$scope.sendAutoCloseToCustom = function(){
simpleAlertFactory.show({
id:"custom",
type:'info',
message:"3... 2... 1...",
title:'Closing in...',
timeout:3000,
clickToClose:true,
callback:saySomething,
callbackParamsArray:["custom error alert was closed!"]
});
};
// proxy function for clearing all simple alerts.
// NOTE: No callbacks are triggered.
$scope.clearAll = function(){
console.log('clearing all alerts.');
simpleAlertFactory.clearAll();
};
});
<!DOCTYPE html>
<html ng-app="plunker">
<head>
<meta charset="utf-8" />
<title>AngularJS test</title>
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css">
<link data-require="jasmine" data-semver="2.0.0" rel="stylesheet" href="//cdn.jsdelivr.net/jasmine/2.0.0/jasmine.css" />
<script data-require="json2" data-semver="0.0.2012100-8" src="//cdnjs.cloudflare.com/ajax/libs/json2/20121008/json2.js"></script>
<script data-require="jasmine" data-semver="2.0.0" src="//cdn.jsdelivr.net/jasmine/2.0.0/jasmine.js"></script>
<script data-require="jasmine" data-semver="2.0.0" src="//cdn.jsdelivr.net/jasmine/2.0.0/jasmine-html.js"></script>
<script data-require="jasmine@*" data-semver="2.0.0" src="//cdn.jsdelivr.net/jasmine/2.0.0/boot.js"></script>
<script data-require="angular.js" data-semver="1.4.0-beta.2" src="https://code.angularjs.org/1.4.0-beta.2/angular.js"></script>
<script data-require="angular-mocks" data-semver="1.4.0-beta.2" src="https://code.angularjs.org/1.4.0-beta.2/angular-mocks.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="simpleAlert.js"></script>
<script src="app.js"></script>
<script src="simpleAlertSpec.js"></script>
<script src="jasmineBootstrap.js"></script>
<!-- bootstraps Jasmine -->
</head>
<body>
<div id="container" ng-controller="MainCtrl">
<h3>Alert Examples:</h3>
<h6>-- global commands --</h6>
<ul>
<li class="btn-link" ng-click="clearAll()">Clear all currently open alerts (no callbacks)</li>
<li class="btn-link" ng-click="removeCustom=!removeCustom">Toggle rendering of custom directive container (no callbacks)</li>
</ul>
<h6>-- default target --</h6>
<ul>
<li class="btn-link" ng-click="basicTest()">Send simple warning to default test area</li>
<li class="btn-link" ng-click="sendWarning()">Send more complex warning message to default test area with custom class</li>
<li class="btn-link" ng-click="sendInfo()">Send info message to default test area</li>
</ul>
<h6>-- custom target --</h6>
<ul>
<li class="btn-link" ng-click="sendWarningToCustom()">Send warning to custom test area with no close button</li>
<li class="btn-link" ng-click="sendErrorToCustom()">Send error alert to custom test area</li>
<li class="btn-link" ng-click="sendAutoCloseToCustom()">Send Auto Closing alert to custom test area</li>
</ul>
<data-simple-alert id="hack-solely-for-hiding-jasmine-cruft-in-plnkr"></data-simple-alert>
<div class="well area col-xs-6">
<p>This is the <strong>default</strong> alert test area:</p>
<!-- put at least the following directive on your page -->
<data-simple-alert></data-simple-alert>
</div>
<!-- Here we add the ng-if for re-rendering to test if the model stays clean, and to check if commands throws errors -->
<div class="well area col-xs-6" ng-if="removeCustom!==true">
<p>This is <strong>custom</strong> alert test area:</p>
<data-simple-alert id="custom"></data-simple-alert>
</div>
</div>
<hr/>
<div id="HTMLReporter" class="jasmine_reporter"></div>
</body>
</html>
(function() {
var jasmineEnv = jasmine.getEnv();
jasmineEnv.updateInterval = 250;
/**
Create the `HTMLReporter`, which Jasmine calls to provide results of each spec and each suite. The Reporter is responsible for presenting results to the user.
*/
var htmlReporter = new jasmine.HtmlReporter();
jasmineEnv.addReporter(htmlReporter);
/**
Delegate filtering of specs to the reporter. Allows for clicking on single suites or specs in the results to only run a subset of the suite.
*/
jasmineEnv.specFilter = function(spec) {
return htmlReporter.specFilter(spec);
};
/**
Run all of the tests when the page finishes loading - and make sure to run any previous `onload` handler
### Test Results
Scroll down to see the results of all of these specs.
*/
var currentWindowOnload = window.onload;
window.onload = function() {
if (currentWindowOnload) {
currentWindowOnload();
}
//document.querySelector('.version').innerHTML = jasmineEnv.versionString();
execJasmine();
};
function execJasmine() {
jasmineEnv.execute();
}
})();
/* restore "body" styling that were changes by "jasmine.css"... */
body { background-color: white; padding: 0; margin: 8px; }
/* ... but remain the "jasmine.css" styling for the Jasmine reporting */
.jasmine_reporter { background-color: #eeeeee; padding: 0; margin: 0; }
.area{
border:1px dashed #44F;
}
simple-alert .btn{
margin-top:10px;
margin-left:10px;
}
.pink {
border:3px dashed pink;
}
.force-top{
position:fixed;
top:0;
left:0;
margin:0;
width:100%;
z-index:9000;
}
angular.module('simpleAlert', [])
.factory('simpleAlertFactory', function($interval,$sce) {
var timeoutVar;
var internal = {
messages: {},
default: {
id: undefined,
type: 'warning',
message: '',
defaultClasses: '', // use this to set site-wide customizations
// custom
classes: '',
//OK is optional
okLabel: '',
callback: angular.noop,
callbackParamsArray: [undefined],
// cancel is optional
cancelLabel: '',
cancelCallback: angular.noop,
cancelCallbackParamsArray: [undefined],
title: undefined,
timeout: 0,
closeIcon: true,
closeIconClasses: 'glyphicon glyphicon-remove-sign',
clickToClose: false
}
};
// setDefault() has two signatures:
// if passed a key value pair, then it will update that element
// otherwise it will expect key to be a new default object,
// and extend the existing default object with that
var setDefault = function(keyOrObject, newDefault) {
if(newDefault){
internal.default[keyOrObject] = newDefault;
} else if(keyOrObject){
angular.extend(internal.default, keyOrObject)
}
};
var show = function(opts) {
var msg = angular.extend({}, internal.default, opts);
internal.messages[msg.id] = msg;
};
var clearById = function(id) {
$interval.cancel(timeoutVar);
internal.messages[id].callback.apply(this, internal.messages[id].callbackParamsArray);
internal.messages[id] = {};
timeoutVar = $interval(function() {
delete internal.messages[id];
}, 0,1);
};
var removeById = function(id) {
delete internal.messages[id];
};
var clearAll = function() {
angular.forEach(internal.messages, function(value, key) {
internal.messages[key] = {};
});
$interval(function() {
angular.forEach(internal.messages, function(value, key) {
delete internal.messages[key];
});
}, 0,1);
};
var cancelById = function(id) {
$interval.cancel(timeoutVar);
internal.messages[id].cancelCallback.apply(this, internal.messages[id].cancelCallbackParamsArray);
internal.messages[id] = {};
// delayed delete so everything has a chance to execute.
timeoutVar = $interval(function() {
delete internal.messages[id];
}, 0,1);
};
return {
setDefault: setDefault,
show: show,
cancelById: cancelById,
clearById: clearById,
removeById: removeById,
clearAll: clearAll,
messages: internal.messages
};
}).directive('simpleAlert', function($interval, simpleAlertFactory,$sce) {
return {
restrict: 'E',
scope: {
id: '@'
},
template: '<div data-ng-click="clickToClose()" class="alert alert-{{msg.type}} alert-dismissable {{msg.defaultClasses}} {{msg.classes}}" ng-show="msg.message.length">' +
'<button class="close" data-dismiss="alert" data-ng-click="clear()" ng-show="msg.closeIcon">' +
'<span class="{{msg.closeIconClasses}}"></span>' +
'</button>' +
'<h3 ng-show="msg.title.length" ng-bind="msg.title"></h3>' +
'<p ng-bind-html="makeHTML(msg.message)"></p>' +
'<button ng-show="msg.cancelLabel.length" class="btn btn-default" data-ng-click="cancel()">{{msg.cancelLabel}}</button>' +
'<button ng-show="msg.okLabel.length" class="btn btn-{{msg.type}}" data-ng-click="clear()">{{msg.okLabel}}</button>' +
'</div>',
link: function($scope) {
var timeoutVar;
$scope.msgObj = simpleAlertFactory.messages;
// deep watch - can we avoid? contains all named alert instances currently in play.
$scope.$watch('msgObj', function(newVal, oldVal) {
$interval.cancel(timeoutVar);
// The id here is the key so newVal[id] is an alert config object
if (newVal[$scope.id] && !angular.equals(newVal[$scope.id], oldVal[$scope.id])) {
$scope.msg = newVal[$scope.id];
// add a timeout if 'timeout exists on msgObj'
if (newVal[$scope.id].timeout) {
timeoutVar = $interval(function() {
$scope.clear();
}, +newVal[$scope.id].timeout,1);
}
}
}, true);
$scope.clickToClose = function() {
if ($scope.msg.clickToClose) {
$scope.clear();
}
};
$scope.makeHTML = function(text){
return $sce.trustAsHtml(text);
}
$scope.clear = function() {
$interval.cancel(timeoutVar);
//removes the message and trigger the success handler
simpleAlertFactory.clearById($scope.id);
};
$scope.cancel = function() {
//removes the message and trigger the cancel handler
simpleAlertFactory.cancelById($scope.id);
};
$scope.$on('$destroy', function() {
$interval.cancel(timeoutVar);
simpleAlertFactory.removeById($scope.id);
});
}
};
});
Angular-Simple-Message
This Directive/Service is an attempt at creating a minimalistic in-page (non-modal) notification system using Bootstrap.
The goal of this project is to minimize excess HTML and code where possible,
while providing options for a rich interactive notification system.
Features:
- Simple
- Fully customizable template.
- Uses plain HTML ID's to determine alert target directive.
- Can handle triggers callbacks and parameters
- Can be used anywhere in an angular app, regardless of scope, just import the service.
- no external dependencies.
- fully tested (coming soon)
describe('simpleAlert: Testing', function() {
var defaultElement, customElement, scope, simpleAlertFactory,
complexTestData, simpleTestData,
simpleTitle, simpleText,simpleFunction,
successParams,failParams;
// create a default app (container)
beforeEach(module('simpleAlert'));
beforeEach(inject(function($rootScope, $compile, _simpleAlertFactory_) {
scope = $rootScope.$new();
simpleAlertFactory = _simpleAlertFactory_;
defaultElement =
' <data-simple-alert></data-simple-alert>';
customElement =
' <data-simple-alert id="test"></data-simple-alert>';
defaultElement = $compile(defaultElement)(scope);
customElement = $compile(customElement)(scope);
scope.$digest();
// --- test data values ---
simpleText = 'message text';
simpleTitle = 'title text';
simpleFunction = function(params){/*no-op*/};
successParams = [1,2,3];
failParams = [-1,-2,-3];
// --- test data objects ---
simpleTestData = {message:simpleText};
complexTestData = {
id : 'test',
type : 'info',
message : 'Complex example demo text',
okLabel : 'Continue',
callback : Function,
callbackParamsArray : [ 1, 2, 3 ],
cancelLabel : 'Cancel',
cancelCallback : Function, cancelCallbackParamsArray : [ -1, -2, -3 ],
title : undefined,
timeout:0,
defaultClasses: "",
classes: "",
closeIcon : true,
closeIconClasses:'glyphicon glyphicon-remove-sign',
clickToClose : false };
}));
describe('an empty default simple alert', function() {
it("should have a message Object defined", function() {
var isolated = defaultElement.isolateScope();
expect(isolated.msgObj).toBeDefined();
});
it("should not have a message Object id defined", function() {
var isolated = defaultElement.isolateScope();
expect(isolated.msgObj.id).toBeUndefined();
});
});
describe('a populated default simple alert with minimal configuration', function() {
it("should not have a message value defined before factory call", function() {
var isolated = defaultElement.isolateScope();
expect(isolated.msgObj[undefined]).toBeUndefined();
});
it("should have a message value defined after factory call", function() {
simpleAlertFactory.show(simpleTestData);
var isolated = defaultElement.isolateScope();
scope.$digest();
expect(isolated.msgObj[undefined].message).toBe(simpleText);
});
it("should not bleed data into a custom alert scope", function() {
simpleAlertFactory.show(simpleTestData);
var isolated = defaultElement.isolateScope();
scope.$digest();
expect(isolated.msgObj['test']).toBeUndefined();
});
it("should not have a message value defined after clear", function() {
simpleAlertFactory.show(simpleTestData);
var isolated = defaultElement.isolateScope();
scope.$digest();
expect(isolated.msgObj).toBeDefined();
simpleAlertFactory.clearAll();
scope.$digest();
expect(isolated.msgObj[undefined]).toEqual({}); //.toBeUndefined();
});
});
describe('a populated default simple alert with complex configuration', function() {
it("should not have a message value defined before factory call", function() {
var isolated = customElement.isolateScope();
expect(isolated.msgObj['test']).toBeUndefined();
});
it("should have all custom values defined after factory call", function() {
simpleAlertFactory.show(complexTestData);
var isolated = customElement.isolateScope();
scope.$digest();
expect(isolated.msgObj['test']).toEqual(complexTestData);
});
it("should not bleed data into the default alert scope", function() {
simpleAlertFactory.show(complexTestData);
var isolated = defaultElement.isolateScope();
scope.$digest();
expect(isolated.msgObj[undefined]).toBeUndefined();
});
// spyOn(complexTestData, 'callback'); expect to have been called with...
//
});
describe('callback handling', function() {
//spyOn(complexTestData,'callback');
})
describe('clearing a simple alert', function() {})
describe('canceling a simple alert', function() {})
});