├── .bowerrc
├── .codeclimate.yml
├── .editorconfig
├── .gitignore
├── .jshintrc
├── .travis.yml
├── .yo-rc.json
├── LICENSE
├── README.md
├── app
├── index.html
├── scripts
│ └── app.js
└── styles
│ └── app.css
├── bower.json
├── dist
├── angular-notification-icons.css
├── angular-notification-icons.js
├── angular-notification-icons.min.css
└── angular-notification-icons.min.js
├── gulp
├── build.js
├── inject.js
├── scripts.js
├── server.js
├── styles.js
├── unit-tests.js
└── watch.js
├── gulpfile.js
├── karma.conf.js
├── package-lock.json
├── package.json
└── src
├── angular-notification-icons.directive.js
├── angular-notification-icons.modules.js
├── less
├── angular-notification-icons.less
└── prefixer.less
├── template
└── notification-icon.html
└── test
├── .jshintrc
└── angular-notification-icons.spec.js
/.bowerrc:
--------------------------------------------------------------------------------
1 | {
2 | "directory": "bower_components",
3 | "json": "bower.json"
4 | }
5 |
--------------------------------------------------------------------------------
/.codeclimate.yml:
--------------------------------------------------------------------------------
1 | # Save as .codeclimate.yml (note leading .) in project root directory
2 | languages:
3 | Ruby: true
4 | JavaScript: true
5 | PHP: true
6 | Python: true
7 | # exclude_paths:
8 | - "dist/*"
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | charset = utf-8
9 | trim_trailing_whitespace = true
10 | insert_final_newline = true
11 |
12 | [*.md]
13 | trim_trailing_whitespace = false
14 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | bower_components/
3 | .sass-cache/
4 | .tmp/
5 | npm-debug.log
6 | build/
7 |
8 | .DS_Store
9 |
10 |
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "node": true, // Enable globals available when code is running inside of the NodeJS runtime environment.
3 | "browser": true, // Standard browser globals e.g. `window`, `document`.
4 | "eqeqeq": true, // Require triple equals i.e. `===`.
5 | "newcap": true, // Require capitalization of all constructor functions e.g. `new F()`.
6 | "noarg": true, // Prohibit use of `arguments.caller` and `arguments.callee`.
7 | "quotmark": "single", // Define quotes to string values.
8 | "regexp": true, // Prohibit `.` and `[^...]` in regular expressions.
9 | "undef": true, // Require all non-global variables be declared before they are used.
10 | "unused": true, // Warn unused variables.
11 | "strict": true, // Require `use strict` pragma in every file.
12 | "trailing": true, // Prohibit trailing whitespaces.
13 | "devel": true, // Allow development statements e.g. `console.log();`.
14 | "noempty": true, // Prohibit use of empty blocks.
15 | "globals": {
16 | "angular": false
17 | }
18 | }
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | cache:
3 | directories:
4 | - node_modules
5 | - bower_components
6 | install:
7 | - npm install
8 | - node_modules/.bin/bower install
9 | script: npm run ci
10 |
--------------------------------------------------------------------------------
/.yo-rc.json:
--------------------------------------------------------------------------------
1 | {
2 | "generator-gulp-angular": {
3 | "version": "0.11.0",
4 | "props": {
5 | "angularVersion": "~1.3.4",
6 | "angularModules": [
7 | {
8 | "key": "animate",
9 | "module": "ngAnimate"
10 | }
11 | ],
12 | "jQuery": {
13 | "key": "none"
14 | },
15 | "resource": {
16 | "key": "none",
17 | "module": null
18 | },
19 | "router": {
20 | "key": "none",
21 | "module": null
22 | },
23 | "ui": {
24 | "key": "none",
25 | "module": null
26 | },
27 | "cssPreprocessor": {
28 | "key": "less",
29 | "extension": "less"
30 | },
31 | "jsPreprocessor": {
32 | "key": "none",
33 | "extension": "js",
34 | "srcExtension": "js"
35 | },
36 | "htmlPreprocessor": {
37 | "key": "none",
38 | "extension": "html"
39 | },
40 | "bootstrapComponents": {
41 | "name": null,
42 | "version": null,
43 | "key": null,
44 | "module": null
45 | },
46 | "foundationComponents": {
47 | "name": null,
48 | "version": null,
49 | "key": null,
50 | "module": null
51 | },
52 | "paths": {
53 | "src": "app",
54 | "dist": "dist",
55 | "e2e": "e2e",
56 | "tmp": ".tmp"
57 | }
58 | }
59 | }
60 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Jacob Meacham
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # angular-notification-icons
2 | Add notification popups to any element http://jemonjam.com/angular-notification-icons
3 |
4 | [](https://travis-ci.org/jacob-meacham/angular-notification-icons)
5 | [](https://coveralls.io/r/jacob-meacham/angular-notification-icons?branch=develop)
6 | [](https://codeclimate.com/github/jacob-meacham/grunt-lcov-merge)
7 |
8 | 
9 |
10 | ```js
11 |
12 |
13 |
14 | ```
15 |
16 | ## Getting Started
17 | ### 1. Install components
18 |
19 | Bower:
20 | ```shell
21 | bower install angular-notification-icons --save
22 | ```
23 |
24 | npm:
25 | ```shell
26 | npm install angular-notification-icons --save
27 | ```
28 |
29 | ### 2. Add css and scripts
30 | Bower:
31 | ```html
32 |
33 |
34 |
35 |
36 |
37 | ```
38 |
39 | npm:
40 | ```html
41 |
42 |
43 |
44 |
45 |
46 | ```
47 |
48 | ### 3. Add a dependency to your app
49 | ```
50 | angular.module('MyApp', ['angular-notification-icons', 'ngAnimate']); // ngAnimate is only required if you want animations
51 | ```
52 |
53 | ### 4. Add a notification-icon element around any other element
54 | ```html
55 |
56 | ...
57 |
58 | ```
59 |
60 | angular-notification-icons is an angular directive that adds a notification popup on top of any element. The counter is tied to a scope variable and updating the count is as easy as updating the scope variable. angular-notification-icons comes with a number of canned animations and a default style, but it is easy to add your own styles or custom animations. angular-notification-icons can also optionally listen for DOM events and clear the count on a DOM event.
61 |
62 | angular-notification-icons has been tested with angular 1.3.x, 1.4.x, and 1.5.x. It will probably work with most recent versions of angular and angular-animate.
63 |
64 | ## Demo App
65 | To run the demo app, run `npm install`, `bower install` and then `gulp serve`.
66 |
67 | ## Webpack and ES6
68 | angular-notification-icons can be used in a webpack Angular application. To the top of your main application, add:
69 | ```
70 | import angular from 'angular'
71 | import 'angular-animate'
72 | import 'angular-notification-icons'
73 | import 'angular-notification-icons/dist/angular-notification-icons.css'
74 | // Can also use less with a less-loader:
75 | // import 'angular-notification-icons/src/less/angular-notification-icons.less'
76 | ```
77 |
78 | ## Basic Usage
79 |
80 | ### Counter
81 | The only required attribute for angular-notification-icons is 'count'. This uses two-way binding to bind a scope variable to the isolate scope. This makes updating the count very simple, since your controller has full control over how it's set.
82 | ```html
83 |
84 | ...
85 |
86 | ```
87 | When myScopeVariable is <= 0, the notification icon will not be visible. Once myScopeVariable > 0, the notification will show up.
88 |
89 | [Live Demo](http://jemonjam.com/angular-notification-icons#basic)
90 |
91 | ### Built-in Animations
92 | angular-notification-icons comes with a few prebuilt animations for your use. Note that these are only available if you are using the ngAnimate module
93 |
94 | * bounce
95 | * fade
96 | * grow
97 | * shake
98 |
99 | There are three separate animation events: appear, update, and disappear. Appear is triggered when the counter goes from 0 to non-zero. Update is trigger when the counter increments or decrements but does not go to or from zero. Disappear is triggered when the counter goes from non-zero to zero. The default animation for appear and update is grow, and there is no default set for disappear. A common case is to use the same animation for appear and update, and you can use the 'animation' attribute for this case.
100 |
101 | ```html
102 |
103 | ...
104 |
105 | ```
106 | This will create a notification that bounces when appearing and when the counter is updated. All three animation events can also be set explicitly:
107 |
108 | ```html
109 |
110 | ...
111 |
112 | ```
113 | This will create a notification that bounces when appearing, shakes when updates, and fades away when disappearing. Because all of these attributes do not use two-way binding, if you're using a variable for the animation, you'll want to use {{myVariable}} when binding.
114 |
115 | [Live Demo](http://jemonjam.com/angular-notification-icons#animations)
116 |
117 | ### DOM Events
118 | angular-notification-icons can respond to DOM events to clear the counter. This clears the scope variable and runs an $apply. Your controller can $watch the variable if you want to react to clearing the counter.
119 |
120 | ```html
121 |
122 | ...
123 |
124 | ```
125 | Will cause the count to be cleared upon click. Any DOM event name is valid as a clear-trigger. Because clear-trigger does not use two-way binding, if you're using a variable as the trigger, you'll want to use {{myVariable}} when binding.
126 |
127 | [Live Demo](http://jemonjam.com/angular-notification-icons#dom-events)
128 |
129 | ## Customizing
130 | angular-notification-icons was designed to be very simple to customize so that it fits the feel of your app.
131 |
132 | ### Adding Custom Style
133 | Adding custom style is done via CSS. When the directive is created, it adds a few elements to the DOM
134 | ```html
135 |
136 |
143 |
144 | ```
145 |
146 | You can add styling at any level. For instance, if you just want to change the look of the notifaction icon, you can add to your app's css:
147 |
148 | ```css
149 | .angular-notification-icons-icon {
150 | left: -10px;
151 | background: yellow;
152 | color: black;
153 | width: 30px;
154 | height: 30px;
155 | font-weight: bolder;
156 | font-size: 1.2em;
157 | }
158 | ```
159 | Which will make the notification icon appear on the left with a yellow background and bold, larger text.
160 | [Live Demo](http://jemonjam.com/angular-notification-icons#custom-style)
161 |
162 | ### Adding Custom Animations
163 | Adding a custom animation is as simple as adding custom styles. angular-notification-icons uses the standard [angular-animate](https://docs.angularjs.org/guide/animations) module for providing animations. This means that you can use either CSS keyframes or CSS transitions to build animations.
164 |
165 | ```css
166 | .angular-notification-icons-icon.my-custom-animation {
167 | transition:0.5s linear all;
168 | }
169 |
170 | .angular-notification-icons-icon.my-custom-animation-add {
171 | background: black;
172 | color: white;
173 | }
174 |
175 | .angular-notification-icons-icon.my-custom-animation-add-active {
176 | background: yellow;
177 | color: black;
178 | }
179 |
180 | .angular-notification-icons-icon.my-custom-keyframe-animation {
181 | animation: custom_keyframe_animation 0.5s;
182 | }
183 | @keyframes custom_keyframe_animation {
184 | from {
185 | opacity: 0;
186 | }
187 | to {
188 | opacity: 1;
189 | }
190 | }
191 | ```
192 |
193 | Adding your animation is as simple as specifying it by name on the directive
194 | ```js
195 |
196 | ...
197 |
198 | ```
199 | [Live Demo](http://jemonjam.com/angular-notification-icons#custom-style)
200 |
201 | ## Advanced Usage
202 |
203 | ### hideCount
204 | If you don't want the count number appear, you can hide the count using the 'hide-count' attribute
205 | ```html
206 |
207 | ...
208 |
209 | ```
210 | When myCount > 0, the notification icon will be visible, but the number will be hidden. When myCount <= 0, the icon will be hidden as normal.
211 |
212 | [Live Demo](http://jemonjam.com/angular-notification-icons#hide-count)
213 |
214 | ### alwaysShow
215 | If you *always* want the count number to appear, even when 0 or negative, you can add the 'always-show' attribute
216 | ```html
217 |
218 | ...
219 |
220 | ```
221 |
222 | [Live Demo](http://jemonjam.com/angular-notification-icons#always-show)
223 |
224 |
225 | ### Pill shape
226 | When the number of notifications grows large enough, the icon changes to a pill shape. This is achieved by adding the css class wide-icon to the icon's div. By default, the shape transitions to a pill once the count is greater than or equal to 100, but is configurable via the attribute 'wide-threshold'.
227 | ```html
228 |
229 | ...
230 |
231 | ```
232 | This will change the shape to a pill once myCount >= 10.
233 |
234 | [Live Demo](http://jemonjam.com/angular-notification-icons#pill)
235 |
236 | ## Helping Out
237 | Pull requests are gladly accepted! Be sure to run `npm run build` to ensure that your PR is lint-free and all the tests pass.
238 |
--------------------------------------------------------------------------------
/app/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | angularNotifications
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
25 |
26 |
27 |
31 |
32 |
33 |
Getting Started
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
Default Animations
52 |
53 |
54 |
55 |
56 |
57 | Grow
58 |
59 |
60 |
61 |
62 |
63 |
64 | Bounce
65 |
66 |
67 |
68 |
69 |
70 |
71 | Shake
72 |
73 |
74 |
75 |
76 |
77 |
78 | Bounce on disappear
79 |
80 |
81 |
82 |
83 | Add Notification
84 |
85 | Clear Notifications
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
Custom style and animations
112 |
113 |
114 |
115 |
116 |
117 | Add Notification
118 |
119 | Clear Notifications
120 |
121 |
122 |
123 |
124 |
125 | JS
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 | CSS
134 |
135 | .custom-style .angular-notification-icons-icon {
136 | left: -10px;
137 | background: yellow;
138 | color: black;
139 | width: 30px;
140 | height: 30px;
141 | font-weight: bolder;
142 | font-size: 1.2em;
143 | }
144 |
145 | .angular-notification-icons-icon.my-custom-animation {
146 | transition:0.5s linear all;
147 | }
148 |
149 | .angular-notification-icons-icon.my-custom-animation-add {
150 | background: black;
151 | color: white;
152 | }
153 |
154 | .angular-notification-icons-icon.my-custom-animation-add-active {
155 | background: yellow;
156 | color: black;
157 | }
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
DOM Events
167 |
168 |
169 |
170 | Click to Clear
171 |
172 |
173 |
174 |
175 | Hover to Clear
176 |
177 |
178 |
179 |
180 |
181 | Add Notification
182 |
183 |
184 |
185 |
186 |
187 | Click to Clear
188 |
189 |
190 |
191 | Hover to Clear
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
Hidden Counter
200 |
201 |
202 |
203 |
204 |
205 | Add Notification
206 |
207 | Clear Notifications
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
Always Show
222 |
223 |
224 |
225 |
226 |
227 | Add Notification
228 |
229 | Remove Notification
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
Pill Shape
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
Usage
262 |
1. Install bower component
263 |
264 | bower install angular-notification-icons --save
265 |
266 |
267 |
2. Add css and script
268 |
269 |
270 |
271 |
3. Add a dependency to your app
272 |
273 | angular.module('MyApp', ['angular-notification-icons']);
274 |
275 |
276 |
4. Add a notification-icon element around any other element
277 |
278 |
279 | ...
280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 |
297 |
298 |
299 |
--------------------------------------------------------------------------------
/app/scripts/app.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var app = angular.module('angular-notification-icons.demo', ['ngRoute', 'hljs', 'angular-notification-icons', 'ngAnimate', 'angular-notification-icons.demo.controllers']);
4 |
5 | // Pre-define modules
6 | angular.module('angular-notification-icons.demo.controllers', []);
7 |
8 | app.config(['$routeProvider', '$locationProvider', function($routeProvider, $locationProvider) {
9 | $routeProvider.
10 | when('/', {
11 | controller: 'DemoController',
12 | controllerAs: 'vm'
13 | })
14 | .otherwise({
15 | redirectTo: '/'
16 | });
17 |
18 | $locationProvider.html5Mode(true);
19 | }]);
20 |
21 | var DemoController = function($interval) {
22 | var vm = this;
23 |
24 | var autoCounter = function(index, start, steps, delay) {
25 | var numIterations = 0;
26 | vm.autoPending[index] = start;
27 | $interval(function() {
28 | if (++numIterations > steps) {
29 | vm.autoPending[index] = start;
30 | numIterations = 0;
31 | } else {
32 | vm.autoPending[index] = vm.autoPending[index] + 1;
33 | }
34 | }, delay);
35 | };
36 |
37 | vm.autoPending = [0, 0];
38 | autoCounter(0, 1, 4, 1000);
39 | autoCounter(1, 98, 4, 1000);
40 |
41 | vm.scriptSrc = ' \n\n' +
42 | '\n' +
43 | '\n' +
44 | '';
45 | };
46 |
47 | var GifController = function($interval) {
48 | var vm = this;
49 |
50 | vm.autoPending = [0, 0, 0, 0];
51 |
52 | $interval(function() {
53 | vm.autoPending[0] = 1;
54 | }, 5000, 1);
55 | $interval(function() {
56 | vm.autoPending[1] = 1;
57 | }, 5800, 1);
58 | $interval(function() {
59 | vm.autoPending[2] = 1;
60 | }, 6600, 1);
61 | $interval(function() {
62 | vm.autoPending[3] = 1;
63 | }, 7400, 1);
64 |
65 | $interval(function() {
66 | vm.autoPending[0] = 2;
67 | vm.autoPending[1] = 2;
68 | vm.autoPending[2] = 2;
69 | vm.autoPending[3] = 2;
70 | }, 8400, 1);
71 | };
72 |
73 |
74 | angular.module('angular-notification-icons.demo.controllers')
75 | .controller('DemoController', ['$interval', DemoController])
76 | .controller('GifController', ['$interval', GifController]);
--------------------------------------------------------------------------------
/app/styles/app.css:
--------------------------------------------------------------------------------
1 | notification-icon {
2 | padding: 10px;
3 | }
4 |
5 | .page-header {
6 | padding-bottom: 9px;
7 | margin: 40px 0 20px;
8 | border-bottom: 1px solid #eee;
9 | }
10 |
11 | .page-header h1 {
12 | font-size: 48px;
13 | margin-bottom: 5px;
14 | }
15 |
16 | .page-header a {
17 | font-weight: bold;
18 | }
19 |
20 | .custom-style .angular-notifications-icon {
21 | left: -10px;
22 | background: yellow;
23 | color: black;
24 | width: 30px;
25 | height: 30px;
26 | font-weight: bolder;
27 | font-size: 1.2em;
28 | }
29 |
30 | .animations .row {
31 | text-align: center;
32 | }
33 |
34 | .animations notification-icon {
35 | display: table;
36 | margin: 0 auto;
37 | }
38 |
39 | .buttons {
40 | padding-top: 20px;
41 | }
42 |
43 | .code {
44 | padding-top: 30px;
45 | padding-bottom: 10px;
46 | }
47 |
48 | .angular-notifications-icon.my-custom-animation {
49 | transition:0.5s linear all;
50 | }
51 |
52 | .angular-notifications-icon.my-custom-animation-add {
53 | background: black;
54 | color: white;
55 | }
56 |
57 | .angular-notifications-icon.my-custom-animation-add-active {
58 | background: yellow;
59 | color: black;
60 | }
61 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular-notification-icons",
3 | "description": "angular directive to add notification popups to any element",
4 | "authors": [{ "name": "jemonjam", "homepage": "http://jemonjam.com"}],
5 | "keywords": ["angular", "angular-plugin", "directive", "notification"],
6 | "license": "MIT",
7 | "main": [
8 | "dist/angular-notification-icons.js",
9 | "dist/angular-notification-icons.css"
10 | ],
11 | "ignore": [
12 | "**/.*",
13 | "node_modules/",
14 | "bower_components/",
15 | "gulp/",
16 | "app/",
17 | "src/*.js",
18 | "src/test/",
19 | "src/template/",
20 | "karma.conf.js",
21 | "gulpfile.js",
22 | "package.json"
23 | ],
24 |
25 | "dependencies": {
26 | "angular": ">1.3.0"
27 | },
28 |
29 | "devDependencies": {
30 | "angular-mocks": "1.4.7",
31 | "angular-route": "1.4.7",
32 | "angular-animate": "1.4.7",
33 | "jquery": "2.1.3",
34 | "bootstrap": "4.0.0",
35 | "angular-bootstrap": "0.12.1",
36 | "fontawesome": "~4.3.0",
37 | "angular-highlightjs": "0.4.1"
38 | },
39 | "resolutions": {
40 | "angular": "~1.4.7"
41 | },
42 | "moduleType": [
43 | "amd",
44 | "globals",
45 | "node"
46 | ]
47 | }
48 |
--------------------------------------------------------------------------------
/dist/angular-notification-icons.css:
--------------------------------------------------------------------------------
1 | .angular-notifications-container {
2 | position: relative;
3 | display: table-cell;
4 | width: 100%;
5 | }
6 | .angular-notifications-icon.overlay {
7 | position: absolute;
8 | z-index: 1;
9 | }
10 | .angular-notifications-icon {
11 | right: -10px;
12 | top: -10px;
13 | border-radius: 50%;
14 | text-align: center;
15 | padding-left: 5px;
16 | padding-right: 5px;
17 | padding-top: 3px;
18 | min-width: 25px;
19 | height: 25px;
20 | line-height: 1.15;
21 | background: red;
22 | color: white;
23 | -webkit-user-select: none;
24 | -moz-user-select: none;
25 | -ms-user-select: none;
26 | user-select: none;
27 | cursor: default;
28 | }
29 | .angular-notifications-icon.wide-icon {
30 | border-radius: 30% / 50%;
31 | background-clip: padding-box;
32 | }
33 | .angular-notifications-icon.fade {
34 | animation: notification-fade-in 0.5s;
35 | }
36 | .angular-notifications-icon.bounce {
37 | animation: notification-bounce 0.5s;
38 | }
39 | .angular-notifications-icon.grow {
40 | animation: notification-grow 0.5s;
41 | }
42 | .angular-notifications-icon.shake {
43 | animation: notification-shake 0.5s;
44 | }
45 | @keyframes notification-fade-in {
46 | from {
47 | opacity: 0;
48 | }
49 | to {
50 | opacity: 1;
51 | }
52 | }
53 | @keyframes notification-bounce {
54 | 30% {
55 | transform: perspective(1px) translate(0, -8px);
56 | }
57 | 60% {
58 | transform: perspective(1px) translate(0, 0);
59 | }
60 | 80% {
61 | transform: perspective(1px) translate(0, -5px);
62 | }
63 | to {
64 | transform: perspective(1px) translate(0, 0);
65 | }
66 | }
67 | @keyframes notification-grow {
68 | 30% {
69 | transform: scale(1.2);
70 | }
71 | 60% {
72 | transform: scale(0.8);
73 | }
74 | to {
75 | transform: scale(1);
76 | }
77 | }
78 | @keyframes notification-shake {
79 | 20% {
80 | transform: rotate(20deg);
81 | }
82 | 40% {
83 | transform: rotate(-20deg);
84 | }
85 | 60% {
86 | transform: rotate(10deg);
87 | }
88 | 80% {
89 | transform: rotate(-10deg);
90 | }
91 | to {
92 | transform: rotate(0);
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/dist/angular-notification-icons.js:
--------------------------------------------------------------------------------
1 | ;(function(root, factory) {
2 | if (typeof define === 'function' && define.amd) {
3 | define(['angular'], factory);
4 | } else if (typeof exports === 'object') {
5 | module.exports = factory(require('angular'));
6 | } else {
7 | root.returnExports = factory(root.angular);
8 | }
9 | }(this, function(angular) {
10 | 'use strict';
11 |
12 | angular.module('angular-notification-icons', ['angular-notification-icons.tpls']);
13 | angular.module('angular-notification-icons.tpls', []);
14 |
15 | angular.module("angular-notification-icons.tpls").run(["$templateCache", function($templateCache) {$templateCache.put("template/notification-icon.html","");}]);
16 | /* global angular */
17 |
18 | 'use strict';
19 |
20 | var NotificationDirectiveController = function($scope, $animate, $q) {
21 | var self = this;
22 | self.visible = false;
23 | self.wideThreshold = self.wideThreshold || 100;
24 | self.alwaysShow = self.alwaysShow || false;
25 |
26 | var animationPromise;
27 | var animationSet = {
28 | appear: self.appearAnimation || self.animation || 'grow',
29 | update: self.updateAnimation || self.animation || 'grow',
30 | disappear: self.disappearAnimation
31 | };
32 |
33 | self.getElement = function(element) {
34 | return angular.element(element[0].querySelector('.angular-notifications-icon'));
35 | };
36 |
37 | self.init = function(element) {
38 | self.$element = self.getElement(element);
39 | if (self.clearTrigger) {
40 | element.on(self.clearTrigger, function() {
41 | self.count = 0;
42 | $scope.$apply();
43 | });
44 | }
45 | };
46 |
47 | var handleAnimation = function(animationClass) {
48 | if (animationClass) {
49 | if (animationPromise) {
50 | $animate.cancel(animationPromise);
51 | }
52 |
53 | // Can't chain because the chained promise doesn't have a cancel function.
54 | animationPromise = $animate.addClass(self.$element, animationClass);
55 | animationPromise.then(function() {
56 | self.$element.removeClass(animationClass);
57 | return $q.when(true);
58 | });
59 |
60 | return animationPromise;
61 | }
62 |
63 | return $q.when(false);
64 | };
65 |
66 | var appear = function() {
67 | self.visible = true;
68 | handleAnimation(animationSet.appear);
69 | };
70 |
71 | var clear = function() {
72 | handleAnimation(animationSet.disappear).then(function(needsDigest) {
73 | self.visible = false;
74 | if (needsDigest) {
75 | $scope.$apply();
76 | }
77 | });
78 | };
79 |
80 | var update = function() {
81 | handleAnimation(animationSet.update);
82 | };
83 |
84 | $scope.$watch(function() { return self.count; }, function() {
85 | if (self.visible === false && (self.alwaysShow || self.count > 0)) {
86 | appear();
87 | } else if (!self.alwaysShow && self.visible === true && self.count <= 0) {
88 | // Only clear if we're not always showing
89 | clear();
90 | } else {
91 | update();
92 | }
93 |
94 | // Use more of a pill shape if the count is high enough.
95 | if (Math.abs(self.count) >= self.wideThreshold) {
96 | self.$element.addClass('wide-icon');
97 | } else {
98 | self.$element.removeClass('wide-icon');
99 | }
100 | });
101 | };
102 |
103 | var notificationDirective = function() {
104 | return {
105 | restrict: 'EA',
106 | scope: {
107 | count: '=',
108 | hideCount: '@',
109 | alwaysShow: '@',
110 | animation: '@',
111 | appearAnimation: '@',
112 | disappearAnimation: '@',
113 | updateAnimation: '@',
114 | clearTrigger: '@',
115 | wideThreshold: '@'
116 | },
117 | controller: 'NotificationDirectiveController',
118 | controllerAs: 'notification',
119 | bindToController: true,
120 | transclude: true,
121 | templateUrl: 'template/notification-icon.html',
122 | link: function(scope, element, attrs, ctrl) {
123 | ctrl.init(element);
124 | }
125 | };
126 | };
127 |
128 | angular.module('angular-notification-icons')
129 | .controller('NotificationDirectiveController', ['$scope', '$animate', '$q', NotificationDirectiveController])
130 | .directive('notificationIcon', notificationDirective);
131 |
132 | return { NotificationDirectiveController: NotificationDirectiveController, notificationDirective: notificationDirective };
133 | }));
134 |
--------------------------------------------------------------------------------
/dist/angular-notification-icons.min.css:
--------------------------------------------------------------------------------
1 | .angular-notifications-container{position:relative;display:table-cell;width:100%}.angular-notifications-icon.overlay{position:absolute;z-index:1}.angular-notifications-icon{right:-10px;top:-10px;border-radius:50%;text-align:center;padding-left:5px;padding-right:5px;padding-top:3px;min-width:25px;height:25px;line-height:1.15;background:red;color:#fff;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;cursor:default}.angular-notifications-icon.wide-icon{border-radius:30%/50%;background-clip:padding-box}.angular-notifications-icon.fade{animation:notification-fade-in .5s}.angular-notifications-icon.bounce{animation:notification-bounce .5s}.angular-notifications-icon.grow{animation:notification-grow .5s}.angular-notifications-icon.shake{animation:notification-shake .5s}@keyframes notification-fade-in{0%{opacity:0}to{opacity:1}}@keyframes notification-bounce{30%{transform:perspective(1px) translate(0,-8px)}60%,to{transform:perspective(1px) translate(0,0)}80%{transform:perspective(1px) translate(0,-5px)}}@keyframes notification-grow{30%{transform:scale(1.2)}60%{transform:scale(.8)}to{transform:scale(1)}}@keyframes notification-shake{20%{transform:rotate(20deg)}40%{transform:rotate(-20deg)}60%{transform:rotate(10deg)}80%{transform:rotate(-10deg)}to{transform:rotate(0)}}
--------------------------------------------------------------------------------
/dist/angular-notification-icons.min.js:
--------------------------------------------------------------------------------
1 | !function(n,i){"function"==typeof define&&define.amd?define(["angular"],i):"object"==typeof exports?module.exports=i(require("angular")):n.returnExports=i(n.angular)}(this,function(n){"use strict";n.module("angular-notification-icons",["angular-notification-icons.tpls"]),n.module("angular-notification-icons.tpls",[]),n.module("angular-notification-icons.tpls").run(["$templateCache",function(n){n.put("template/notification-icon.html",'')}]);var i=function(i,t,e){var o=this;o.visible=!1,o.wideThreshold=o.wideThreshold||100,o.alwaysShow=o.alwaysShow||!1;var a,r={appear:o.appearAnimation||o.animation||"grow",update:o.updateAnimation||o.animation||"grow",disappear:o.disappearAnimation};o.getElement=function(i){return n.element(i[0].querySelector(".angular-notifications-icon"))},o.init=function(n){o.$element=o.getElement(n),o.clearTrigger&&n.on(o.clearTrigger,function(){o.count=0,i.$apply()})};var c=function(n){return n?(a&&t.cancel(a),a=t.addClass(o.$element,n),a.then(function(){return o.$element.removeClass(n),e.when(!0)}),a):e.when(!1)},l=function(){o.visible=!0,c(r.appear)},u=function(){c(r.disappear).then(function(n){o.visible=!1,n&&i.$apply()})},s=function(){c(r.update)};i.$watch(function(){return o.count},function(){o.visible===!1&&(o.alwaysShow||o.count>0)?l():!o.alwaysShow&&o.visible===!0&&o.count<=0?u():s(),Math.abs(o.count)>=o.wideThreshold?o.$element.addClass("wide-icon"):o.$element.removeClass("wide-icon")})},t=function(){return{restrict:"EA",scope:{count:"=",hideCount:"@",alwaysShow:"@",animation:"@",appearAnimation:"@",disappearAnimation:"@",updateAnimation:"@",clearTrigger:"@",wideThreshold:"@"},controller:"NotificationDirectiveController",controllerAs:"notification",bindToController:!0,transclude:!0,templateUrl:"template/notification-icon.html",link:function(n,i,t,e){e.init(i)}}};return n.module("angular-notification-icons").controller("NotificationDirectiveController",["$scope","$animate","$q",i]).directive("notificationIcon",t),{NotificationDirectiveController:i,notificationDirective:t}});
--------------------------------------------------------------------------------
/gulp/build.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var gulp = require('gulp');
4 |
5 | // TODO: why no templateCache in $?
6 | var $ = require('gulp-load-plugins')({
7 | pattern: ['gulp-*', 'main-bower-files', 'uglify-save-license', 'del']
8 | });
9 | var templateCache = require('gulp-angular-templatecache');
10 |
11 | module.exports = function(options) {
12 | gulp.task('clean', function (done) {
13 | $.del([options.dist + '/', options.tmp + '/'], done);
14 | });
15 |
16 | // TODO: Combine all of these pipes
17 | gulp.task('copy:templates', function() {
18 | return gulp.src(options.src + '/**/*.html')
19 | .pipe(templateCache({module: 'angular-notification-icons.tpls'}))
20 | .pipe(gulp.dest(options.tmp + '/templateCache'));
21 | });
22 |
23 | gulp.task('build', ['scripts:jshint', 'copy:templates'], function() {
24 | gulp.src([options.src + '/**/*.js', options.tmp + '/templateCache/*.js', '!' + options.src + '/**/*.spec.js'])
25 | .pipe($.angularFilesort()).on('error', options.errorHandler('AngularFilesort'))
26 | .pipe($.concat('angular-notification-icons.js'))
27 | .pipe($.umd({
28 | dependencies: function(file) {
29 | return [
30 | {
31 | name: 'angular'
32 | }
33 | ]
34 | },
35 | exports: function(file) {
36 | return '{ NotificationDirectiveController: NotificationDirectiveController, notificationDirective: notificationDirective }'
37 | },
38 | namespace: function(file) {
39 | return 'returnExports'
40 | }
41 | }))
42 | .pipe(gulp.dest(options.dist))
43 | .pipe($.uglify())
44 | .pipe($.rename('angular-notification-icons.min.js'))
45 | .pipe(gulp.dest(options.dist));
46 |
47 | return gulp.src(options.src + '/less/angular-notification-icons.less')
48 | .pipe($.less()).on('error', options.errorHandler('Less'))
49 | .pipe($.autoprefixer()).on('error', options.errorHandler('Autoprefixer'))
50 | .pipe($.rename('angular-notification-icons.css'))
51 | .pipe(gulp.dest(options.dist))
52 | .pipe($.csso())
53 | .pipe($.rename('angular-notification-icons.min.css'))
54 | .pipe(gulp.dest(options.dist));
55 | });
56 | };
57 |
--------------------------------------------------------------------------------
/gulp/inject.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var gulp = require('gulp');
4 |
5 | var $ = require('gulp-load-plugins')();
6 |
7 | var wiredep = require('wiredep').stream;
8 |
9 | module.exports = function(options) {
10 | var inject = function(srcDir, _injectOptions) {
11 | return function() {
12 | if (!_injectOptions) {
13 | _injectOptions = {};
14 | }
15 |
16 | var injectOptions = {
17 | ignorePath: [options.tmp + '/serve'],
18 | addRootSlash: false
19 | };
20 | injectOptions.srcStyles = _injectOptions.srcStyles || options.tmp + '/serve/**/*.css';
21 | injectOptions.srcJs = _injectOptions.srcJs || srcDir + '/**/*.js';
22 |
23 | var injectStyles = gulp.src(injectOptions.srcStyles, { read: false });
24 |
25 | var injectScripts = gulp.src([
26 | options.app + '/**/*.js',
27 | injectOptions.srcJs,
28 | '!' + srcDir + '/**/*.spec.js'
29 | ])
30 | .pipe($.angularFilesort()).on('error', options.errorHandler('AngularFilesort'));
31 |
32 | return gulp.src(options.app + '/*.html')
33 | .pipe($.inject(injectStyles, injectOptions))
34 | .pipe($.inject(injectScripts, injectOptions))
35 | .pipe(wiredep(options.wiredep))
36 | .pipe(gulp.dest(options.tmp + '/serve'));
37 | };
38 | };
39 | gulp.task('inject', ['scripts', 'styles'], inject(options.src));
40 | gulp.task('inject:dist', inject(options.dist, {srcStyles: options.dist + '/angular-notification-icons.css', srcJs: options.dist + '/angular-notification-icons.js'}));
41 | gulp.task('inject:dist:min', inject(options.dist, {srcStyles: options.dist + '/angular-notification-icons.min.css', srcJs: options.dist + '/angular-notification-icons.min.js'}));
42 | };
43 |
--------------------------------------------------------------------------------
/gulp/scripts.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var gulp = require('gulp');
4 | var browserSync = require('browser-sync');
5 |
6 | var $ = require('gulp-load-plugins')();
7 |
8 | module.exports = function(options) {
9 | gulp.task('scripts:jshint', function() {
10 | return gulp.src([options.src + '/**/*.js', options.app + '/**/*.js'])
11 | .pipe($.jshint())
12 | .pipe($.jshint.reporter('jshint-stylish'))
13 | .pipe($.jshint.reporter('fail'));
14 | });
15 |
16 | gulp.task('scripts', ['scripts:jshint'], function () {
17 | return gulp.src([options.src + '/**/*.js', options.app + '/**/*.js'])
18 | .pipe(browserSync.reload({ stream: true }))
19 | .pipe($.size());
20 | });
21 | };
22 |
--------------------------------------------------------------------------------
/gulp/server.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var gulp = require('gulp');
4 | var browserSync = require('browser-sync');
5 | var browserSyncSpa = require('browser-sync-spa');
6 |
7 | var util = require('util');
8 |
9 | module.exports = function(options) {
10 |
11 | function browserSyncInit(baseDir, browser) {
12 | browser = browser === undefined ? 'default' : browser;
13 |
14 | var routes = null;
15 | if(baseDir === options.app || (util.isArray(baseDir) && baseDir.indexOf(options.app) !== -1)) {
16 | routes = {
17 | '/bower_components': 'bower_components',
18 | '/app': 'app',
19 | '/src': options.src,
20 | '/dist': options.dist,
21 | '/template': options.src + '/template'
22 | };
23 | }
24 |
25 | var server = {
26 | baseDir: baseDir,
27 | routes: routes
28 | };
29 |
30 | browserSync.instance = browserSync.init({
31 | startPath: '/',
32 | server: server,
33 | browser: browser
34 | });
35 | }
36 |
37 | browserSync.use(browserSyncSpa({
38 | selector: '[ng-app]'// Only needed for angular apps
39 | }));
40 |
41 | gulp.task('serve', ['watch'], function () {
42 | browserSyncInit([options.tmp + '/serve', options.app]);
43 | });
44 |
45 | gulp.task('serve:dist', ['build', 'watch:dist'], function () {
46 | browserSyncInit([options.tmp + '/serve', options.app]);
47 | });
48 |
49 | gulp.task('serve:dist:min', ['build', 'watch:dist:min'], function () {
50 | browserSyncInit([options.tmp + '/serve', options.app]);
51 | });
52 | };
53 |
--------------------------------------------------------------------------------
/gulp/styles.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var gulp = require('gulp');
4 | var browserSync = require('browser-sync');
5 |
6 | var $ = require('gulp-load-plugins')();
7 |
8 | module.exports = function(options) {
9 | gulp.task('styles', function () {
10 | var lessOptions = {
11 | options: [
12 | 'bower_components',
13 | options.src
14 | ]
15 | };
16 |
17 | return gulp.src(options.src + '/**/*.less')
18 | .pipe($.sourcemaps.init())
19 | .pipe($.less(lessOptions)).on('error', options.errorHandler('Less'))
20 | .pipe($.autoprefixer()).on('error', options.errorHandler('Autoprefixer'))
21 | .pipe($.sourcemaps.write())
22 | .pipe(gulp.dest(options.tmp + '/serve/'))
23 | .pipe(browserSync.reload({ stream: true }));
24 | });
25 | };
26 |
--------------------------------------------------------------------------------
/gulp/unit-tests.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var gulp = require('gulp');
4 |
5 | var wiredep = require('wiredep');
6 | var karma = require('karma');
7 | var concat = require('concat-stream');
8 | var _ = require('lodash');
9 | var $ = require('gulp-load-plugins')();
10 |
11 | module.exports = function(options) {
12 | function listFiles(callback) {
13 | var wiredepOptions = _.extend({}, options.wiredep, {
14 | dependencies: true,
15 | devDependencies: true
16 | });
17 | var bowerDeps = wiredep(wiredepOptions);
18 |
19 | var specFiles = [
20 | options.src + '/**/*.spec.js'
21 | ];
22 |
23 | var htmlFiles = [
24 | options.src + '/**/*.html',
25 | ];
26 |
27 | var srcFiles = [
28 | options.src + '/**/*.js'
29 | ].concat(specFiles.map(function(file) {
30 | return '!' + file;
31 | }));
32 |
33 |
34 | gulp.src(srcFiles)
35 | .pipe($.angularFilesort()).on('error', options.errorHandler('AngularFilesort'))
36 | .pipe(concat(function(files) {
37 | callback(bowerDeps.js
38 | .concat(_.pluck(files, 'path'))
39 | .concat(htmlFiles)
40 | .concat(specFiles));
41 | }));
42 | }
43 |
44 | function runTests (singleRun, done) {
45 | listFiles(function(files) {
46 | karma.server.start({
47 | configFile: __dirname + '/../karma.conf.js',
48 | files: files,
49 | singleRun: singleRun,
50 | autoWatch: !singleRun
51 | }, done);
52 | });
53 | }
54 |
55 | gulp.task('test', ['scripts'], function(done) {
56 | runTests(true, done);
57 | });
58 | gulp.task('test:auto', ['watch'], function(done) {
59 | runTests(false, done);
60 | });
61 | };
62 |
--------------------------------------------------------------------------------
/gulp/watch.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var gulp = require('gulp');
4 | var browserSync = require('browser-sync');
5 |
6 | function isOnlyChange(event) {
7 | return event.type === 'changed';
8 | }
9 |
10 | module.exports = function(options) {
11 | var watch = function(srcDir, watchOptions) {
12 | return function() {
13 | if (!watchOptions) {
14 | watchOptions = {};
15 | }
16 |
17 | watchOptions.watchSrcLess = watchOptions.watchSrcLess || true;
18 | watchOptions.watchSrcHtml = watchOptions.watchSrcHtml || true;
19 | watchOptions.srcJs = watchOptions.srcJs || '/**/*.js';
20 |
21 | gulp.watch([options.app + '/*.html', 'bower.json'], ['inject']);
22 |
23 | if (watchOptions.watchSrcLess) {
24 | gulp.watch([
25 | srcDir + '/**/*.less'
26 | ], function(event) {
27 | if(isOnlyChange(event)) {
28 | gulp.start('styles');
29 | } else {
30 | gulp.start('inject');
31 | }
32 | });
33 | }
34 |
35 | gulp.watch([srcDir + watchOptions.srcJs, options.app + '/**/*.js'], function(event) {
36 | if(isOnlyChange(event)) {
37 | gulp.start('scripts');
38 | } else {
39 | gulp.start('inject');
40 | }
41 | });
42 |
43 | var htmlToWatch = [options.app + '/*.html'];
44 | if (watchOptions.watchSrcHtml) {
45 | htmlToWatch.push(srcDir + '/**/*.html');
46 | }
47 |
48 | return gulp.watch(htmlToWatch, function(event) {
49 | browserSync.reload(event.path);
50 | });
51 | };
52 | };
53 |
54 | gulp.task('watch', ['inject'], watch(options.src));
55 | gulp.task('watch:dist', ['inject:dist'], watch(options.dist, {watchSrcLess: false, watchSrcHtml: false, srcJs: 'angular-notification-icons.js'}));
56 | gulp.task('watch:dist:min', ['inject:dist:min'], watch(options.dist, {watchSrcLess: false, watchSrcHtml: false, srcJs: 'angular-notification-icons.min.js'}));
57 | };
58 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var gulp = require('gulp');
4 | var gutil = require('gulp-util');
5 | var wrench = require('wrench');
6 | var coveralls = require('gulp-coveralls');
7 |
8 | var options = {
9 | app: 'app',
10 | src: 'src',
11 | dist: 'dist',
12 | tmp: '.tmp',
13 | root: '.',
14 | errorHandler: function(title) {
15 | return function(err) {
16 | gutil.log(gutil.colors.red('[' + title + ']'), err.toString());
17 | this.emit('end');
18 | };
19 | },
20 | wiredep: {
21 | directory: 'bower_components',
22 | devDependencies: true
23 | }
24 | };
25 |
26 | wrench.readdirSyncRecursive('./gulp').filter(function(file) {
27 | return (/\.js$/i).test(file);
28 | }).map(function(file) {
29 | require('./gulp/' + file)(options);
30 | });
31 |
32 | gulp.task('default', ['clean'], function() {
33 | gulp.start('build');
34 | });
35 |
36 | gulp.task('ci', ['build', 'test'], function() {
37 | return gulp.src('build/**/lcov.info')
38 | .pipe(coveralls());
39 | });
40 |
--------------------------------------------------------------------------------
/karma.conf.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = function(config) {
4 |
5 | var configuration = {
6 | autoWatch : false,
7 | colors: true,
8 |
9 | frameworks: ['mocha', 'chai', 'sinon', 'sinon-chai'],
10 |
11 | browsers : ['PhantomJS'],
12 |
13 | reporters: ['mocha', 'coverage'],
14 |
15 | preprocessors: {
16 | 'src/**/*.js' : 'coverage',
17 | 'src/**/*.html': ['ng-html2js']
18 | },
19 |
20 | coverageReporter: {
21 | type: 'lcov',
22 | dir: 'build/coverage/client'
23 | },
24 |
25 | ngHtml2JsPreprocessor: {
26 | stripPrefix: 'src/',
27 | moduleName: 'angular-notifications.tpls'
28 | },
29 | };
30 |
31 | config.set(configuration);
32 | };
33 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular-notification-icons",
3 | "version": "1.0.0",
4 | "description": "angular directive to add notification popups to any element",
5 | "author": {
6 | "name": "jemonjam",
7 | "url": "http://jemonjam.com"
8 | },
9 | "license": "MIT",
10 | "main": "dist/angular-notification-icons.js",
11 | "files": [
12 | "dist",
13 | "src",
14 | "LICENSE"
15 | ],
16 | "scripts": {
17 | "start": "gulp",
18 | "run": "gulp serve",
19 | "test": "gulp test",
20 | "build": "gulp build",
21 | "ci": "gulp ci"
22 | },
23 | "bugs": "https://github.com/jacob-meacham/angular-notification-icons/issues",
24 | "repository": "https://github.com/jacob-meacham/angular-notification-icons",
25 | "keywords": [
26 | "angular",
27 | "angular-plugin",
28 | "directive",
29 | "notification"
30 | ],
31 | "peerDependencies": {
32 | "angular": ">1.3.0"
33 | },
34 | "engines": {
35 | "node" : "<=5.4.0"
36 | },
37 | "devDependencies": {
38 | "bower": "^1.7.9",
39 | "browser-sync": "~2.1.4",
40 | "browser-sync-spa": "~1.0.2",
41 | "chai": "^3.5.0",
42 | "chalk": "~0.5.1",
43 | "concat-stream": "~1.4.7",
44 | "del": "~1.1.1",
45 | "gulp": "~3.8.10",
46 | "gulp-angular-filesort": "~1.0.4",
47 | "gulp-angular-templatecache": "^1.6.0",
48 | "gulp-autoprefixer": "~2.1.0",
49 | "gulp-concat": "^2.5.2",
50 | "gulp-coveralls": "^0.1.4",
51 | "gulp-csso": "~1.0.0",
52 | "gulp-filter": "~2.0.2",
53 | "gulp-flatten": "~0.0.4",
54 | "gulp-inject": "~1.1.1",
55 | "gulp-jshint": "^1.10.0",
56 | "gulp-less": "~3.0.0",
57 | "gulp-load-plugins": "~0.8.0",
58 | "gulp-minify-html": "~0.1.7",
59 | "gulp-ng-annotate": "~0.5.2",
60 | "gulp-protractor": "~0.0.12",
61 | "gulp-rename": "~1.2.0",
62 | "gulp-replace": "~0.5.0",
63 | "gulp-rev": "~3.0.1",
64 | "gulp-rev-replace": "~0.3.1",
65 | "gulp-size": "~1.2.0",
66 | "gulp-sourcemaps": "~1.3.0",
67 | "gulp-uglify": "~1.1.0",
68 | "gulp-umd": "^0.2.0",
69 | "gulp-useref": "~1.1.0",
70 | "gulp-util": "~3.0.2",
71 | "http-proxy": "~1.8.0",
72 | "jshint-stylish": "^1.0.2",
73 | "karma": "~0.12.31",
74 | "karma-chai": "0.1.0",
75 | "karma-chrome-launcher": "0.1.8",
76 | "karma-coverage": "0.3.1",
77 | "karma-mocha": "0.1.10",
78 | "karma-mocha-reporter": "1.0.2",
79 | "karma-ng-html2js-preprocessor": "0.1.2",
80 | "karma-phantomjs-launcher": "0.1.4",
81 | "karma-sinon": "1.0.4",
82 | "karma-sinon-chai": "0.3.0",
83 | "lodash": "~3.2.0",
84 | "main-bower-files": "~2.5.0",
85 | "merge-stream": "~0.1.7",
86 | "mocha": "^3.0.2",
87 | "protractor": "~1.7.0",
88 | "require-dir": "~0.1.0",
89 | "sinon": "^1.17.5",
90 | "uglify-save-license": "~0.4.1",
91 | "wiredep": "~2.2.0",
92 | "wrench": "~1.5.8"
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/src/angular-notification-icons.directive.js:
--------------------------------------------------------------------------------
1 | /* global angular */
2 |
3 | 'use strict';
4 |
5 | var NotificationDirectiveController = function($scope, $animate, $q) {
6 | var self = this;
7 | self.visible = false;
8 | self.wideThreshold = self.wideThreshold || 100;
9 | self.alwaysShow = self.alwaysShow || false;
10 |
11 | var animationPromise;
12 | var animationSet = {
13 | appear: self.appearAnimation || self.animation || 'grow',
14 | update: self.updateAnimation || self.animation || 'grow',
15 | disappear: self.disappearAnimation
16 | };
17 |
18 | self.getElement = function(element) {
19 | return angular.element(element[0].querySelector('.angular-notifications-icon'));
20 | };
21 |
22 | self.init = function(element) {
23 | self.$element = self.getElement(element);
24 | if (self.clearTrigger) {
25 | element.on(self.clearTrigger, function() {
26 | self.count = 0;
27 | $scope.$apply();
28 | });
29 | }
30 | };
31 |
32 | var handleAnimation = function(animationClass) {
33 | if (animationClass) {
34 | if (animationPromise) {
35 | $animate.cancel(animationPromise);
36 | }
37 |
38 | // Can't chain because the chained promise doesn't have a cancel function.
39 | animationPromise = $animate.addClass(self.$element, animationClass);
40 | animationPromise.then(function() {
41 | self.$element.removeClass(animationClass);
42 | return $q.when(true);
43 | });
44 |
45 | return animationPromise;
46 | }
47 |
48 | return $q.when(false);
49 | };
50 |
51 | var appear = function() {
52 | self.visible = true;
53 | handleAnimation(animationSet.appear);
54 | };
55 |
56 | var clear = function() {
57 | handleAnimation(animationSet.disappear).then(function(needsDigest) {
58 | self.visible = false;
59 | if (needsDigest) {
60 | $scope.$apply();
61 | }
62 | });
63 | };
64 |
65 | var update = function() {
66 | handleAnimation(animationSet.update);
67 | };
68 |
69 | $scope.$watch(function() { return self.count; }, function() {
70 | if (self.visible === false && (self.alwaysShow || self.count > 0)) {
71 | appear();
72 | } else if (!self.alwaysShow && self.visible === true && self.count <= 0) {
73 | // Only clear if we're not always showing
74 | clear();
75 | } else {
76 | update();
77 | }
78 |
79 | // Use more of a pill shape if the count is high enough.
80 | if (Math.abs(self.count) >= self.wideThreshold) {
81 | self.$element.addClass('wide-icon');
82 | } else {
83 | self.$element.removeClass('wide-icon');
84 | }
85 | });
86 | };
87 |
88 | var notificationDirective = function() {
89 | return {
90 | restrict: 'EA',
91 | scope: {
92 | count: '=',
93 | hideCount: '@',
94 | alwaysShow: '@',
95 | animation: '@',
96 | appearAnimation: '@',
97 | disappearAnimation: '@',
98 | updateAnimation: '@',
99 | clearTrigger: '@',
100 | wideThreshold: '@'
101 | },
102 | controller: 'NotificationDirectiveController',
103 | controllerAs: 'notification',
104 | bindToController: true,
105 | transclude: true,
106 | templateUrl: 'template/notification-icon.html',
107 | link: function(scope, element, attrs, ctrl) {
108 | ctrl.init(element);
109 | }
110 | };
111 | };
112 |
113 | angular.module('angular-notification-icons')
114 | .controller('NotificationDirectiveController', ['$scope', '$animate', '$q', NotificationDirectiveController])
115 | .directive('notificationIcon', notificationDirective);
116 |
--------------------------------------------------------------------------------
/src/angular-notification-icons.modules.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | angular.module('angular-notification-icons', ['angular-notification-icons.tpls']);
4 | angular.module('angular-notification-icons.tpls', []);
5 |
--------------------------------------------------------------------------------
/src/less/angular-notification-icons.less:
--------------------------------------------------------------------------------
1 | @import "./prefixer.less";
2 |
3 | .angular-notifications-container {
4 | position: relative;
5 | display: table-cell;
6 | width: 100%;
7 | }
8 |
9 | .angular-notifications-icon.overlay {
10 | position: absolute;
11 | z-index: 1;
12 | }
13 |
14 | .angular-notifications-icon {
15 | right: -10px;
16 | top: -10px;
17 | border-radius: 50%;
18 | text-align: center;
19 | padding-left: 5px;
20 | padding-right: 5px;
21 | padding-top: 3px;
22 | min-width: 25px;
23 | height: 25px;
24 | line-height: 1.15;
25 | background: red;
26 | color: white;
27 | user-select: none;
28 | cursor: default;
29 | }
30 |
31 | .angular-notifications-icon.wide-icon {
32 | .border-radius(~"30% / 50%");
33 | }
34 |
35 | .angular-notifications-icon.fade {
36 | .animation(notification-fade-in 0.5s);
37 | }
38 |
39 | .angular-notifications-icon.bounce {
40 | .animation(notification-bounce 0.5s);
41 | }
42 |
43 | .angular-notifications-icon.grow {
44 | .animation(notification-grow 0.5s);
45 | }
46 |
47 | .angular-notifications-icon.shake {
48 | .animation(notification-shake 0.5s);
49 | }
50 |
51 | @-webkit-keyframes notification-fade-in {.fade-frames;}
52 | @-moz-keyframes notification-fade-in {.fade-frames;}
53 | @-ms-keyframes notification-fade-in {.fade-frames;}
54 | @-o-keyframes notification-fade-in {.fade-frames;}
55 | @keyframes notification-fade-in {.fade-frames;}
56 | .fade-frames () {
57 | from {
58 | opacity: 0;
59 | }
60 |
61 | to {
62 | opacity: 1;
63 | }
64 | }
65 |
66 | @-webkit-keyframes notification-bounce {.bounce-frames;}
67 | @-moz-keyframes notification-bounce {.bounce-frames;}
68 | @-ms-keyframes notification-bounce {.bounce-frames;}
69 | @-o-keyframes notification-bounce {.bounce-frames;}
70 | @keyframes notification-bounce {.bounce-frames;}
71 | .bounce-frames () {
72 | 30% {
73 | .translate(0, -8px);
74 | }
75 |
76 | 60% {
77 | .translate(0, 0);
78 | }
79 |
80 | 80% {
81 | .translate(0, -5px);
82 | }
83 |
84 | to {
85 | .translate(0, 0);
86 | }
87 | }
88 |
89 | @-webkit-keyframes notification-grow {.grow-frames;}
90 | @-moz-keyframes notification-grow {.grow-frames;}
91 | @-ms-keyframes notification-grow {.grow-frames;}
92 | @-o-keyframes notification-grow {.grow-frames;}
93 | @keyframes notification-grow {.grow-frames;}
94 | .grow-frames () {
95 | 30% {
96 | .scale(1.2);
97 | }
98 |
99 | 60% {
100 | .scale(0.8);
101 | }
102 |
103 | to {
104 | transform: scale(1);
105 | }
106 | }
107 |
108 | @-webkit-keyframes notification-shake {.shake-frames;}
109 | @-moz-keyframes notification-shake {.shake-frames;}
110 | @-ms-keyframes notification-shake {.shake-frames;}
111 | @-o-keyframes notification-shake {.shake-frames;}
112 | @keyframes notification-shake {.shake-frames;}
113 | .shake-frames () {
114 | 20% {
115 | .rotate(20deg);
116 | }
117 |
118 | 40% {
119 | .rotate(-20deg);
120 | }
121 |
122 | 60% {
123 | .rotate(10deg);
124 | }
125 |
126 | 80% {
127 | .rotate(-10deg);
128 | }
129 |
130 | to {
131 | .rotate(0);
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/src/less/prefixer.less:
--------------------------------------------------------------------------------
1 | // Credit to LESS Elements for the motivation and
2 | // to CSS3Please.com for implementation.
3 | //
4 | // Copyright (c) 2012 Joel Sutherland
5 | // MIT Licensed:
6 | // http://www.opensource.org/licenses/mit-license.php
7 | //
8 | //---------------------------------------------------
9 | // This is a stripped down version of LESS-Prefixer (http://lessprefixer.com/)
10 |
11 | .animation(@args) {
12 | -webkit-animation: @args;
13 | -moz-animation: @args;
14 | -ms-animation: @args;
15 | -o-animation: @args;
16 | animation: @args;
17 | }
18 |
19 | .border-radius(@args) {
20 | -webkit-border-radius: @args;
21 | border-radius: @args;
22 |
23 | background-clip: padding-box;
24 | }
25 |
26 | .transform(@args) {
27 | -webkit-transform: @args;
28 | -moz-transform: @args;
29 | -ms-transform: @args;
30 | -o-transform: @args;
31 | transform: @args;
32 | }
33 |
34 | .rotate(@deg) {
35 | .transform(rotate(@deg));
36 | }
37 |
38 | .scale(@factor) {
39 | .transform(scale(@factor));
40 | }
41 |
42 | .translate(@x, @y) {
43 | .transform(perspective(1px) translate(@x, @y));
44 | }
--------------------------------------------------------------------------------
/src/template/notification-icon.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/test/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "node": true, // Enable globals available when code is running inside of the NodeJS runtime environment.
3 | "browser": true, // Standard browser globals e.g. `window`, `document`.
4 | "eqeqeq": true, // Require triple equals i.e. `===`.
5 | "newcap": true, // Require capitalization of all constructor functions e.g. `new F()`.
6 | "noarg": true, // Prohibit use of `arguments.caller` and `arguments.callee`.
7 | "quotmark": "single", // Define quotes to string values.
8 | "regexp": true, // Prohibit `.` and `[^...]` in regular expressions.
9 | "undef": true, // Require all non-global variables be declared before they are used.
10 | "unused": true, // Warn unused variables.
11 | "strict": true, // Require `use strict` pragma in every file.
12 | "trailing": true, // Prohibit trailing whitespaces.
13 | "devel": true, // Allow development statements e.g. `console.log();`.
14 | "noempty": true, // Prohibit use of empty blocks.
15 | "expr": true, // Used for Chai
16 | "globals": { // Globals variables.
17 | "angular": false,
18 |
19 | /* MOCHA */
20 | "describe": false,
21 | "it": false,
22 | "before": false,
23 | "beforeEach": false,
24 | "after": false,
25 | "afterEach": false,
26 |
27 | /* KARMA */
28 | "inject": false,
29 | "sinon": false,
30 | "expect": false
31 | }
32 | }
--------------------------------------------------------------------------------
/src/test/angular-notification-icons.spec.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | describe('angular-notifications-icon', function() {
4 | beforeEach(module('angular-notification-icons', 'angular-notifications.tpls'));
5 |
6 | describe('NotificationDirectiveController', function() {
7 | var $controller;
8 | var scope;
9 | var $animate;
10 | var $q;
11 |
12 | var sandbox;
13 | var element;
14 | var angularElement;
15 |
16 | beforeEach(function() {
17 | sandbox = sinon.sandbox.create();
18 |
19 | element = { name: 'foo' };
20 | angularElement = { };
21 | });
22 |
23 | afterEach(function() {
24 | sandbox.restore();
25 | });
26 |
27 | beforeEach(inject(function(_$controller_, $rootScope, _$animate_, _$q_) {
28 | $controller = _$controller_;
29 | scope = {$scope: $rootScope.$new()};
30 | $animate = _$animate_;
31 | $q = _$q_;
32 | }));
33 |
34 | var spyOn = function(object, functionName) {
35 | if (!object[functionName]) {
36 | var spy = sandbox.spy();
37 | object[functionName] = spy;
38 | return spy;
39 | }
40 |
41 | return sandbox.spy(object, functionName);
42 | };
43 |
44 | it('should start with reasonable defaults', function() {
45 | var ctrl = $controller('NotificationDirectiveController', scope);
46 | ctrl.visible.should.be.false;
47 | ctrl.wideThreshold.should.eql(100);
48 | });
49 |
50 | it('should initialize with an element', function() {
51 | var jqElement = [angularElement];
52 | angularElement.querySelector = function() { };
53 | var selectorSpy = spyOn(angularElement, 'querySelector');
54 |
55 | var ctrl = $controller('NotificationDirectiveController', scope);
56 | ctrl.init(jqElement);
57 | selectorSpy.firstCall.args.should.eql(['.angular-notifications-icon']);
58 | });
59 |
60 | it('should set a clear trigger if specified', function() {
61 | var clearTriggerFn;
62 | angularElement.on = {};
63 | sandbox.stub(angularElement, 'on', function(trigger, callback) {
64 | trigger.should.eql('mouseover');
65 | clearTriggerFn = callback;
66 | });
67 |
68 | var deferred = $q.defer();
69 | spyOn(element, 'removeClass');
70 | sandbox.stub($animate, 'addClass').returns(deferred.promise);
71 |
72 | var ctrl = $controller('NotificationDirectiveController', scope, {clearTrigger: 'mouseover'});
73 | sandbox.stub(ctrl, 'getElement').returns(element);
74 | ctrl.init(angularElement);
75 |
76 | ctrl.count = 10;
77 | clearTriggerFn();
78 | ctrl.count.should.eql(0);
79 | });
80 |
81 | it('should appear if not visible and count goes above 0', function() {
82 | var deferred = $q.defer();
83 | var removeClassSpy = spyOn(element, 'removeClass');
84 | var addClassStub = sandbox.stub($animate, 'addClass').returns(deferred.promise);
85 |
86 | var ctrl = $controller('NotificationDirectiveController', scope);
87 | sandbox.stub(ctrl, 'getElement').returns(element);
88 | ctrl.init(angularElement);
89 | scope.$scope.self = ctrl;
90 |
91 | ctrl.count = 1;
92 | scope.$scope.$digest();
93 |
94 | // Element should now be visible
95 | ctrl.visible.should.eql(true);
96 | addClassStub.should.have.been.calledWith(element, 'grow'); // Default animation was played
97 |
98 | // Class was removed after animation finished
99 | deferred.resolve();
100 | scope.$scope.$apply();
101 | removeClassSpy.secondCall.args.should.eql(['grow']);
102 | });
103 |
104 | it('should play appear animation if set', function() {
105 | var deferred = $q.defer();
106 | var removeClassSpy = spyOn(element, 'removeClass');
107 | var addClassStub = sandbox.stub($animate, 'addClass').returns(deferred.promise);
108 |
109 | var ctrl = $controller('NotificationDirectiveController', scope, {appearAnimation: 'some-animation'});
110 | sandbox.stub(ctrl, 'getElement').returns(element);
111 | ctrl.init(angularElement);
112 | scope.$scope.self = ctrl;
113 |
114 | ctrl.count = 1;
115 | scope.$scope.$digest();
116 |
117 | // Element should now be visible
118 | ctrl.visible.should.eql(true);
119 | addClassStub.should.have.been.calledWith(element, 'some-animation');
120 |
121 | // Class was removed after animation finished
122 | deferred.resolve();
123 | scope.$scope.$apply();
124 | removeClassSpy.secondCall.args.should.eql(['some-animation']);
125 | });
126 |
127 | it('should update if visible and the count changes', function() {
128 | var deferred = $q.defer();
129 | var removeClassSpy = spyOn(element, 'removeClass');
130 | var addClassStub = sandbox.stub($animate, 'addClass').returns(deferred.promise);
131 |
132 | var ctrl = $controller('NotificationDirectiveController', scope);
133 | sandbox.stub(ctrl, 'getElement').returns(element);
134 | ctrl.init(angularElement);
135 | scope.$scope.self = ctrl;
136 |
137 | ctrl.count = 1;
138 | ctrl.visible = true;
139 | scope.$scope.$digest();
140 |
141 | addClassStub.should.have.been.calledWith(element, 'grow'); // Default animation was played
142 |
143 | // Class was removed after animation finished
144 | deferred.resolve();
145 | scope.$scope.$apply();
146 | removeClassSpy.secondCall.args.should.eql(['grow']);
147 | });
148 |
149 | it('should play update animation if set', function() {
150 | var deferred = $q.defer();
151 | var removeClassSpy = spyOn(element, 'removeClass');
152 | var addClassStub = sandbox.stub($animate, 'addClass').returns(deferred.promise);
153 |
154 | var ctrl = $controller('NotificationDirectiveController', scope, {updateAnimation: 'some-animation'});
155 | sandbox.stub(ctrl, 'getElement').returns(element);
156 | ctrl.init(angularElement);
157 | scope.$scope.self = ctrl;
158 |
159 | ctrl.count = 1;
160 | ctrl.visible = true;
161 | scope.$scope.$digest();
162 |
163 | addClassStub.should.have.been.calledWith(element, 'some-animation');
164 |
165 | // Class was removed after animation finished
166 | deferred.resolve();
167 | scope.$scope.$apply();
168 | removeClassSpy.secondCall.args.should.eql(['some-animation']);
169 | });
170 |
171 | it('should set the appear animation based on the \'animation\' property', function() {
172 | var deferred = $q.defer();
173 | var removeClassSpy = spyOn(element, 'removeClass');
174 | var addClassStub = sandbox.stub($animate, 'addClass').returns(deferred.promise);
175 |
176 | var ctrl = $controller('NotificationDirectiveController', scope, {animation: 'some-animation'});
177 | sandbox.stub(ctrl, 'getElement').returns(element);
178 | ctrl.init(angularElement);
179 | scope.$scope.self = ctrl;
180 |
181 | ctrl.count = 1;
182 | scope.$scope.$digest();
183 |
184 | // Element should now be visible
185 | ctrl.visible.should.eql(true);
186 | addClassStub.should.have.been.calledWith(element, 'some-animation');
187 |
188 | // Class was removed after animation finished
189 | deferred.resolve();
190 | scope.$scope.$apply();
191 | removeClassSpy.secondCall.args.should.eql(['some-animation']);
192 | });
193 |
194 | it('should set the update animation based on the \'animation\' property', function() {
195 | var deferred = $q.defer();
196 | var removeClassSpy = spyOn(element, 'removeClass');
197 | var addClassStub = sandbox.stub($animate, 'addClass').returns(deferred.promise);
198 |
199 | var ctrl = $controller('NotificationDirectiveController', scope, {animation: 'some-animation'});
200 | sandbox.stub(ctrl, 'getElement').returns(element);
201 | ctrl.init(angularElement);
202 | scope.$scope.self = ctrl;
203 |
204 | ctrl.count = 1;
205 | ctrl.visible = true;
206 | scope.$scope.$digest();
207 |
208 | addClassStub.should.have.been.calledWith(element, 'some-animation');
209 |
210 | // Class was removed after animation finished
211 | deferred.resolve();
212 | scope.$scope.$apply();
213 | removeClassSpy.secondCall.args.should.eql(['some-animation']);
214 | });
215 |
216 | it('should disappear if visible and the count goes to or below 0', function() {
217 | var ctrl = $controller('NotificationDirectiveController', scope, {});
218 | spyOn(element, 'removeClass');
219 | sandbox.stub(ctrl, 'getElement').returns(element);
220 | ctrl.init(angularElement);
221 | scope.$scope.self = ctrl;
222 |
223 | ctrl.count = 0;
224 | ctrl.visible = true;
225 | scope.$scope.$digest();
226 |
227 | ctrl.visible.should.eql(false);
228 | });
229 |
230 | it('should play disappear animation if set', function() {
231 | var deferred = $q.defer();
232 | var removeClassSpy = spyOn(element, 'removeClass');
233 | var addClassStub = sandbox.stub($animate, 'addClass').returns(deferred.promise);
234 |
235 | var ctrl = $controller('NotificationDirectiveController', scope, {disappearAnimation: 'some-animation'});
236 | sandbox.stub(ctrl, 'getElement').returns(element);
237 | ctrl.init(angularElement);
238 | scope.$scope.self = ctrl;
239 |
240 | ctrl.count = 0;
241 | ctrl.visible = true;
242 | scope.$scope.$digest();
243 |
244 | addClassStub.should.have.been.calledWith(element, 'some-animation');
245 |
246 | // Class was removed after animation finished
247 | deferred.resolve();
248 | scope.$scope.$apply();
249 | removeClassSpy.secondCall.args.should.eql(['some-animation']);
250 | });
251 |
252 | it('should interrupt an animation that is already playing', function() {
253 | var deferred = $q.defer();
254 | spyOn(element, 'removeClass');
255 | sandbox.stub($animate, 'addClass').returns(deferred.promise);
256 |
257 | var cancelStub = sandbox.stub($animate, 'cancel');
258 |
259 | var ctrl = $controller('NotificationDirectiveController', scope);
260 | sandbox.stub(ctrl, 'getElement').returns(element);
261 | ctrl.init(angularElement);
262 | scope.$scope.self = ctrl;
263 |
264 | ctrl.count = 1;
265 | scope.$scope.$digest();
266 |
267 | ctrl.count = 2;
268 | scope.$scope.$digest();
269 |
270 | cancelStub.should.have.callCount(1);
271 | });
272 |
273 | it('should add .wide-icon if the count is higher than the wide threshold', function() {
274 | var deferred = $q.defer();
275 | var addClassSpy = spyOn(element, 'addClass');
276 | sandbox.stub($animate, 'addClass').returns(deferred.promise);
277 |
278 | var ctrl = $controller('NotificationDirectiveController', scope, {wideThreshold: 10});
279 | sandbox.stub(ctrl, 'getElement').returns(element);
280 | ctrl.init(angularElement);
281 | scope.$scope.self = ctrl;
282 |
283 | ctrl.count = 11;
284 | scope.$scope.$digest();
285 |
286 | addClassSpy.should.have.been.calledWith('wide-icon');
287 | });
288 |
289 | it('should remove .wide-icon if the count is lower than the wide threshold', function() {
290 | var deferred = $q.defer();
291 | var removeClassSpy = spyOn(element, 'removeClass');
292 | sandbox.stub($animate, 'addClass').returns(deferred.promise);
293 |
294 | var ctrl = $controller('NotificationDirectiveController', scope, {wideThreshold: 10});
295 | sandbox.stub(ctrl, 'getElement').returns(element);
296 | ctrl.init(angularElement);
297 | scope.$scope.self = ctrl;
298 |
299 | ctrl.count = 1;
300 | scope.$scope.$digest();
301 |
302 | removeClassSpy.firstCall.args.should.eql(['wide-icon']);
303 | });
304 | });
305 |
306 | describe('notificationIcon directive', function() {
307 | var $compile;
308 | var $scope;
309 |
310 | beforeEach(inject(function(_$compile_, $rootScope) {
311 | $compile = _$compile_;
312 | $scope = $rootScope.$new();
313 | }));
314 |
315 | it('should compile with a count', function() {
316 | var element = $compile('
')($scope);
317 | $scope.$digest();
318 |
319 | var ctrl = element.isolateScope().notification;
320 | ctrl.count.should.eql(10);
321 |
322 | element.find('.inner-test-dev').should.exist;
323 | });
324 | });
325 | });
--------------------------------------------------------------------------------