angularjs-foundation-rails 0.3.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: e82de542ed9a238a426dbc68a03ea1fabbbf72bc
4
+ data.tar.gz: bf32f5a35a99e023acb30ce2ad6cffd76ce87124
5
+ SHA512:
6
+ metadata.gz: 44f9f9263b0c92e360e103e8c771800144b3dd948c582f63d02408b6f228e76214cd9900d37c3aeb4b6774fb2381ed46bb650326a69408c811ab9b007d2b2b5a
7
+ data.tar.gz: b196302b21cb2fba1b73dee00c8760fa48a55c62cdb38569fb9abef498bf8d46677e968881a8209f10830bcf5c674e68967309f7d62f6a03c3c5ab42be118084
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2014 Santiago Doldán
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.
data/README.md ADDED
@@ -0,0 +1,25 @@
1
+ # angularjs-foundation-rails
2
+
3
+ angularjs-foundation-rails wraps the [AngularJS Foundation directives](https://github.com/madmimi/angular-foundation) library for use in Rails 3.1 and above. Assets will minify automatically during production.
4
+
5
+ ## Usage
6
+
7
+ Add the following to your gemfile:
8
+
9
+ gem 'angularjs-foundation-rails'
10
+
11
+ Add the following directive to your Javascript manifest file (application.js):
12
+
13
+ //= require angular
14
+ //= require angular-mm-foundation
15
+
16
+ If you would like to use the default foundation templates, use the following directive instead
17
+
18
+ //= require angular
19
+ //= require angular-mm-foundation-tpls
20
+
21
+ You may need to add 'mm-foundation' into your app declaration for example
22
+
23
+ app = angular.module('MyApp', ['mm.foundation'])
24
+
25
+ Gem based on angular-ui-bootstrap-rails (https://github.com/cconstantin/angular-ui-bootstrap-rails) by Chris Constantin
@@ -0,0 +1,10 @@
1
+ require 'angularjs-foundation-rails/version'
2
+
3
+ module Angularjs
4
+ module Foundation
5
+ module Rails
6
+ class Engine < ::Rails::Engine
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,7 @@
1
+ module Angularjs
2
+ module Foundation
3
+ module Rails
4
+ VERSION = '0.3.1.2'
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,3499 @@
1
+ /*
2
+ * angular-mm-foundation
3
+ * http://pineconellc.github.io/angular-foundation/
4
+
5
+ * Version: 0.3.1 - 2014-08-19
6
+ * License: MIT
7
+ * (c) Pinecone, LLC
8
+ */
9
+ angular.module("mm.foundation", ["mm.foundation.tpls", "mm.foundation.accordion","mm.foundation.alert","mm.foundation.bindHtml","mm.foundation.buttons","mm.foundation.position","mm.foundation.dropdownToggle","mm.foundation.interchange","mm.foundation.transition","mm.foundation.modal","mm.foundation.offcanvas","mm.foundation.pagination","mm.foundation.tooltip","mm.foundation.popover","mm.foundation.progressbar","mm.foundation.rating","mm.foundation.tabs","mm.foundation.topbar","mm.foundation.tour","mm.foundation.typeahead"]);
10
+ angular.module("mm.foundation.tpls", ["template/accordion/accordion-group.html","template/accordion/accordion.html","template/alert/alert.html","template/modal/backdrop.html","template/modal/window.html","template/pagination/pager.html","template/pagination/pagination.html","template/tooltip/tooltip-html-unsafe-popup.html","template/tooltip/tooltip-popup.html","template/popover/popover.html","template/progressbar/bar.html","template/progressbar/progress.html","template/progressbar/progressbar.html","template/rating/rating.html","template/tabs/tab.html","template/tabs/tabset.html","template/topbar/has-dropdown.html","template/topbar/toggle-top-bar.html","template/topbar/top-bar-dropdown.html","template/topbar/top-bar-section.html","template/topbar/top-bar.html","template/tour/tour.html","template/typeahead/typeahead-match.html","template/typeahead/typeahead-popup.html"]);
11
+ angular.module('mm.foundation.accordion', [])
12
+
13
+ .constant('accordionConfig', {
14
+ closeOthers: true
15
+ })
16
+
17
+ .controller('AccordionController', ['$scope', '$attrs', 'accordionConfig', function ($scope, $attrs, accordionConfig) {
18
+
19
+ // This array keeps track of the accordion groups
20
+ this.groups = [];
21
+
22
+ // Ensure that all the groups in this accordion are closed, unless close-others explicitly says not to
23
+ this.closeOthers = function(openGroup) {
24
+ var closeOthers = angular.isDefined($attrs.closeOthers) ? $scope.$eval($attrs.closeOthers) : accordionConfig.closeOthers;
25
+ if ( closeOthers ) {
26
+ angular.forEach(this.groups, function (group) {
27
+ if ( group !== openGroup ) {
28
+ group.isOpen = false;
29
+ }
30
+ });
31
+ }
32
+ };
33
+
34
+ // This is called from the accordion-group directive to add itself to the accordion
35
+ this.addGroup = function(groupScope) {
36
+ var that = this;
37
+ this.groups.push(groupScope);
38
+
39
+ groupScope.$on('$destroy', function (event) {
40
+ that.removeGroup(groupScope);
41
+ });
42
+ };
43
+
44
+ // This is called from the accordion-group directive when to remove itself
45
+ this.removeGroup = function(group) {
46
+ var index = this.groups.indexOf(group);
47
+ if ( index !== -1 ) {
48
+ this.groups.splice(this.groups.indexOf(group), 1);
49
+ }
50
+ };
51
+
52
+ }])
53
+
54
+ // The accordion directive simply sets up the directive controller
55
+ // and adds an accordion CSS class to itself element.
56
+ .directive('accordion', function () {
57
+ return {
58
+ restrict:'EA',
59
+ controller:'AccordionController',
60
+ transclude: true,
61
+ replace: false,
62
+ templateUrl: 'template/accordion/accordion.html'
63
+ };
64
+ })
65
+
66
+ // The accordion-group directive indicates a block of html that will expand and collapse in an accordion
67
+ .directive('accordionGroup', ['$parse', function($parse) {
68
+ return {
69
+ require:'^accordion', // We need this directive to be inside an accordion
70
+ restrict:'EA',
71
+ transclude:true, // It transcludes the contents of the directive into the template
72
+ replace: true, // The element containing the directive will be replaced with the template
73
+ templateUrl:'template/accordion/accordion-group.html',
74
+ scope:{ heading:'@' }, // Create an isolated scope and interpolate the heading attribute onto this scope
75
+ controller: function() {
76
+ this.setHeading = function(element) {
77
+ this.heading = element;
78
+ };
79
+ },
80
+ link: function(scope, element, attrs, accordionCtrl) {
81
+ var getIsOpen, setIsOpen;
82
+
83
+ accordionCtrl.addGroup(scope);
84
+
85
+ scope.isOpen = false;
86
+
87
+ if ( attrs.isOpen ) {
88
+ getIsOpen = $parse(attrs.isOpen);
89
+ setIsOpen = getIsOpen.assign;
90
+
91
+ scope.$parent.$watch(getIsOpen, function(value) {
92
+ scope.isOpen = !!value;
93
+ });
94
+ }
95
+
96
+ scope.$watch('isOpen', function(value) {
97
+ if ( value ) {
98
+ accordionCtrl.closeOthers(scope);
99
+ }
100
+ if ( setIsOpen ) {
101
+ setIsOpen(scope.$parent, value);
102
+ }
103
+ });
104
+ }
105
+ };
106
+ }])
107
+
108
+ // Use accordion-heading below an accordion-group to provide a heading containing HTML
109
+ // <accordion-group>
110
+ // <accordion-heading>Heading containing HTML - <img src="..."></accordion-heading>
111
+ // </accordion-group>
112
+ .directive('accordionHeading', function() {
113
+ return {
114
+ restrict: 'EA',
115
+ transclude: true, // Grab the contents to be used as the heading
116
+ template: '', // In effect remove this element!
117
+ replace: true,
118
+ require: '^accordionGroup',
119
+ compile: function(element, attr, transclude) {
120
+ return function link(scope, element, attr, accordionGroupCtrl) {
121
+ // Pass the heading to the accordion-group controller
122
+ // so that it can be transcluded into the right place in the template
123
+ // [The second parameter to transclude causes the elements to be cloned so that they work in ng-repeat]
124
+ accordionGroupCtrl.setHeading(transclude(scope, function() {}));
125
+ };
126
+ }
127
+ };
128
+ })
129
+
130
+ // Use in the accordion-group template to indicate where you want the heading to be transcluded
131
+ // You must provide the property on the accordion-group controller that will hold the transcluded element
132
+ // <div class="accordion-group">
133
+ // <div class="accordion-heading" ><a ... accordion-transclude="heading">...</a></div>
134
+ // ...
135
+ // </div>
136
+ .directive('accordionTransclude', function() {
137
+ return {
138
+ require: '^accordionGroup',
139
+ link: function(scope, element, attr, controller) {
140
+ scope.$watch(function() { return controller[attr.accordionTransclude]; }, function(heading) {
141
+ if ( heading ) {
142
+ element.html('');
143
+ element.append(heading);
144
+ }
145
+ });
146
+ }
147
+ };
148
+ });
149
+
150
+ angular.module("mm.foundation.alert", [])
151
+
152
+ .controller('AlertController', ['$scope', '$attrs', function ($scope, $attrs) {
153
+ $scope.closeable = 'close' in $attrs;
154
+ }])
155
+
156
+ .directive('alert', function () {
157
+ return {
158
+ restrict:'EA',
159
+ controller:'AlertController',
160
+ templateUrl:'template/alert/alert.html',
161
+ transclude:true,
162
+ replace:true,
163
+ scope: {
164
+ type: '=',
165
+ close: '&'
166
+ }
167
+ };
168
+ });
169
+
170
+ angular.module('mm.foundation.bindHtml', [])
171
+
172
+ .directive('bindHtmlUnsafe', function () {
173
+ return function (scope, element, attr) {
174
+ element.addClass('ng-binding').data('$binding', attr.bindHtmlUnsafe);
175
+ scope.$watch(attr.bindHtmlUnsafe, function bindHtmlUnsafeWatchAction(value) {
176
+ element.html(value || '');
177
+ });
178
+ };
179
+ });
180
+
181
+ angular.module('mm.foundation.buttons', [])
182
+
183
+ .constant('buttonConfig', {
184
+ activeClass: 'active',
185
+ toggleEvent: 'click'
186
+ })
187
+
188
+ .controller('ButtonsController', ['buttonConfig', function(buttonConfig) {
189
+ this.activeClass = buttonConfig.activeClass;
190
+ this.toggleEvent = buttonConfig.toggleEvent;
191
+ }])
192
+
193
+ .directive('btnRadio', function () {
194
+ return {
195
+ require: ['btnRadio', 'ngModel'],
196
+ controller: 'ButtonsController',
197
+ link: function (scope, element, attrs, ctrls) {
198
+ var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1];
199
+
200
+ //model -> UI
201
+ ngModelCtrl.$render = function () {
202
+ element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, scope.$eval(attrs.btnRadio)));
203
+ };
204
+
205
+ //ui->model
206
+ element.bind(buttonsCtrl.toggleEvent, function () {
207
+ if (!element.hasClass(buttonsCtrl.activeClass)) {
208
+ scope.$apply(function () {
209
+ ngModelCtrl.$setViewValue(scope.$eval(attrs.btnRadio));
210
+ ngModelCtrl.$render();
211
+ });
212
+ }
213
+ });
214
+ }
215
+ };
216
+ })
217
+
218
+ .directive('btnCheckbox', function () {
219
+ return {
220
+ require: ['btnCheckbox', 'ngModel'],
221
+ controller: 'ButtonsController',
222
+ link: function (scope, element, attrs, ctrls) {
223
+ var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1];
224
+
225
+ function getTrueValue() {
226
+ return getCheckboxValue(attrs.btnCheckboxTrue, true);
227
+ }
228
+
229
+ function getFalseValue() {
230
+ return getCheckboxValue(attrs.btnCheckboxFalse, false);
231
+ }
232
+
233
+ function getCheckboxValue(attributeValue, defaultValue) {
234
+ var val = scope.$eval(attributeValue);
235
+ return angular.isDefined(val) ? val : defaultValue;
236
+ }
237
+
238
+ //model -> UI
239
+ ngModelCtrl.$render = function () {
240
+ element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, getTrueValue()));
241
+ };
242
+
243
+ //ui->model
244
+ element.bind(buttonsCtrl.toggleEvent, function () {
245
+ scope.$apply(function () {
246
+ ngModelCtrl.$setViewValue(element.hasClass(buttonsCtrl.activeClass) ? getFalseValue() : getTrueValue());
247
+ ngModelCtrl.$render();
248
+ });
249
+ });
250
+ }
251
+ };
252
+ });
253
+
254
+ angular.module('mm.foundation.position', [])
255
+
256
+ /**
257
+ * A set of utility methods that can be use to retrieve position of DOM elements.
258
+ * It is meant to be used where we need to absolute-position DOM elements in
259
+ * relation to other, existing elements (this is the case for tooltips, popovers,
260
+ * typeahead suggestions etc.).
261
+ */
262
+ .factory('$position', ['$document', '$window', function ($document, $window) {
263
+
264
+ function getStyle(el, cssprop) {
265
+ if (el.currentStyle) { //IE
266
+ return el.currentStyle[cssprop];
267
+ } else if ($window.getComputedStyle) {
268
+ return $window.getComputedStyle(el)[cssprop];
269
+ }
270
+ // finally try and get inline style
271
+ return el.style[cssprop];
272
+ }
273
+
274
+ /**
275
+ * Checks if a given element is statically positioned
276
+ * @param element - raw DOM element
277
+ */
278
+ function isStaticPositioned(element) {
279
+ return (getStyle(element, "position") || 'static' ) === 'static';
280
+ }
281
+
282
+ /**
283
+ * returns the closest, non-statically positioned parentOffset of a given element
284
+ * @param element
285
+ */
286
+ var parentOffsetEl = function (element) {
287
+ var docDomEl = $document[0];
288
+ var offsetParent = element.offsetParent || docDomEl;
289
+ while (offsetParent && offsetParent !== docDomEl && isStaticPositioned(offsetParent) ) {
290
+ offsetParent = offsetParent.offsetParent;
291
+ }
292
+ return offsetParent || docDomEl;
293
+ };
294
+
295
+ return {
296
+ /**
297
+ * Provides read-only equivalent of jQuery's position function:
298
+ * http://api.jquery.com/position/
299
+ */
300
+ position: function (element) {
301
+ var elBCR = this.offset(element);
302
+ var offsetParentBCR = { top: 0, left: 0 };
303
+ var offsetParentEl = parentOffsetEl(element[0]);
304
+ if (offsetParentEl != $document[0]) {
305
+ offsetParentBCR = this.offset(angular.element(offsetParentEl));
306
+ offsetParentBCR.top += offsetParentEl.clientTop - offsetParentEl.scrollTop;
307
+ offsetParentBCR.left += offsetParentEl.clientLeft - offsetParentEl.scrollLeft;
308
+ }
309
+
310
+ var boundingClientRect = element[0].getBoundingClientRect();
311
+ return {
312
+ width: boundingClientRect.width || element.prop('offsetWidth'),
313
+ height: boundingClientRect.height || element.prop('offsetHeight'),
314
+ top: elBCR.top - offsetParentBCR.top,
315
+ left: elBCR.left - offsetParentBCR.left
316
+ };
317
+ },
318
+
319
+ /**
320
+ * Provides read-only equivalent of jQuery's offset function:
321
+ * http://api.jquery.com/offset/
322
+ */
323
+ offset: function (element) {
324
+ var boundingClientRect = element[0].getBoundingClientRect();
325
+ return {
326
+ width: boundingClientRect.width || element.prop('offsetWidth'),
327
+ height: boundingClientRect.height || element.prop('offsetHeight'),
328
+ top: boundingClientRect.top + ($window.pageYOffset || $document[0].body.scrollTop || $document[0].documentElement.scrollTop),
329
+ left: boundingClientRect.left + ($window.pageXOffset || $document[0].body.scrollLeft || $document[0].documentElement.scrollLeft)
330
+ };
331
+ }
332
+ };
333
+ }]);
334
+
335
+ /*
336
+ * dropdownToggle - Provides dropdown menu functionality
337
+ * @restrict class or attribute
338
+ * @example:
339
+
340
+ <a dropdown-toggle="#dropdown-menu">My Dropdown Menu</a>
341
+ <ul id="dropdown-menu" class="f-dropdown">
342
+ <li ng-repeat="choice in dropChoices">
343
+ <a ng-href="{{choice.href}}">{{choice.text}}</a>
344
+ </li>
345
+ </ul>
346
+ */
347
+ angular.module('mm.foundation.dropdownToggle', [ 'mm.foundation.position' ])
348
+
349
+ .directive('dropdownToggle', ['$document', '$location', '$position', function ($document, $location, $position) {
350
+ var openElement = null,
351
+ closeMenu = angular.noop;
352
+ return {
353
+ restrict: 'CA',
354
+ scope: {
355
+ dropdownToggle: '@'
356
+ },
357
+ link: function(scope, element, attrs) {
358
+ var dropdown = angular.element($document[0].querySelector(scope.dropdownToggle));
359
+
360
+ scope.$watch('$location.path', function() { closeMenu(); });
361
+ element.bind('click', function (event) {
362
+ dropdown = angular.element($document[0].querySelector(scope.dropdownToggle));
363
+ var elementWasOpen = (element === openElement);
364
+
365
+ event.preventDefault();
366
+ event.stopPropagation();
367
+
368
+ if (!!openElement) {
369
+ closeMenu();
370
+ }
371
+
372
+ if (!elementWasOpen && !element.hasClass('disabled') && !element.prop('disabled')) {
373
+ dropdown.css('display', 'block');
374
+
375
+ var offset = $position.offset(element);
376
+ var parentOffset = $position.offset(angular.element(dropdown[0].offsetParent));
377
+
378
+ dropdown.css({
379
+ left: offset.left - parentOffset.left + 'px',
380
+ top: offset.top - parentOffset.top + offset.height + 'px'
381
+ });
382
+
383
+ openElement = element;
384
+ closeMenu = function (event) {
385
+ $document.unbind('click', closeMenu);
386
+ dropdown.css('display', 'none');
387
+ closeMenu = angular.noop;
388
+ openElement = null;
389
+ };
390
+ $document.bind('click', closeMenu);
391
+ }
392
+ });
393
+
394
+ if (dropdown) {
395
+ dropdown.css('display', 'none');
396
+ }
397
+ }
398
+ };
399
+ }]);
400
+
401
+ /**
402
+ * @ngdoc service
403
+ * @name mm.foundation.interchange
404
+ * @description
405
+ *
406
+ * Package containing all services and directives
407
+ * about the `interchange` module
408
+ */
409
+ angular.module('mm.foundation.interchange', [])
410
+
411
+ /**
412
+ * @ngdoc function
413
+ * @name mm.foundation.interchange.interchageQuery
414
+ * @function interchageQuery
415
+ * @description
416
+ *
417
+ * this service inject meta tags objects in the head
418
+ * to get the list of media queries from Foundation
419
+ * stylesheets.
420
+ *
421
+ * @return {object} Queries list name => mediaQuery
422
+ */
423
+ .factory('interchangeQueries', ['$document', function ($document) {
424
+ var element,
425
+ mediaSize,
426
+ formatList = {
427
+ 'default': 'only screen',
428
+ landscape : 'only screen and (orientation: landscape)',
429
+ portrait : 'only screen and (orientation: portrait)',
430
+ retina : 'only screen and (-webkit-min-device-pixel-ratio: 2),' +
431
+ 'only screen and (min--moz-device-pixel-ratio: 2),' +
432
+ 'only screen and (-o-min-device-pixel-ratio: 2/1),' +
433
+ 'only screen and (min-device-pixel-ratio: 2),' +
434
+ 'only screen and (min-resolution: 192dpi),' +
435
+ 'only screen and (min-resolution: 2dppx)'
436
+ },
437
+ classPrefix = 'foundation-mq-',
438
+ classList = ['small', 'medium', 'large', 'xlarge', 'xxlarge'],
439
+ head = angular.element($document[0].querySelector('head'));
440
+
441
+ for (var i = 0; i < classList.length; i++) {
442
+ head.append('<meta class="' + classPrefix + classList[i] + '" />');
443
+ element = getComputedStyle(head[0].querySelector('meta.' + classPrefix + classList[i]));
444
+ mediaSize = element.fontFamily.replace(/^[\/\\'"]+|(;\s?})+|[\/\\'"]+$/g, '');
445
+ formatList[classList[i]] = mediaSize;
446
+ }
447
+ return formatList;
448
+ }])
449
+
450
+ /**
451
+ * @ngdoc function
452
+ * @name mm.foundation.interchange.interchangeQueriesManager
453
+ * @function interchangeQueriesManager
454
+ * @description
455
+ *
456
+ * interface to add and remove named queries
457
+ * in the interchangeQueries list
458
+ */
459
+ .factory('interchangeQueriesManager', ['interchangeQueries', function (interchangeQueries) {
460
+ return {
461
+ /**
462
+ * @ngdoc method
463
+ * @name interchangeQueriesManager#add
464
+ * @methodOf mm.foundation.interchange.interchangeQueriesManager
465
+ * @description
466
+ *
467
+ * Add a custom media query in the `interchangeQueries`
468
+ * factory. This method does not allow to update an existing
469
+ * media query.
470
+ *
471
+ * @param {string} name MediaQuery name
472
+ * @param {string} media MediaQuery
473
+ * @returns {boolean} True if the insert is a success
474
+ */
475
+ add: function (name, media) {
476
+ if (!name || !media ||
477
+ !angular.isString(name) || !angular.isString(media) ||
478
+ !!interchangeQueries[name]) {
479
+ return false;
480
+ }
481
+ interchangeQueries[name] = media;
482
+ return true;
483
+ }
484
+ };
485
+ }])
486
+
487
+ /**
488
+ * @ngdoc function
489
+ * @name mm.foundation.interchange.interchangeTools
490
+ * @function interchangeTools
491
+ * @description
492
+ *
493
+ * Tools to help with the `interchange` module.
494
+ */
495
+ .factory('interchangeTools', ['$window', 'interchangeQueries', function ($window, namedQueries) {
496
+
497
+ /**
498
+ * @ngdoc method
499
+ * @name interchangeTools#parseAttribute
500
+ * @methodOf mm.foundation.interchange.interchangeTools
501
+ * @description
502
+ *
503
+ * Attribute parser to transform an `interchange` attribute
504
+ * value to an object with media query (name or query) as key,
505
+ * and file to use as value.
506
+ *
507
+ * ```
508
+ * {
509
+ * small: 'bridge-500.jpg',
510
+ * large: 'bridge-1200.jpg'
511
+ * }
512
+ * ```
513
+ *
514
+ * @param {string} value Interchange query string
515
+ * @returns {object} Attribute parsed
516
+ */
517
+ var parseAttribute = function (value) {
518
+ var raw = value.split(/\[(.*?)\]/),
519
+ i = raw.length,
520
+ breaker = /^(.+)\,\ \((.+)\)$/,
521
+ breaked,
522
+ output = {};
523
+
524
+ while (i--) {
525
+ if (raw[i].replace(/[\W\d]+/, '').length > 4) {
526
+ breaked = breaker.exec(raw[i]);
527
+ if (!!breaked && breaked.length === 3) {
528
+ output[breaked[2]] = breaked[1];
529
+ }
530
+ }
531
+ }
532
+ return output;
533
+ };
534
+
535
+ /**
536
+ * @ngdoc method
537
+ * @name interchangeTools#findCurrentMediaFile
538
+ * @methodOf mm.foundation.interchange.interchangeTools
539
+ * @description
540
+ *
541
+ * Find the current item to display from a file list
542
+ * (object returned by `parseAttribute`) and the
543
+ * current page dimensions.
544
+ *
545
+ * ```
546
+ * {
547
+ * small: 'bridge-500.jpg',
548
+ * large: 'bridge-1200.jpg'
549
+ * }
550
+ * ```
551
+ *
552
+ * @param {object} files Parsed version of `interchange` attribute
553
+ * @returns {string} File to display (or `undefined`)
554
+ */
555
+ var findCurrentMediaFile = function (files) {
556
+ var file, media, match;
557
+ for (file in files) {
558
+ media = namedQueries[file] || file;
559
+ match = $window.matchMedia(media);
560
+ if (match.matches) {
561
+ return files[file];
562
+ }
563
+ }
564
+ return;
565
+ };
566
+
567
+ return {
568
+ parseAttribute: parseAttribute,
569
+ findCurrentMediaFile: findCurrentMediaFile
570
+ };
571
+ }])
572
+
573
+ /**
574
+ * @ngdoc directive
575
+ * @name mm.foundation.interchange.directive:interchange
576
+ * @restrict A
577
+ * @element DIV|IMG
578
+ * @priority 450
579
+ * @scope true
580
+ * @description
581
+ *
582
+ * Interchange directive, following the same features as
583
+ * ZURB documentation. The directive is splitted in 3 parts.
584
+ *
585
+ * 1. This directive use `compile` and not `link` for a simple
586
+ * reason: if the method is applied on a DIV element to
587
+ * display a template, the compile method will inject an ng-include.
588
+ * Because using a `templateUrl` or `template` to do it wouldn't
589
+ * be appropriate for all cases (`IMG` or dynamic backgrounds).
590
+ * And doing it in `link` is too late to be applied.
591
+ *
592
+ * 2. In the `compile:post`, the attribute is parsed to find
593
+ * out the type of content to display.
594
+ *
595
+ * 3. At the start and on event `resize`, the method `replace`
596
+ * is called to reevaluate which file is supposed to be displayed
597
+ * and update the value if necessary. The methd will also
598
+ * trigger a `replace` event.
599
+ */
600
+ .directive('interchange', ['$window', '$rootScope', 'interchangeTools', function ($window, $rootScope, interchangeTools) {
601
+
602
+ var pictureFilePattern = /[A-Za-z0-9-_]+\.(jpg|jpeg|png|gif|bmp|tiff)\ *,/i;
603
+
604
+ return {
605
+ restrict: 'A',
606
+ scope: true,
607
+ priority: 450,
608
+ compile: function compile($element, attrs) {
609
+ // Set up the attribute to update
610
+ if ($element[0].nodeName === 'DIV' && !pictureFilePattern.test(attrs.interchange)) {
611
+ $element.html('<ng-include src="currentFile"></ng-include>');
612
+ }
613
+
614
+ return {
615
+ pre: function preLink($scope, $element, attrs) {},
616
+ post: function postLink($scope, $element, attrs) {
617
+ var currentFile, nodeName;
618
+
619
+ // Set up the attribute to update
620
+ nodeName = $element && $element[0] && $element[0].nodeName;
621
+ $scope.fileMap = interchangeTools.parseAttribute(attrs.interchange);
622
+
623
+ // Find the type of interchange
624
+ switch (nodeName) {
625
+ case 'DIV':
626
+ // If the tag is a div, we test the current file to see if it's picture
627
+ currentFile = interchangeTools.findCurrentMediaFile($scope.fileMap);
628
+ if (/[A-Za-z0-9-_]+\.(jpg|jpeg|png|gif|bmp|tiff)$/i.test(currentFile)) {
629
+ $scope.type = 'background';
630
+ }
631
+ else {
632
+ $scope.type = 'include';
633
+ }
634
+ break;
635
+
636
+ case 'IMG':
637
+ $scope.type = 'image';
638
+ break;
639
+
640
+ default:
641
+ return;
642
+ }
643
+
644
+ var replace = function (e) {
645
+ // The the new file to display (exit if the same)
646
+ var currentFile = interchangeTools.findCurrentMediaFile($scope.fileMap);
647
+ if (!!$scope.currentFile && $scope.currentFile === currentFile) {
648
+ return;
649
+ }
650
+
651
+ // Set up the new file
652
+ $scope.currentFile = currentFile;
653
+ switch ($scope.type) {
654
+ case 'image':
655
+ $element.attr('src', $scope.currentFile);
656
+ break;
657
+
658
+ case 'background':
659
+ $element.css('background-image', 'url(' + $scope.currentFile + ')');
660
+ break;
661
+ }
662
+
663
+ // Trigger events
664
+ $rootScope.$emit('replace', $element, $scope);
665
+ if (!!e) {
666
+ $scope.$apply();
667
+ }
668
+ };
669
+
670
+ // Start
671
+ replace();
672
+ $window.addEventListener('resize', replace);
673
+ $scope.$on('$destroy', function () {
674
+ $window.removeEventListener('resize', replace);
675
+ });
676
+ }
677
+ };
678
+ }
679
+ };
680
+ }]);
681
+
682
+ angular.module('mm.foundation.transition', [])
683
+
684
+ /**
685
+ * $transition service provides a consistent interface to trigger CSS 3 transitions and to be informed when they complete.
686
+ * @param {DOMElement} element The DOMElement that will be animated.
687
+ * @param {string|object|function} trigger The thing that will cause the transition to start:
688
+ * - As a string, it represents the css class to be added to the element.
689
+ * - As an object, it represents a hash of style attributes to be applied to the element.
690
+ * - As a function, it represents a function to be called that will cause the transition to occur.
691
+ * @return {Promise} A promise that is resolved when the transition finishes.
692
+ */
693
+ .factory('$transition', ['$q', '$timeout', '$rootScope', function($q, $timeout, $rootScope) {
694
+
695
+ var $transition = function(element, trigger, options) {
696
+ options = options || {};
697
+ var deferred = $q.defer();
698
+ var endEventName = $transition[options.animation ? "animationEndEventName" : "transitionEndEventName"];
699
+
700
+ var transitionEndHandler = function(event) {
701
+ $rootScope.$apply(function() {
702
+ element.unbind(endEventName, transitionEndHandler);
703
+ deferred.resolve(element);
704
+ });
705
+ };
706
+
707
+ if (endEventName) {
708
+ element.bind(endEventName, transitionEndHandler);
709
+ }
710
+
711
+ // Wrap in a timeout to allow the browser time to update the DOM before the transition is to occur
712
+ $timeout(function() {
713
+ if ( angular.isString(trigger) ) {
714
+ element.addClass(trigger);
715
+ } else if ( angular.isFunction(trigger) ) {
716
+ trigger(element);
717
+ } else if ( angular.isObject(trigger) ) {
718
+ element.css(trigger);
719
+ }
720
+ //If browser does not support transitions, instantly resolve
721
+ if ( !endEventName ) {
722
+ deferred.resolve(element);
723
+ }
724
+ });
725
+
726
+ // Add our custom cancel function to the promise that is returned
727
+ // We can call this if we are about to run a new transition, which we know will prevent this transition from ending,
728
+ // i.e. it will therefore never raise a transitionEnd event for that transition
729
+ deferred.promise.cancel = function() {
730
+ if ( endEventName ) {
731
+ element.unbind(endEventName, transitionEndHandler);
732
+ }
733
+ deferred.reject('Transition cancelled');
734
+ };
735
+
736
+ return deferred.promise;
737
+ };
738
+
739
+ // Work out the name of the transitionEnd event
740
+ var transElement = document.createElement('trans');
741
+ var transitionEndEventNames = {
742
+ 'WebkitTransition': 'webkitTransitionEnd',
743
+ 'MozTransition': 'transitionend',
744
+ 'OTransition': 'oTransitionEnd',
745
+ 'transition': 'transitionend'
746
+ };
747
+ var animationEndEventNames = {
748
+ 'WebkitTransition': 'webkitAnimationEnd',
749
+ 'MozTransition': 'animationend',
750
+ 'OTransition': 'oAnimationEnd',
751
+ 'transition': 'animationend'
752
+ };
753
+ function findEndEventName(endEventNames) {
754
+ for (var name in endEventNames){
755
+ if (transElement.style[name] !== undefined) {
756
+ return endEventNames[name];
757
+ }
758
+ }
759
+ }
760
+ $transition.transitionEndEventName = findEndEventName(transitionEndEventNames);
761
+ $transition.animationEndEventName = findEndEventName(animationEndEventNames);
762
+ return $transition;
763
+ }]);
764
+
765
+ angular.module('mm.foundation.modal', ['mm.foundation.transition'])
766
+
767
+ /**
768
+ * A helper, internal data structure that acts as a map but also allows getting / removing
769
+ * elements in the LIFO order
770
+ */
771
+ .factory('$$stackedMap', function () {
772
+ return {
773
+ createNew: function () {
774
+ var stack = [];
775
+
776
+ return {
777
+ add: function (key, value) {
778
+ stack.push({
779
+ key: key,
780
+ value: value
781
+ });
782
+ },
783
+ get: function (key) {
784
+ for (var i = 0; i < stack.length; i++) {
785
+ if (key == stack[i].key) {
786
+ return stack[i];
787
+ }
788
+ }
789
+ },
790
+ keys: function() {
791
+ var keys = [];
792
+ for (var i = 0; i < stack.length; i++) {
793
+ keys.push(stack[i].key);
794
+ }
795
+ return keys;
796
+ },
797
+ top: function () {
798
+ return stack[stack.length - 1];
799
+ },
800
+ remove: function (key) {
801
+ var idx = -1;
802
+ for (var i = 0; i < stack.length; i++) {
803
+ if (key == stack[i].key) {
804
+ idx = i;
805
+ break;
806
+ }
807
+ }
808
+ return stack.splice(idx, 1)[0];
809
+ },
810
+ removeTop: function () {
811
+ return stack.splice(stack.length - 1, 1)[0];
812
+ },
813
+ length: function () {
814
+ return stack.length;
815
+ }
816
+ };
817
+ }
818
+ };
819
+ })
820
+
821
+ /**
822
+ * A helper directive for the $modal service. It creates a backdrop element.
823
+ */
824
+ .directive('modalBackdrop', ['$modalStack', '$timeout', function ($modalStack, $timeout) {
825
+ return {
826
+ restrict: 'EA',
827
+ replace: true,
828
+ templateUrl: 'template/modal/backdrop.html',
829
+ link: function (scope) {
830
+
831
+ scope.animate = false;
832
+
833
+ //trigger CSS transitions
834
+ $timeout(function () {
835
+ scope.animate = true;
836
+ });
837
+
838
+ scope.close = function (evt) {
839
+ var modal = $modalStack.getTop();
840
+ if (modal && modal.value.backdrop && modal.value.backdrop != 'static' && (evt.target === evt.currentTarget)) {
841
+ evt.preventDefault();
842
+ evt.stopPropagation();
843
+ $modalStack.dismiss(modal.key, 'backdrop click');
844
+ }
845
+ };
846
+ }
847
+ };
848
+ }])
849
+
850
+ .directive('modalWindow', ['$modalStack', '$timeout', function ($modalStack, $timeout) {
851
+ return {
852
+ restrict: 'EA',
853
+ scope: {
854
+ index: '@',
855
+ animate: '='
856
+ },
857
+ replace: true,
858
+ transclude: true,
859
+ templateUrl: 'template/modal/window.html',
860
+ link: function (scope, element, attrs) {
861
+ scope.windowClass = attrs.windowClass || '';
862
+
863
+ $timeout(function () {
864
+ // trigger CSS transitions
865
+ scope.animate = true;
866
+
867
+ // If the modal contains any autofocus elements refocus onto the first one
868
+ if (element[0].querySelectorAll('[autofocus]').length > 0) {
869
+ element[0].querySelectorAll('[autofocus]')[0].focus();
870
+ }
871
+ else{
872
+ // otherwise focus the freshly-opened modal
873
+ element[0].focus();
874
+ }
875
+ });
876
+ }
877
+ };
878
+ }])
879
+
880
+ .factory('$modalStack', ['$transition', '$timeout', '$document', '$compile', '$rootScope', '$$stackedMap',
881
+ function ($transition, $timeout, $document, $compile, $rootScope, $$stackedMap) {
882
+
883
+ var OPENED_MODAL_CLASS = 'modal-open';
884
+
885
+ var backdropDomEl, backdropScope;
886
+ var openedWindows = $$stackedMap.createNew();
887
+ var $modalStack = {};
888
+
889
+ function backdropIndex() {
890
+ var topBackdropIndex = -1;
891
+ var opened = openedWindows.keys();
892
+ for (var i = 0; i < opened.length; i++) {
893
+ if (openedWindows.get(opened[i]).value.backdrop) {
894
+ topBackdropIndex = i;
895
+ }
896
+ }
897
+ return topBackdropIndex;
898
+ }
899
+
900
+ $rootScope.$watch(backdropIndex, function(newBackdropIndex){
901
+ if (backdropScope) {
902
+ backdropScope.index = newBackdropIndex;
903
+ }
904
+ });
905
+
906
+ function removeModalWindow(modalInstance) {
907
+
908
+ var body = $document.find('body').eq(0);
909
+ var modalWindow = openedWindows.get(modalInstance).value;
910
+
911
+ //clean up the stack
912
+ openedWindows.remove(modalInstance);
913
+
914
+ //remove window DOM element
915
+ removeAfterAnimate(modalWindow.modalDomEl, modalWindow.modalScope, 300, checkRemoveBackdrop);
916
+ body.toggleClass(OPENED_MODAL_CLASS, openedWindows.length() > 0);
917
+ }
918
+
919
+ function checkRemoveBackdrop() {
920
+ //remove backdrop if no longer needed
921
+ if (backdropDomEl && backdropIndex() == -1) {
922
+ var backdropScopeRef = backdropScope;
923
+ removeAfterAnimate(backdropDomEl, backdropScope, 150, function () {
924
+ backdropScopeRef.$destroy();
925
+ backdropScopeRef = null;
926
+ });
927
+ backdropDomEl = undefined;
928
+ backdropScope = undefined;
929
+ }
930
+ }
931
+
932
+ function removeAfterAnimate(domEl, scope, emulateTime, done) {
933
+ // Closing animation
934
+ scope.animate = false;
935
+
936
+ var transitionEndEventName = $transition.transitionEndEventName;
937
+ if (transitionEndEventName) {
938
+ // transition out
939
+ var timeout = $timeout(afterAnimating, emulateTime);
940
+
941
+ domEl.bind(transitionEndEventName, function () {
942
+ $timeout.cancel(timeout);
943
+ afterAnimating();
944
+ scope.$apply();
945
+ });
946
+ } else {
947
+ // Ensure this call is async
948
+ $timeout(afterAnimating, 0);
949
+ }
950
+
951
+ function afterAnimating() {
952
+ if (afterAnimating.done) {
953
+ return;
954
+ }
955
+ afterAnimating.done = true;
956
+
957
+ domEl.remove();
958
+ if (done) {
959
+ done();
960
+ }
961
+ }
962
+ }
963
+
964
+ $document.bind('keydown', function (evt) {
965
+ var modal;
966
+
967
+ if (evt.which === 27) {
968
+ modal = openedWindows.top();
969
+ if (modal && modal.value.keyboard) {
970
+ $rootScope.$apply(function () {
971
+ $modalStack.dismiss(modal.key);
972
+ });
973
+ }
974
+ }
975
+ });
976
+
977
+ $modalStack.open = function (modalInstance, modal) {
978
+
979
+ openedWindows.add(modalInstance, {
980
+ deferred: modal.deferred,
981
+ modalScope: modal.scope,
982
+ backdrop: modal.backdrop,
983
+ keyboard: modal.keyboard
984
+ });
985
+
986
+ var body = $document.find('body').eq(0),
987
+ currBackdropIndex = backdropIndex();
988
+
989
+ if (currBackdropIndex >= 0 && !backdropDomEl) {
990
+ backdropScope = $rootScope.$new(true);
991
+ backdropScope.index = currBackdropIndex;
992
+ backdropDomEl = $compile('<div modal-backdrop></div>')(backdropScope);
993
+ body.append(backdropDomEl);
994
+ }
995
+
996
+ var angularDomEl = angular.element('<div modal-window></div>');
997
+ angularDomEl.attr('window-class', modal.windowClass);
998
+ angularDomEl.attr('index', openedWindows.length() - 1);
999
+ angularDomEl.attr('animate', 'animate');
1000
+ angularDomEl.html(modal.content);
1001
+
1002
+ var modalDomEl = $compile(angularDomEl)(modal.scope);
1003
+ openedWindows.top().value.modalDomEl = modalDomEl;
1004
+ body.append(modalDomEl);
1005
+ body.addClass(OPENED_MODAL_CLASS);
1006
+ };
1007
+
1008
+ $modalStack.close = function (modalInstance, result) {
1009
+ var modalWindow = openedWindows.get(modalInstance).value;
1010
+ if (modalWindow) {
1011
+ modalWindow.deferred.resolve(result);
1012
+ removeModalWindow(modalInstance);
1013
+ }
1014
+ };
1015
+
1016
+ $modalStack.dismiss = function (modalInstance, reason) {
1017
+ var modalWindow = openedWindows.get(modalInstance).value;
1018
+ if (modalWindow) {
1019
+ modalWindow.deferred.reject(reason);
1020
+ removeModalWindow(modalInstance);
1021
+ }
1022
+ };
1023
+
1024
+ $modalStack.dismissAll = function (reason) {
1025
+ var topModal = this.getTop();
1026
+ while (topModal) {
1027
+ this.dismiss(topModal.key, reason);
1028
+ topModal = this.getTop();
1029
+ }
1030
+ };
1031
+
1032
+ $modalStack.getTop = function () {
1033
+ return openedWindows.top();
1034
+ };
1035
+
1036
+ return $modalStack;
1037
+ }])
1038
+
1039
+ .provider('$modal', function () {
1040
+
1041
+ var $modalProvider = {
1042
+ options: {
1043
+ backdrop: true, //can be also false or 'static'
1044
+ keyboard: true
1045
+ },
1046
+ $get: ['$injector', '$rootScope', '$q', '$http', '$templateCache', '$controller', '$modalStack',
1047
+ function ($injector, $rootScope, $q, $http, $templateCache, $controller, $modalStack) {
1048
+
1049
+ var $modal = {};
1050
+
1051
+ function getTemplatePromise(options) {
1052
+ return options.template ? $q.when(options.template) :
1053
+ $http.get(options.templateUrl, {cache: $templateCache}).then(function (result) {
1054
+ return result.data;
1055
+ });
1056
+ }
1057
+
1058
+ function getResolvePromises(resolves) {
1059
+ var promisesArr = [];
1060
+ angular.forEach(resolves, function (value, key) {
1061
+ if (angular.isFunction(value) || angular.isArray(value)) {
1062
+ promisesArr.push($q.when($injector.invoke(value)));
1063
+ }
1064
+ });
1065
+ return promisesArr;
1066
+ }
1067
+
1068
+ $modal.open = function (modalOptions) {
1069
+
1070
+ var modalResultDeferred = $q.defer();
1071
+ var modalOpenedDeferred = $q.defer();
1072
+
1073
+ //prepare an instance of a modal to be injected into controllers and returned to a caller
1074
+ var modalInstance = {
1075
+ result: modalResultDeferred.promise,
1076
+ opened: modalOpenedDeferred.promise,
1077
+ close: function (result) {
1078
+ $modalStack.close(modalInstance, result);
1079
+ },
1080
+ dismiss: function (reason) {
1081
+ $modalStack.dismiss(modalInstance, reason);
1082
+ }
1083
+ };
1084
+
1085
+ //merge and clean up options
1086
+ modalOptions = angular.extend({}, $modalProvider.options, modalOptions);
1087
+ modalOptions.resolve = modalOptions.resolve || {};
1088
+
1089
+ //verify options
1090
+ if (!modalOptions.template && !modalOptions.templateUrl) {
1091
+ throw new Error('One of template or templateUrl options is required.');
1092
+ }
1093
+
1094
+ var templateAndResolvePromise =
1095
+ $q.all([getTemplatePromise(modalOptions)].concat(getResolvePromises(modalOptions.resolve)));
1096
+
1097
+
1098
+ templateAndResolvePromise.then(function resolveSuccess(tplAndVars) {
1099
+
1100
+ var modalScope = (modalOptions.scope || $rootScope).$new();
1101
+ modalScope.$close = modalInstance.close;
1102
+ modalScope.$dismiss = modalInstance.dismiss;
1103
+
1104
+ var ctrlInstance, ctrlLocals = {};
1105
+ var resolveIter = 1;
1106
+
1107
+ //controllers
1108
+ if (modalOptions.controller) {
1109
+ ctrlLocals.$scope = modalScope;
1110
+ ctrlLocals.$modalInstance = modalInstance;
1111
+ angular.forEach(modalOptions.resolve, function (value, key) {
1112
+ ctrlLocals[key] = tplAndVars[resolveIter++];
1113
+ });
1114
+
1115
+ ctrlInstance = $controller(modalOptions.controller, ctrlLocals);
1116
+ }
1117
+
1118
+ $modalStack.open(modalInstance, {
1119
+ scope: modalScope,
1120
+ deferred: modalResultDeferred,
1121
+ content: tplAndVars[0],
1122
+ backdrop: modalOptions.backdrop,
1123
+ keyboard: modalOptions.keyboard,
1124
+ windowClass: modalOptions.windowClass
1125
+ });
1126
+
1127
+ }, function resolveError(reason) {
1128
+ modalResultDeferred.reject(reason);
1129
+ });
1130
+
1131
+ templateAndResolvePromise.then(function () {
1132
+ modalOpenedDeferred.resolve(true);
1133
+ }, function () {
1134
+ modalOpenedDeferred.reject(false);
1135
+ });
1136
+
1137
+ return modalInstance;
1138
+ };
1139
+
1140
+ return $modal;
1141
+ }]
1142
+ };
1143
+
1144
+ return $modalProvider;
1145
+ });
1146
+
1147
+ angular.module("mm.foundation.offcanvas", [])
1148
+ .directive('offCanvasWrap', ['$window', function ($window) {
1149
+ return {
1150
+ scope: {},
1151
+ restrict: 'C',
1152
+ link: function ($scope, element, attrs) {
1153
+ var win = angular.element($window);
1154
+ var sidebar = $scope.sidebar = element;
1155
+
1156
+ $scope.hide = function () {
1157
+ sidebar.removeClass('move-left');
1158
+ sidebar.removeClass('move-right');
1159
+ };
1160
+
1161
+ win.bind("resize.body", $scope.hide);
1162
+
1163
+ $scope.$on('$destroy', function() {
1164
+ win.unbind("resize.body", $scope.hide);
1165
+ });
1166
+
1167
+ },
1168
+ controller: ['$scope', function($scope) {
1169
+
1170
+ this.leftToggle = function() {
1171
+ $scope.sidebar.toggleClass("move-right");
1172
+ };
1173
+
1174
+ this.rightToggle = function() {
1175
+ $scope.sidebar.toggleClass("move-left");
1176
+ };
1177
+
1178
+ this.hide = function() {
1179
+ $scope.hide();
1180
+ };
1181
+ }]
1182
+ };
1183
+ }])
1184
+ .directive('leftOffCanvasToggle', [function () {
1185
+ return {
1186
+ require: '^offCanvasWrap',
1187
+ restrict: 'C',
1188
+ link: function ($scope, element, attrs, offCanvasWrap) {
1189
+ element.on('click', function () {
1190
+ offCanvasWrap.leftToggle();
1191
+ });
1192
+ }
1193
+ };
1194
+ }])
1195
+ .directive('rightOffCanvasToggle', [function () {
1196
+ return {
1197
+ require: '^offCanvasWrap',
1198
+ restrict: 'C',
1199
+ link: function ($scope, element, attrs, offCanvasWrap) {
1200
+ element.on('click', function () {
1201
+ offCanvasWrap.rightToggle();
1202
+ });
1203
+ }
1204
+ };
1205
+ }])
1206
+ .directive('exitOffCanvas', [function () {
1207
+ return {
1208
+ require: '^offCanvasWrap',
1209
+ restrict: 'C',
1210
+ link: function ($scope, element, attrs, offCanvasWrap) {
1211
+ element.on('click', function () {
1212
+ offCanvasWrap.hide();
1213
+ });
1214
+ }
1215
+ };
1216
+ }])
1217
+ .directive('offCanvasList', [function () {
1218
+ return {
1219
+ require: '^offCanvasWrap',
1220
+ restrict: 'C',
1221
+ link: function ($scope, element, attrs, offCanvasWrap) {
1222
+ element.on('click', function () {
1223
+ offCanvasWrap.hide();
1224
+ });
1225
+ }
1226
+ };
1227
+ }]);
1228
+
1229
+ angular.module('mm.foundation.pagination', [])
1230
+
1231
+ .controller('PaginationController', ['$scope', '$attrs', '$parse', '$interpolate', function ($scope, $attrs, $parse, $interpolate) {
1232
+ var self = this,
1233
+ setNumPages = $attrs.numPages ? $parse($attrs.numPages).assign : angular.noop;
1234
+
1235
+ this.init = function(defaultItemsPerPage) {
1236
+ if ($attrs.itemsPerPage) {
1237
+ $scope.$parent.$watch($parse($attrs.itemsPerPage), function(value) {
1238
+ self.itemsPerPage = parseInt(value, 10);
1239
+ $scope.totalPages = self.calculateTotalPages();
1240
+ });
1241
+ } else {
1242
+ this.itemsPerPage = defaultItemsPerPage;
1243
+ }
1244
+ };
1245
+
1246
+ this.noPrevious = function() {
1247
+ return this.page === 1;
1248
+ };
1249
+ this.noNext = function() {
1250
+ return this.page === $scope.totalPages;
1251
+ };
1252
+
1253
+ this.isActive = function(page) {
1254
+ return this.page === page;
1255
+ };
1256
+
1257
+ this.calculateTotalPages = function() {
1258
+ var totalPages = this.itemsPerPage < 1 ? 1 : Math.ceil($scope.totalItems / this.itemsPerPage);
1259
+ return Math.max(totalPages || 0, 1);
1260
+ };
1261
+
1262
+ this.getAttributeValue = function(attribute, defaultValue, interpolate) {
1263
+ return angular.isDefined(attribute) ? (interpolate ? $interpolate(attribute)($scope.$parent) : $scope.$parent.$eval(attribute)) : defaultValue;
1264
+ };
1265
+
1266
+ this.render = function() {
1267
+ this.page = parseInt($scope.page, 10) || 1;
1268
+ if (this.page > 0 && this.page <= $scope.totalPages) {
1269
+ $scope.pages = this.getPages(this.page, $scope.totalPages);
1270
+ }
1271
+ };
1272
+
1273
+ $scope.selectPage = function(page) {
1274
+ if ( ! self.isActive(page) && page > 0 && page <= $scope.totalPages) {
1275
+ $scope.page = page;
1276
+ $scope.onSelectPage({ page: page });
1277
+ }
1278
+ };
1279
+
1280
+ $scope.$watch('page', function() {
1281
+ self.render();
1282
+ });
1283
+
1284
+ $scope.$watch('totalItems', function() {
1285
+ $scope.totalPages = self.calculateTotalPages();
1286
+ });
1287
+
1288
+ $scope.$watch('totalPages', function(value) {
1289
+ setNumPages($scope.$parent, value); // Readonly variable
1290
+
1291
+ if ( self.page > value ) {
1292
+ $scope.selectPage(value);
1293
+ } else {
1294
+ self.render();
1295
+ }
1296
+ });
1297
+ }])
1298
+
1299
+ .constant('paginationConfig', {
1300
+ itemsPerPage: 10,
1301
+ boundaryLinks: false,
1302
+ directionLinks: true,
1303
+ firstText: 'First',
1304
+ previousText: 'Previous',
1305
+ nextText: 'Next',
1306
+ lastText: 'Last',
1307
+ rotate: true
1308
+ })
1309
+
1310
+ .directive('pagination', ['$parse', 'paginationConfig', function($parse, config) {
1311
+ return {
1312
+ restrict: 'EA',
1313
+ scope: {
1314
+ page: '=',
1315
+ totalItems: '=',
1316
+ onSelectPage:' &'
1317
+ },
1318
+ controller: 'PaginationController',
1319
+ templateUrl: 'template/pagination/pagination.html',
1320
+ replace: true,
1321
+ link: function(scope, element, attrs, paginationCtrl) {
1322
+
1323
+ // Setup configuration parameters
1324
+ var maxSize,
1325
+ boundaryLinks = paginationCtrl.getAttributeValue(attrs.boundaryLinks, config.boundaryLinks ),
1326
+ directionLinks = paginationCtrl.getAttributeValue(attrs.directionLinks, config.directionLinks ),
1327
+ firstText = paginationCtrl.getAttributeValue(attrs.firstText, config.firstText, true),
1328
+ previousText = paginationCtrl.getAttributeValue(attrs.previousText, config.previousText, true),
1329
+ nextText = paginationCtrl.getAttributeValue(attrs.nextText, config.nextText, true),
1330
+ lastText = paginationCtrl.getAttributeValue(attrs.lastText, config.lastText, true),
1331
+ rotate = paginationCtrl.getAttributeValue(attrs.rotate, config.rotate);
1332
+
1333
+ paginationCtrl.init(config.itemsPerPage);
1334
+
1335
+ if (attrs.maxSize) {
1336
+ scope.$parent.$watch($parse(attrs.maxSize), function(value) {
1337
+ maxSize = parseInt(value, 10);
1338
+ paginationCtrl.render();
1339
+ });
1340
+ }
1341
+
1342
+ // Create page object used in template
1343
+ function makePage(number, text, isActive, isDisabled) {
1344
+ return {
1345
+ number: number,
1346
+ text: text,
1347
+ active: isActive,
1348
+ disabled: isDisabled
1349
+ };
1350
+ }
1351
+
1352
+ paginationCtrl.getPages = function(currentPage, totalPages) {
1353
+ var pages = [];
1354
+
1355
+ // Default page limits
1356
+ var startPage = 1, endPage = totalPages;
1357
+ var isMaxSized = ( angular.isDefined(maxSize) && maxSize < totalPages );
1358
+
1359
+ // recompute if maxSize
1360
+ if ( isMaxSized ) {
1361
+ if ( rotate ) {
1362
+ // Current page is displayed in the middle of the visible ones
1363
+ startPage = Math.max(currentPage - Math.floor(maxSize/2), 1);
1364
+ endPage = startPage + maxSize - 1;
1365
+
1366
+ // Adjust if limit is exceeded
1367
+ if (endPage > totalPages) {
1368
+ endPage = totalPages;
1369
+ startPage = endPage - maxSize + 1;
1370
+ }
1371
+ } else {
1372
+ // Visible pages are paginated with maxSize
1373
+ startPage = ((Math.ceil(currentPage / maxSize) - 1) * maxSize) + 1;
1374
+
1375
+ // Adjust last page if limit is exceeded
1376
+ endPage = Math.min(startPage + maxSize - 1, totalPages);
1377
+ }
1378
+ }
1379
+
1380
+ // Add page number links
1381
+ for (var number = startPage; number <= endPage; number++) {
1382
+ var page = makePage(number, number, paginationCtrl.isActive(number), false);
1383
+ pages.push(page);
1384
+ }
1385
+
1386
+ // Add links to move between page sets
1387
+ if ( isMaxSized && ! rotate ) {
1388
+ if ( startPage > 1 ) {
1389
+ var previousPageSet = makePage(startPage - 1, '...', false, false);
1390
+ pages.unshift(previousPageSet);
1391
+ }
1392
+
1393
+ if ( endPage < totalPages ) {
1394
+ var nextPageSet = makePage(endPage + 1, '...', false, false);
1395
+ pages.push(nextPageSet);
1396
+ }
1397
+ }
1398
+
1399
+ // Add previous & next links
1400
+ if (directionLinks) {
1401
+ var previousPage = makePage(currentPage - 1, previousText, false, paginationCtrl.noPrevious());
1402
+ pages.unshift(previousPage);
1403
+
1404
+ var nextPage = makePage(currentPage + 1, nextText, false, paginationCtrl.noNext());
1405
+ pages.push(nextPage);
1406
+ }
1407
+
1408
+ // Add first & last links
1409
+ if (boundaryLinks) {
1410
+ var firstPage = makePage(1, firstText, false, paginationCtrl.noPrevious());
1411
+ pages.unshift(firstPage);
1412
+
1413
+ var lastPage = makePage(totalPages, lastText, false, paginationCtrl.noNext());
1414
+ pages.push(lastPage);
1415
+ }
1416
+
1417
+ return pages;
1418
+ };
1419
+ }
1420
+ };
1421
+ }])
1422
+
1423
+ .constant('pagerConfig', {
1424
+ itemsPerPage: 10,
1425
+ previousText: '« Previous',
1426
+ nextText: 'Next »',
1427
+ align: true
1428
+ })
1429
+
1430
+ .directive('pager', ['pagerConfig', function(config) {
1431
+ return {
1432
+ restrict: 'EA',
1433
+ scope: {
1434
+ page: '=',
1435
+ totalItems: '=',
1436
+ onSelectPage:' &'
1437
+ },
1438
+ controller: 'PaginationController',
1439
+ templateUrl: 'template/pagination/pager.html',
1440
+ replace: true,
1441
+ link: function(scope, element, attrs, paginationCtrl) {
1442
+
1443
+ // Setup configuration parameters
1444
+ var previousText = paginationCtrl.getAttributeValue(attrs.previousText, config.previousText, true),
1445
+ nextText = paginationCtrl.getAttributeValue(attrs.nextText, config.nextText, true),
1446
+ align = paginationCtrl.getAttributeValue(attrs.align, config.align);
1447
+
1448
+ paginationCtrl.init(config.itemsPerPage);
1449
+
1450
+ // Create page object used in template
1451
+ function makePage(number, text, isDisabled, isPrevious, isNext) {
1452
+ return {
1453
+ number: number,
1454
+ text: text,
1455
+ disabled: isDisabled,
1456
+ previous: ( align && isPrevious ),
1457
+ next: ( align && isNext )
1458
+ };
1459
+ }
1460
+
1461
+ paginationCtrl.getPages = function(currentPage) {
1462
+ return [
1463
+ makePage(currentPage - 1, previousText, paginationCtrl.noPrevious(), true, false),
1464
+ makePage(currentPage + 1, nextText, paginationCtrl.noNext(), false, true)
1465
+ ];
1466
+ };
1467
+ }
1468
+ };
1469
+ }]);
1470
+
1471
+ /**
1472
+ * The following features are still outstanding: animation as a
1473
+ * function, placement as a function, inside, support for more triggers than
1474
+ * just mouse enter/leave, html tooltips, and selector delegation.
1475
+ */
1476
+ angular.module( 'mm.foundation.tooltip', [ 'mm.foundation.position', 'mm.foundation.bindHtml' ] )
1477
+
1478
+ /**
1479
+ * The $tooltip service creates tooltip- and popover-like directives as well as
1480
+ * houses global options for them.
1481
+ */
1482
+ .provider( '$tooltip', function () {
1483
+ // The default options tooltip and popover.
1484
+ var defaultOptions = {
1485
+ placement: 'top',
1486
+ animation: true,
1487
+ popupDelay: 0
1488
+ };
1489
+
1490
+ // Default hide triggers for each show trigger
1491
+ var triggerMap = {
1492
+ 'mouseenter': 'mouseleave',
1493
+ 'click': 'click',
1494
+ 'focus': 'blur'
1495
+ };
1496
+
1497
+ // The options specified to the provider globally.
1498
+ var globalOptions = {};
1499
+
1500
+ /**
1501
+ * `options({})` allows global configuration of all tooltips in the
1502
+ * application.
1503
+ *
1504
+ * var app = angular.module( 'App', ['mm.foundation.tooltip'], function( $tooltipProvider ) {
1505
+ * // place tooltips left instead of top by default
1506
+ * $tooltipProvider.options( { placement: 'left' } );
1507
+ * });
1508
+ */
1509
+ this.options = function( value ) {
1510
+ angular.extend( globalOptions, value );
1511
+ };
1512
+
1513
+ /**
1514
+ * This allows you to extend the set of trigger mappings available. E.g.:
1515
+ *
1516
+ * $tooltipProvider.setTriggers( 'openTrigger': 'closeTrigger' );
1517
+ */
1518
+ this.setTriggers = function setTriggers ( triggers ) {
1519
+ angular.extend( triggerMap, triggers );
1520
+ };
1521
+
1522
+ /**
1523
+ * This is a helper function for translating camel-case to snake-case.
1524
+ */
1525
+ function snake_case(name){
1526
+ var regexp = /[A-Z]/g;
1527
+ var separator = '-';
1528
+ return name.replace(regexp, function(letter, pos) {
1529
+ return (pos ? separator : '') + letter.toLowerCase();
1530
+ });
1531
+ }
1532
+
1533
+ /**
1534
+ * Returns the actual instance of the $tooltip service.
1535
+ * TODO support multiple triggers
1536
+ */
1537
+ this.$get = [ '$window', '$compile', '$timeout', '$parse', '$document', '$position', '$interpolate', function ( $window, $compile, $timeout, $parse, $document, $position, $interpolate ) {
1538
+ return function $tooltip ( type, prefix, defaultTriggerShow ) {
1539
+ var options = angular.extend( {}, defaultOptions, globalOptions );
1540
+
1541
+ /**
1542
+ * Returns an object of show and hide triggers.
1543
+ *
1544
+ * If a trigger is supplied,
1545
+ * it is used to show the tooltip; otherwise, it will use the `trigger`
1546
+ * option passed to the `$tooltipProvider.options` method; else it will
1547
+ * default to the trigger supplied to this directive factory.
1548
+ *
1549
+ * The hide trigger is based on the show trigger. If the `trigger` option
1550
+ * was passed to the `$tooltipProvider.options` method, it will use the
1551
+ * mapped trigger from `triggerMap` or the passed trigger if the map is
1552
+ * undefined; otherwise, it uses the `triggerMap` value of the show
1553
+ * trigger; else it will just use the show trigger.
1554
+ */
1555
+ function getTriggers ( trigger ) {
1556
+ var show = trigger || options.trigger || defaultTriggerShow;
1557
+ var hide = triggerMap[show] || show;
1558
+ return {
1559
+ show: show,
1560
+ hide: hide
1561
+ };
1562
+ }
1563
+
1564
+ var directiveName = snake_case( type );
1565
+
1566
+ var startSym = $interpolate.startSymbol();
1567
+ var endSym = $interpolate.endSymbol();
1568
+ var template =
1569
+ '<div '+ directiveName +'-popup '+
1570
+ 'title="'+startSym+'tt_title'+endSym+'" '+
1571
+ 'content="'+startSym+'tt_content'+endSym+'" '+
1572
+ 'placement="'+startSym+'tt_placement'+endSym+'" '+
1573
+ 'animation="tt_animation" '+
1574
+ 'is-open="tt_isOpen"'+
1575
+ '>'+
1576
+ '</div>';
1577
+
1578
+ return {
1579
+ restrict: 'EA',
1580
+ scope: true,
1581
+ compile: function (tElem, tAttrs) {
1582
+ var tooltipLinker = $compile( template );
1583
+
1584
+ return function link ( scope, element, attrs ) {
1585
+ var tooltip;
1586
+ var transitionTimeout;
1587
+ var popupTimeout;
1588
+ var appendToBody = angular.isDefined( options.appendToBody ) ? options.appendToBody : false;
1589
+ var triggers = getTriggers( undefined );
1590
+ var hasRegisteredTriggers = false;
1591
+ var hasEnableExp = angular.isDefined(attrs[prefix+'Enable']);
1592
+
1593
+ var positionTooltip = function (){
1594
+ var position,
1595
+ ttWidth,
1596
+ ttHeight,
1597
+ ttPosition;
1598
+ // Get the position of the directive element.
1599
+ position = appendToBody ? $position.offset( element ) : $position.position( element );
1600
+
1601
+ // Get the height and width of the tooltip so we can center it.
1602
+ ttWidth = tooltip.prop( 'offsetWidth' );
1603
+ ttHeight = tooltip.prop( 'offsetHeight' );
1604
+
1605
+ // Calculate the tooltip's top and left coordinates to center it with
1606
+ // this directive.
1607
+ switch ( scope.tt_placement ) {
1608
+ case 'right':
1609
+ ttPosition = {
1610
+ top: position.top + position.height / 2 - ttHeight / 2,
1611
+ left: position.left + position.width + 10
1612
+ };
1613
+ break;
1614
+ case 'bottom':
1615
+ ttPosition = {
1616
+ top: position.top + position.height + 10,
1617
+ left: position.left
1618
+ };
1619
+ break;
1620
+ case 'left':
1621
+ ttPosition = {
1622
+ top: position.top + position.height / 2 - ttHeight / 2,
1623
+ left: position.left - ttWidth - 10
1624
+ };
1625
+ break;
1626
+ default:
1627
+ ttPosition = {
1628
+ top: position.top - ttHeight - 10,
1629
+ left: position.left
1630
+ };
1631
+ break;
1632
+ }
1633
+
1634
+ ttPosition.top += 'px';
1635
+ ttPosition.left += 'px';
1636
+
1637
+ // Now set the calculated positioning.
1638
+ tooltip.css( ttPosition );
1639
+
1640
+ };
1641
+
1642
+ // By default, the tooltip is not open.
1643
+ // TODO add ability to start tooltip opened
1644
+ scope.tt_isOpen = false;
1645
+
1646
+ function toggleTooltipBind () {
1647
+ if ( ! scope.tt_isOpen ) {
1648
+ showTooltipBind();
1649
+ } else {
1650
+ hideTooltipBind();
1651
+ }
1652
+ }
1653
+
1654
+ // Show the tooltip with delay if specified, otherwise show it immediately
1655
+ function showTooltipBind() {
1656
+ if(hasEnableExp && !scope.$eval(attrs[prefix+'Enable'])) {
1657
+ return;
1658
+ }
1659
+ if ( scope.tt_popupDelay ) {
1660
+ popupTimeout = $timeout( show, scope.tt_popupDelay, false );
1661
+ popupTimeout.then(function(reposition){reposition();});
1662
+ } else {
1663
+ show()();
1664
+ }
1665
+ }
1666
+
1667
+ function hideTooltipBind () {
1668
+ scope.$apply(function () {
1669
+ hide();
1670
+ });
1671
+ }
1672
+
1673
+ // Show the tooltip popup element.
1674
+ function show() {
1675
+
1676
+
1677
+ // Don't show empty tooltips.
1678
+ if ( ! scope.tt_content ) {
1679
+ return angular.noop;
1680
+ }
1681
+
1682
+ createTooltip();
1683
+
1684
+ // If there is a pending remove transition, we must cancel it, lest the
1685
+ // tooltip be mysteriously removed.
1686
+ if ( transitionTimeout ) {
1687
+ $timeout.cancel( transitionTimeout );
1688
+ }
1689
+
1690
+ // Set the initial positioning.
1691
+ tooltip.css({ top: 0, left: 0, display: 'block' });
1692
+
1693
+ // Now we add it to the DOM because need some info about it. But it's not
1694
+ // visible yet anyway.
1695
+ if ( appendToBody ) {
1696
+ $document.find( 'body' ).append( tooltip );
1697
+ } else {
1698
+ element.after( tooltip );
1699
+ }
1700
+
1701
+ positionTooltip();
1702
+
1703
+ // And show the tooltip.
1704
+ scope.tt_isOpen = true;
1705
+ scope.$digest(); // digest required as $apply is not called
1706
+
1707
+ // Return positioning function as promise callback for correct
1708
+ // positioning after draw.
1709
+ return positionTooltip;
1710
+ }
1711
+
1712
+ // Hide the tooltip popup element.
1713
+ function hide() {
1714
+ // First things first: we don't show it anymore.
1715
+ scope.tt_isOpen = false;
1716
+
1717
+ //if tooltip is going to be shown after delay, we must cancel this
1718
+ $timeout.cancel( popupTimeout );
1719
+
1720
+ // And now we remove it from the DOM. However, if we have animation, we
1721
+ // need to wait for it to expire beforehand.
1722
+ // FIXME: this is a placeholder for a port of the transitions library.
1723
+ if ( scope.tt_animation ) {
1724
+ transitionTimeout = $timeout(removeTooltip, 500);
1725
+ } else {
1726
+ removeTooltip();
1727
+ }
1728
+ }
1729
+
1730
+ function createTooltip() {
1731
+ // There can only be one tooltip element per directive shown at once.
1732
+ if (tooltip) {
1733
+ removeTooltip();
1734
+ }
1735
+ tooltip = tooltipLinker(scope, function () {});
1736
+
1737
+ // Get contents rendered into the tooltip
1738
+ scope.$digest();
1739
+ }
1740
+
1741
+ function removeTooltip() {
1742
+ if (tooltip) {
1743
+ tooltip.remove();
1744
+ tooltip = null;
1745
+ }
1746
+ }
1747
+
1748
+ /**
1749
+ * Observe the relevant attributes.
1750
+ */
1751
+ attrs.$observe( type, function ( val ) {
1752
+ scope.tt_content = val;
1753
+
1754
+ if (!val && scope.tt_isOpen ) {
1755
+ hide();
1756
+ }
1757
+ });
1758
+
1759
+ attrs.$observe( prefix+'Title', function ( val ) {
1760
+ scope.tt_title = val;
1761
+ });
1762
+
1763
+ attrs.$observe( prefix+'Placement', function ( val ) {
1764
+ scope.tt_placement = angular.isDefined( val ) ? val : options.placement;
1765
+ });
1766
+
1767
+ attrs.$observe( prefix+'PopupDelay', function ( val ) {
1768
+ var delay = parseInt( val, 10 );
1769
+ scope.tt_popupDelay = ! isNaN(delay) ? delay : options.popupDelay;
1770
+ });
1771
+
1772
+ var unregisterTriggers = function() {
1773
+ if (hasRegisteredTriggers) {
1774
+ element.unbind( triggers.show, showTooltipBind );
1775
+ element.unbind( triggers.hide, hideTooltipBind );
1776
+ }
1777
+ };
1778
+
1779
+ var unregisterTriggerFunction = function () {};
1780
+
1781
+ attrs.$observe( prefix+'Trigger', function ( val ) {
1782
+ unregisterTriggers();
1783
+ unregisterTriggerFunction();
1784
+
1785
+ triggers = getTriggers( val );
1786
+
1787
+ if ( angular.isFunction( triggers.show ) ) {
1788
+ unregisterTriggerFunction = scope.$watch( function () {
1789
+ return triggers.show( scope, element, attrs );
1790
+ }, function ( val ) {
1791
+ return val ? $timeout( show ) : $timeout( hide );
1792
+ });
1793
+ } else {
1794
+ if ( triggers.show === triggers.hide ) {
1795
+ element.bind( triggers.show, toggleTooltipBind );
1796
+ } else {
1797
+ element.bind( triggers.show, showTooltipBind );
1798
+ element.bind( triggers.hide, hideTooltipBind );
1799
+ }
1800
+ }
1801
+
1802
+ hasRegisteredTriggers = true;
1803
+ });
1804
+
1805
+ var animation = scope.$eval(attrs[prefix + 'Animation']);
1806
+ scope.tt_animation = angular.isDefined(animation) ? !!animation : options.animation;
1807
+
1808
+ attrs.$observe( prefix+'AppendToBody', function ( val ) {
1809
+ appendToBody = angular.isDefined( val ) ? $parse( val )( scope ) : appendToBody;
1810
+ });
1811
+
1812
+ // if a tooltip is attached to <body> we need to remove it on
1813
+ // location change as its parent scope will probably not be destroyed
1814
+ // by the change.
1815
+ if ( appendToBody ) {
1816
+ scope.$on('$locationChangeSuccess', function closeTooltipOnLocationChangeSuccess () {
1817
+ if ( scope.tt_isOpen ) {
1818
+ hide();
1819
+ }
1820
+ });
1821
+ }
1822
+
1823
+ // Make sure tooltip is destroyed and removed.
1824
+ scope.$on('$destroy', function onDestroyTooltip() {
1825
+ $timeout.cancel( transitionTimeout );
1826
+ $timeout.cancel( popupTimeout );
1827
+ unregisterTriggers();
1828
+ unregisterTriggerFunction();
1829
+ removeTooltip();
1830
+ });
1831
+ };
1832
+ }
1833
+ };
1834
+ };
1835
+ }];
1836
+ })
1837
+
1838
+ .directive( 'tooltipPopup', function () {
1839
+ return {
1840
+ restrict: 'EA',
1841
+ replace: true,
1842
+ scope: { content: '@', placement: '@', animation: '&', isOpen: '&' },
1843
+ templateUrl: 'template/tooltip/tooltip-popup.html'
1844
+ };
1845
+ })
1846
+
1847
+ .directive( 'tooltip', [ '$tooltip', function ( $tooltip ) {
1848
+ return $tooltip( 'tooltip', 'tooltip', 'mouseenter' );
1849
+ }])
1850
+
1851
+ .directive( 'tooltipHtmlUnsafePopup', function () {
1852
+ return {
1853
+ restrict: 'EA',
1854
+ replace: true,
1855
+ scope: { content: '@', placement: '@', animation: '&', isOpen: '&' },
1856
+ templateUrl: 'template/tooltip/tooltip-html-unsafe-popup.html'
1857
+ };
1858
+ })
1859
+
1860
+ .directive( 'tooltipHtmlUnsafe', [ '$tooltip', function ( $tooltip ) {
1861
+ return $tooltip( 'tooltipHtmlUnsafe', 'tooltip', 'mouseenter' );
1862
+ }]);
1863
+
1864
+ /**
1865
+ * The following features are still outstanding: popup delay, animation as a
1866
+ * function, placement as a function, inside, support for more triggers than
1867
+ * just mouse enter/leave, html popovers, and selector delegatation.
1868
+ */
1869
+ angular.module( 'mm.foundation.popover', [ 'mm.foundation.tooltip' ] )
1870
+
1871
+ .directive( 'popoverPopup', function () {
1872
+ return {
1873
+ restrict: 'EA',
1874
+ replace: true,
1875
+ scope: { title: '@', content: '@', placement: '@', animation: '&', isOpen: '&' },
1876
+ templateUrl: 'template/popover/popover.html'
1877
+ };
1878
+ })
1879
+
1880
+ .directive( 'popover', [ '$tooltip', function ( $tooltip ) {
1881
+ return $tooltip( 'popover', 'popover', 'click' );
1882
+ }]);
1883
+
1884
+ angular.module('mm.foundation.progressbar', ['mm.foundation.transition'])
1885
+
1886
+ .constant('progressConfig', {
1887
+ animate: true,
1888
+ max: 100
1889
+ })
1890
+
1891
+ .controller('ProgressController', ['$scope', '$attrs', 'progressConfig', '$transition', function($scope, $attrs, progressConfig, $transition) {
1892
+ var self = this,
1893
+ bars = [],
1894
+ max = angular.isDefined($attrs.max) ? $scope.$parent.$eval($attrs.max) : progressConfig.max,
1895
+ animate = angular.isDefined($attrs.animate) ? $scope.$parent.$eval($attrs.animate) : progressConfig.animate;
1896
+
1897
+ this.addBar = function(bar, element) {
1898
+ var oldValue = 0, index = bar.$parent.$index;
1899
+ if ( angular.isDefined(index) && bars[index] ) {
1900
+ oldValue = bars[index].value;
1901
+ }
1902
+ bars.push(bar);
1903
+
1904
+ this.update(element, bar.value, oldValue);
1905
+
1906
+ bar.$watch('value', function(value, oldValue) {
1907
+ if (value !== oldValue) {
1908
+ self.update(element, value, oldValue);
1909
+ }
1910
+ });
1911
+
1912
+ bar.$on('$destroy', function() {
1913
+ self.removeBar(bar);
1914
+ });
1915
+ };
1916
+
1917
+ // Update bar element width
1918
+ this.update = function(element, newValue, oldValue) {
1919
+ var percent = this.getPercentage(newValue);
1920
+
1921
+ if (animate) {
1922
+ element.css('width', this.getPercentage(oldValue) + '%');
1923
+ $transition(element, {width: percent + '%'});
1924
+ } else {
1925
+ element.css({'transition': 'none', 'width': percent + '%'});
1926
+ }
1927
+ };
1928
+
1929
+ this.removeBar = function(bar) {
1930
+ bars.splice(bars.indexOf(bar), 1);
1931
+ };
1932
+
1933
+ this.getPercentage = function(value) {
1934
+ return Math.round(100 * value / max);
1935
+ };
1936
+ }])
1937
+
1938
+ .directive('progress', function() {
1939
+ return {
1940
+ restrict: 'EA',
1941
+ replace: true,
1942
+ transclude: true,
1943
+ controller: 'ProgressController',
1944
+ require: 'progress',
1945
+ scope: {},
1946
+ template: '<div class="progress" ng-transclude></div>'
1947
+ //templateUrl: 'template/progressbar/progress.html' // Works in AngularJS 1.2
1948
+ };
1949
+ })
1950
+
1951
+ .directive('bar', function() {
1952
+ return {
1953
+ restrict: 'EA',
1954
+ replace: true,
1955
+ transclude: true,
1956
+ require: '^progress',
1957
+ scope: {
1958
+ value: '=',
1959
+ type: '@'
1960
+ },
1961
+ templateUrl: 'template/progressbar/bar.html',
1962
+ link: function(scope, element, attrs, progressCtrl) {
1963
+ progressCtrl.addBar(scope, element);
1964
+ }
1965
+ };
1966
+ })
1967
+
1968
+ .directive('progressbar', function() {
1969
+ return {
1970
+ restrict: 'EA',
1971
+ replace: true,
1972
+ transclude: true,
1973
+ controller: 'ProgressController',
1974
+ scope: {
1975
+ value: '=',
1976
+ type: '@'
1977
+ },
1978
+ templateUrl: 'template/progressbar/progressbar.html',
1979
+ link: function(scope, element, attrs, progressCtrl) {
1980
+ progressCtrl.addBar(scope, angular.element(element.children()[0]));
1981
+ }
1982
+ };
1983
+ });
1984
+
1985
+ angular.module('mm.foundation.rating', [])
1986
+
1987
+ .constant('ratingConfig', {
1988
+ max: 5,
1989
+ stateOn: null,
1990
+ stateOff: null
1991
+ })
1992
+
1993
+ .controller('RatingController', ['$scope', '$attrs', '$parse', 'ratingConfig', function($scope, $attrs, $parse, ratingConfig) {
1994
+
1995
+ this.maxRange = angular.isDefined($attrs.max) ? $scope.$parent.$eval($attrs.max) : ratingConfig.max;
1996
+ this.stateOn = angular.isDefined($attrs.stateOn) ? $scope.$parent.$eval($attrs.stateOn) : ratingConfig.stateOn;
1997
+ this.stateOff = angular.isDefined($attrs.stateOff) ? $scope.$parent.$eval($attrs.stateOff) : ratingConfig.stateOff;
1998
+
1999
+ this.createRateObjects = function(states) {
2000
+ var defaultOptions = {
2001
+ stateOn: this.stateOn,
2002
+ stateOff: this.stateOff
2003
+ };
2004
+
2005
+ for (var i = 0, n = states.length; i < n; i++) {
2006
+ states[i] = angular.extend({ index: i }, defaultOptions, states[i]);
2007
+ }
2008
+ return states;
2009
+ };
2010
+
2011
+ // Get objects used in template
2012
+ $scope.range = angular.isDefined($attrs.ratingStates) ? this.createRateObjects(angular.copy($scope.$parent.$eval($attrs.ratingStates))): this.createRateObjects(new Array(this.maxRange));
2013
+
2014
+ $scope.rate = function(value) {
2015
+ if ( $scope.value !== value && !$scope.readonly ) {
2016
+ $scope.value = value;
2017
+ }
2018
+ };
2019
+
2020
+ $scope.enter = function(value) {
2021
+ if ( ! $scope.readonly ) {
2022
+ $scope.val = value;
2023
+ }
2024
+ $scope.onHover({value: value});
2025
+ };
2026
+
2027
+ $scope.reset = function() {
2028
+ $scope.val = angular.copy($scope.value);
2029
+ $scope.onLeave();
2030
+ };
2031
+
2032
+ $scope.$watch('value', function(value) {
2033
+ $scope.val = value;
2034
+ });
2035
+
2036
+ $scope.readonly = false;
2037
+ if ($attrs.readonly) {
2038
+ $scope.$parent.$watch($parse($attrs.readonly), function(value) {
2039
+ $scope.readonly = !!value;
2040
+ });
2041
+ }
2042
+ }])
2043
+
2044
+ .directive('rating', function() {
2045
+ return {
2046
+ restrict: 'EA',
2047
+ scope: {
2048
+ value: '=',
2049
+ onHover: '&',
2050
+ onLeave: '&'
2051
+ },
2052
+ controller: 'RatingController',
2053
+ templateUrl: 'template/rating/rating.html',
2054
+ replace: true
2055
+ };
2056
+ });
2057
+
2058
+
2059
+ /**
2060
+ * @ngdoc overview
2061
+ * @name mm.foundation.tabs
2062
+ *
2063
+ * @description
2064
+ * AngularJS version of the tabs directive.
2065
+ */
2066
+
2067
+ angular.module('mm.foundation.tabs', [])
2068
+
2069
+ .controller('TabsetController', ['$scope', function TabsetCtrl($scope) {
2070
+ var ctrl = this,
2071
+ tabs = ctrl.tabs = $scope.tabs = [];
2072
+
2073
+ ctrl.select = function(tab) {
2074
+ angular.forEach(tabs, function(tab) {
2075
+ tab.active = false;
2076
+ });
2077
+ tab.active = true;
2078
+ };
2079
+
2080
+ ctrl.addTab = function addTab(tab) {
2081
+ tabs.push(tab);
2082
+ if (tabs.length === 1 || tab.active) {
2083
+ ctrl.select(tab);
2084
+ }
2085
+ };
2086
+
2087
+ ctrl.removeTab = function removeTab(tab) {
2088
+ var index = tabs.indexOf(tab);
2089
+ //Select a new tab if the tab to be removed is selected
2090
+ if (tab.active && tabs.length > 1) {
2091
+ //If this is the last tab, select the previous tab. else, the next tab.
2092
+ var newActiveIndex = index == tabs.length - 1 ? index - 1 : index + 1;
2093
+ ctrl.select(tabs[newActiveIndex]);
2094
+ }
2095
+ tabs.splice(index, 1);
2096
+ };
2097
+ }])
2098
+
2099
+ /**
2100
+ * @ngdoc directive
2101
+ * @name mm.foundation.tabs.directive:tabset
2102
+ * @restrict EA
2103
+ *
2104
+ * @description
2105
+ * Tabset is the outer container for the tabs directive
2106
+ *
2107
+ * @param {boolean=} vertical Whether or not to use vertical styling for the tabs.
2108
+ * @param {boolean=} justified Whether or not to use justified styling for the tabs.
2109
+ *
2110
+ * @example
2111
+ <example module="mm.foundation">
2112
+ <file name="index.html">
2113
+ <tabset>
2114
+ <tab heading="Tab 1"><b>First</b> Content!</tab>
2115
+ <tab heading="Tab 2"><i>Second</i> Content!</tab>
2116
+ </tabset>
2117
+ <hr />
2118
+ <tabset vertical="true">
2119
+ <tab heading="Vertical Tab 1"><b>First</b> Vertical Content!</tab>
2120
+ <tab heading="Vertical Tab 2"><i>Second</i> Vertical Content!</tab>
2121
+ </tabset>
2122
+ <tabset justified="true">
2123
+ <tab heading="Justified Tab 1"><b>First</b> Justified Content!</tab>
2124
+ <tab heading="Justified Tab 2"><i>Second</i> Justified Content!</tab>
2125
+ </tabset>
2126
+ </file>
2127
+ </example>
2128
+ */
2129
+ .directive('tabset', function() {
2130
+ return {
2131
+ restrict: 'EA',
2132
+ transclude: true,
2133
+ replace: true,
2134
+ scope: {},
2135
+ controller: 'TabsetController',
2136
+ templateUrl: 'template/tabs/tabset.html',
2137
+ link: function(scope, element, attrs) {
2138
+ scope.vertical = angular.isDefined(attrs.vertical) ? scope.$parent.$eval(attrs.vertical) : false;
2139
+ scope.justified = angular.isDefined(attrs.justified) ? scope.$parent.$eval(attrs.justified) : false;
2140
+ scope.type = angular.isDefined(attrs.type) ? scope.$parent.$eval(attrs.type) : 'tabs';
2141
+ }
2142
+ };
2143
+ })
2144
+
2145
+ /**
2146
+ * @ngdoc directive
2147
+ * @name mm.foundation.tabs.directive:tab
2148
+ * @restrict EA
2149
+ *
2150
+ * @param {string=} heading The visible heading, or title, of the tab. Set HTML headings with {@link mm.foundation.tabs.directive:tabHeading tabHeading}.
2151
+ * @param {string=} select An expression to evaluate when the tab is selected.
2152
+ * @param {boolean=} active A binding, telling whether or not this tab is selected.
2153
+ * @param {boolean=} disabled A binding, telling whether or not this tab is disabled.
2154
+ *
2155
+ * @description
2156
+ * Creates a tab with a heading and content. Must be placed within a {@link mm.foundation.tabs.directive:tabset tabset}.
2157
+ *
2158
+ * @example
2159
+ <example module="mm.foundation">
2160
+ <file name="index.html">
2161
+ <div ng-controller="TabsDemoCtrl">
2162
+ <button class="button small" ng-click="items[0].active = true">
2163
+ Select item 1, using active binding
2164
+ </button>
2165
+ <button class="button small" ng-click="items[1].disabled = !items[1].disabled">
2166
+ Enable/disable item 2, using disabled binding
2167
+ </button>
2168
+ <br />
2169
+ <tabset>
2170
+ <tab heading="Tab 1">First Tab</tab>
2171
+ <tab select="alertMe()">
2172
+ <tab-heading><i class="fa fa-bell"></i> Alert me!</tab-heading>
2173
+ Second Tab, with alert callback and html heading!
2174
+ </tab>
2175
+ <tab ng-repeat="item in items"
2176
+ heading="{{item.title}}"
2177
+ disabled="item.disabled"
2178
+ active="item.active">
2179
+ {{item.content}}
2180
+ </tab>
2181
+ </tabset>
2182
+ </div>
2183
+ </file>
2184
+ <file name="script.js">
2185
+ function TabsDemoCtrl($scope) {
2186
+ $scope.items = [
2187
+ { title:"Dynamic Title 1", content:"Dynamic Item 0" },
2188
+ { title:"Dynamic Title 2", content:"Dynamic Item 1", disabled: true }
2189
+ ];
2190
+
2191
+ $scope.alertMe = function() {
2192
+ setTimeout(function() {
2193
+ alert("You've selected the alert tab!");
2194
+ });
2195
+ };
2196
+ };
2197
+ </file>
2198
+ </example>
2199
+ */
2200
+
2201
+ /**
2202
+ * @ngdoc directive
2203
+ * @name mm.foundation.tabs.directive:tabHeading
2204
+ * @restrict EA
2205
+ *
2206
+ * @description
2207
+ * Creates an HTML heading for a {@link mm.foundation.tabs.directive:tab tab}. Must be placed as a child of a tab element.
2208
+ *
2209
+ * @example
2210
+ <example module="mm.foundation">
2211
+ <file name="index.html">
2212
+ <tabset>
2213
+ <tab>
2214
+ <tab-heading><b>HTML</b> in my titles?!</tab-heading>
2215
+ And some content, too!
2216
+ </tab>
2217
+ <tab>
2218
+ <tab-heading><i class="fa fa-heart"></i> Icon heading?!?</tab-heading>
2219
+ That's right.
2220
+ </tab>
2221
+ </tabset>
2222
+ </file>
2223
+ </example>
2224
+ */
2225
+ .directive('tab', ['$parse', function($parse) {
2226
+ return {
2227
+ require: '^tabset',
2228
+ restrict: 'EA',
2229
+ replace: true,
2230
+ templateUrl: 'template/tabs/tab.html',
2231
+ transclude: true,
2232
+ scope: {
2233
+ heading: '@',
2234
+ onSelect: '&select', //This callback is called in contentHeadingTransclude
2235
+ //once it inserts the tab's content into the dom
2236
+ onDeselect: '&deselect'
2237
+ },
2238
+ controller: function() {
2239
+ //Empty controller so other directives can require being 'under' a tab
2240
+ },
2241
+ compile: function(elm, attrs, transclude) {
2242
+ return function postLink(scope, elm, attrs, tabsetCtrl) {
2243
+ var getActive, setActive;
2244
+ if (attrs.active) {
2245
+ getActive = $parse(attrs.active);
2246
+ setActive = getActive.assign;
2247
+ scope.$parent.$watch(getActive, function updateActive(value, oldVal) {
2248
+ // Avoid re-initializing scope.active as it is already initialized
2249
+ // below. (watcher is called async during init with value ===
2250
+ // oldVal)
2251
+ if (value !== oldVal) {
2252
+ scope.active = !!value;
2253
+ }
2254
+ });
2255
+ scope.active = getActive(scope.$parent);
2256
+ } else {
2257
+ setActive = getActive = angular.noop;
2258
+ }
2259
+
2260
+ scope.$watch('active', function(active) {
2261
+ // Note this watcher also initializes and assigns scope.active to the
2262
+ // attrs.active expression.
2263
+ setActive(scope.$parent, active);
2264
+ if (active) {
2265
+ tabsetCtrl.select(scope);
2266
+ scope.onSelect();
2267
+ } else {
2268
+ scope.onDeselect();
2269
+ }
2270
+ });
2271
+
2272
+ scope.disabled = false;
2273
+ if ( attrs.disabled ) {
2274
+ scope.$parent.$watch($parse(attrs.disabled), function(value) {
2275
+ scope.disabled = !! value;
2276
+ });
2277
+ }
2278
+
2279
+ scope.select = function() {
2280
+ if ( ! scope.disabled ) {
2281
+ scope.active = true;
2282
+ }
2283
+ };
2284
+
2285
+ tabsetCtrl.addTab(scope);
2286
+ scope.$on('$destroy', function() {
2287
+ tabsetCtrl.removeTab(scope);
2288
+ });
2289
+
2290
+
2291
+ //We need to transclude later, once the content container is ready.
2292
+ //when this link happens, we're inside a tab heading.
2293
+ scope.$transcludeFn = transclude;
2294
+ };
2295
+ }
2296
+ };
2297
+ }])
2298
+
2299
+ .directive('tabHeadingTransclude', [function() {
2300
+ return {
2301
+ restrict: 'A',
2302
+ require: '^tab',
2303
+ link: function(scope, elm, attrs, tabCtrl) {
2304
+ scope.$watch('headingElement', function updateHeadingElement(heading) {
2305
+ if (heading) {
2306
+ elm.html('');
2307
+ elm.append(heading);
2308
+ }
2309
+ });
2310
+ }
2311
+ };
2312
+ }])
2313
+
2314
+ .directive('tabContentTransclude', function() {
2315
+ return {
2316
+ restrict: 'A',
2317
+ require: '^tabset',
2318
+ link: function(scope, elm, attrs) {
2319
+ var tab = scope.$eval(attrs.tabContentTransclude);
2320
+
2321
+ //Now our tab is ready to be transcluded: both the tab heading area
2322
+ //and the tab content area are loaded. Transclude 'em both.
2323
+ tab.$transcludeFn(tab.$parent, function(contents) {
2324
+ angular.forEach(contents, function(node) {
2325
+ if (isTabHeading(node)) {
2326
+ //Let tabHeadingTransclude know.
2327
+ tab.headingElement = node;
2328
+ } else {
2329
+ elm.append(node);
2330
+ }
2331
+ });
2332
+ });
2333
+ }
2334
+ };
2335
+ function isTabHeading(node) {
2336
+ return node.tagName && (
2337
+ node.hasAttribute('tab-heading') ||
2338
+ node.hasAttribute('data-tab-heading') ||
2339
+ node.tagName.toLowerCase() === 'tab-heading' ||
2340
+ node.tagName.toLowerCase() === 'data-tab-heading'
2341
+ );
2342
+ }
2343
+ })
2344
+
2345
+ ;
2346
+
2347
+
2348
+ angular.module("mm.foundation.topbar", [])
2349
+ .factory('mediaQueries', ['$document', '$window', function($document, $window){
2350
+ var head = angular.element($document[0].querySelector('head'));
2351
+ head.append('<meta class="foundation-mq-topbar" />');
2352
+ head.append('<meta class="foundation-mq-small" />');
2353
+ head.append('<meta class="foundation-mq-medium" />');
2354
+ head.append('<meta class="foundation-mq-large" />');
2355
+
2356
+ // MatchMedia for IE <= 9
2357
+ var matchMedia = $window.matchMedia || (function(doc, undefined){
2358
+ var bool,
2359
+ docElem = doc.documentElement,
2360
+ refNode = docElem.firstElementChild || docElem.firstChild,
2361
+ // fakeBody required for <FF4 when executed in <head>
2362
+ fakeBody = doc.createElement("body"),
2363
+ div = doc.createElement("div");
2364
+
2365
+ div.id = "mq-test-1";
2366
+ div.style.cssText = "position:absolute;top:-100em";
2367
+ fakeBody.style.background = "none";
2368
+ fakeBody.appendChild(div);
2369
+
2370
+ return function (q) {
2371
+ div.innerHTML = "&shy;<style media=\"" + q + "\"> #mq-test-1 { width: 42px; }</style>";
2372
+ docElem.insertBefore(fakeBody, refNode);
2373
+ bool = div.offsetWidth === 42;
2374
+ docElem.removeChild(fakeBody);
2375
+ return {
2376
+ matches: bool,
2377
+ media: q
2378
+ };
2379
+ };
2380
+
2381
+ }($document[0]));
2382
+
2383
+ var regex = /^[\/\\'"]+|(;\s?})+|[\/\\'"]+$/g;
2384
+ var queries = {
2385
+ topbar: getComputedStyle(head[0].querySelector('meta.foundation-mq-topbar')).fontFamily.replace(regex, ''),
2386
+ small : getComputedStyle(head[0].querySelector('meta.foundation-mq-small')).fontFamily.replace(regex, ''),
2387
+ medium : getComputedStyle(head[0].querySelector('meta.foundation-mq-medium')).fontFamily.replace(regex, ''),
2388
+ large : getComputedStyle(head[0].querySelector('meta.foundation-mq-large')).fontFamily.replace(regex, '')
2389
+ };
2390
+
2391
+ return {
2392
+ topbarBreakpoint: function () {
2393
+ return !matchMedia(queries.topbar).matches;
2394
+ },
2395
+ small: function () {
2396
+ return matchMedia(queries.small).matches;
2397
+ },
2398
+ medium: function () {
2399
+ return matchMedia(queries.medium).matches;
2400
+ },
2401
+ large: function () {
2402
+ return matchMedia(queries.large).matches;
2403
+ }
2404
+ };
2405
+
2406
+ }])
2407
+ .factory('closest', [function(){
2408
+ return function(el, selector) {
2409
+ var matchesSelector = function (node, selector) {
2410
+ var nodes = (node.parentNode || node.document).querySelectorAll(selector);
2411
+ var i = -1;
2412
+ while (nodes[++i] && nodes[i] != node){}
2413
+ return !!nodes[i];
2414
+ };
2415
+
2416
+ var element = el[0];
2417
+ while (element) {
2418
+ if (matchesSelector(element, selector)) {
2419
+ return angular.element(element);
2420
+ } else {
2421
+ element = element.parentElement;
2422
+ }
2423
+ }
2424
+ return false;
2425
+ };
2426
+ }])
2427
+ .directive('topBar', ['$timeout','$compile', '$window', '$document', 'mediaQueries',
2428
+ function ($timeout, $compile, $window, $document, mediaQueries) {
2429
+ return {
2430
+ scope: {
2431
+ stickyClass : '@',
2432
+ backText: '@',
2433
+ stickyOn : '=',
2434
+ customBackText: '=',
2435
+ isHover: '=',
2436
+ mobileShowParentLink: '=',
2437
+ scrolltop : '=',
2438
+ },
2439
+ restrict: 'EA',
2440
+ replace: true,
2441
+ templateUrl: 'template/topbar/top-bar.html',
2442
+ transclude: true,
2443
+ link: function ($scope, element, attrs) {
2444
+ var topbar = $scope.topbar = element;
2445
+ var topbarContainer = topbar.parent();
2446
+ var body = angular.element($document[0].querySelector('body'));
2447
+
2448
+ var isSticky = $scope.isSticky = function () {
2449
+ var sticky = topbarContainer.hasClass($scope.settings.stickyClass);
2450
+ if (sticky && $scope.settings.stickyOn === 'all') {
2451
+ return true;
2452
+ } else if (sticky && mediaQueries.small() && $scope.settings.stickyOn === 'small') {
2453
+ return true;
2454
+ } else if (sticky && mediaQueries.medium() && $scope.settings.stickyOn === 'medium') {
2455
+ return true;
2456
+ } else if (sticky && mediaQueries.large() && $scope.settings.stickyOn === 'large') {
2457
+ return true;
2458
+ }
2459
+ return false;
2460
+ };
2461
+
2462
+ var updateStickyPositioning = function(){
2463
+ if (!$scope.stickyTopbar || !$scope.isSticky()) {
2464
+ return;
2465
+ }
2466
+
2467
+ var $class = angular.element($document[0].querySelector('.' + $scope.settings.stickyClass));
2468
+ var distance = stickyoffset;
2469
+
2470
+ if ($window.scrollY > distance && !$class.hasClass('fixed')) {
2471
+ $class.addClass('fixed');
2472
+ body.css('padding-top', $scope.originalHeight + 'px');
2473
+ } else if ($window.scrollY <= distance && $class.hasClass('fixed')) {
2474
+ $class.removeClass('fixed');
2475
+ body.css('padding-top', '');
2476
+ }
2477
+ };
2478
+
2479
+ $scope.toggle = function(on) {
2480
+ if(!mediaQueries.topbarBreakpoint()){
2481
+ return false;
2482
+ }
2483
+
2484
+ var expand = (on === undefined) ? !topbar.hasClass('expanded') : on;
2485
+
2486
+ if (expand){
2487
+ topbar.addClass('expanded');
2488
+ }
2489
+ else {
2490
+ topbar.removeClass('expanded');
2491
+ }
2492
+
2493
+ if ($scope.settings.scrolltop) {
2494
+ if (!expand && topbar.hasClass('fixed')) {
2495
+ topbar.parent().addClass('fixed');
2496
+ topbar.removeClass('fixed');
2497
+ body.css('padding-top', $scope.originalHeight + 'px');
2498
+ } else if (expand && topbar.parent().hasClass('fixed')) {
2499
+ topbar.parent().removeClass('fixed');
2500
+ topbar.addClass('fixed');
2501
+ body.css('padding-top', '');
2502
+ $window.scrollTo(0,0);
2503
+ }
2504
+ } else {
2505
+ if(isSticky()) {
2506
+ topbar.parent().addClass('fixed');
2507
+ }
2508
+
2509
+ if(topbar.parent().hasClass('fixed')) {
2510
+ if (!expand) {
2511
+ topbar.removeClass('fixed');
2512
+ topbar.parent().removeClass('expanded');
2513
+ updateStickyPositioning();
2514
+ } else {
2515
+ topbar.addClass('fixed');
2516
+ topbar.parent().addClass('expanded');
2517
+ body.css('padding-top', $scope.originalHeight + 'px');
2518
+ }
2519
+ }
2520
+ }
2521
+ };
2522
+
2523
+ if(topbarContainer.hasClass('fixed') || isSticky() ) {
2524
+ $scope.stickyTopbar = true;
2525
+ $scope.height = topbarContainer[0].offsetHeight;
2526
+ var stickyoffset = topbarContainer[0].getBoundingClientRect().top;
2527
+ } else {
2528
+ $scope.height = topbar[0].offsetHeight;
2529
+ }
2530
+
2531
+ $scope.originalHeight = $scope.height;
2532
+
2533
+ $scope.$watch('height', function(h){
2534
+ if(h){
2535
+ topbar.css('height', h + 'px');
2536
+ } else {
2537
+ topbar.css('height', '');
2538
+ }
2539
+ });
2540
+
2541
+ var lastBreakpoint = mediaQueries.topbarBreakpoint();
2542
+
2543
+ angular.element($window).bind('resize', function(){
2544
+ var currentBreakpoint = mediaQueries.topbarBreakpoint();
2545
+ if(lastBreakpoint === currentBreakpoint){
2546
+ return;
2547
+ }
2548
+ lastBreakpoint = mediaQueries.topbarBreakpoint();
2549
+
2550
+ topbar.removeClass('expanded');
2551
+ topbar.parent().removeClass('expanded');
2552
+ $scope.height = '';
2553
+
2554
+ var sections = angular.element(topbar[0].querySelectorAll('section'));
2555
+ angular.forEach(sections, function(section){
2556
+ angular.element(section.querySelectorAll('li.moved')).removeClass('moved');
2557
+ });
2558
+
2559
+ $scope.$apply();
2560
+ });
2561
+
2562
+ // update sticky positioning
2563
+ angular.element($window).bind("scroll", function() {
2564
+ updateStickyPositioning();
2565
+ $scope.$apply();
2566
+ });
2567
+
2568
+ $scope.$on('$destroy', function(){
2569
+ angular.element($window).unbind("scroll");
2570
+ angular.element($window).unbind("resize");
2571
+ });
2572
+
2573
+ if (topbarContainer.hasClass('fixed')) {
2574
+ body.css('padding-top', $scope.originalHeight + 'px');
2575
+ }
2576
+
2577
+ },
2578
+ controller: ['$window', '$scope', 'closest', function($window, $scope, closest) {
2579
+ $scope.settings = {};
2580
+ $scope.settings.stickyClass = $scope.stickyClass || 'sticky';
2581
+ $scope.settings.backText = $scope.backText || 'Back';
2582
+ $scope.settings.stickyOn = $scope.stickyOn || 'all';
2583
+
2584
+ $scope.settings.customBackText = $scope.customBackText === undefined ? true : $scope.customBackText;
2585
+ $scope.settings.isHover = $scope.isHover === undefined ? true : $scope.isHover;
2586
+ $scope.settings.mobileShowParentLink = $scope.mobileShowParentLink === undefined ? true : $scope.mobileShowParentLink;
2587
+ $scope.settings.scrolltop = $scope.scrolltop === undefined ? true : $scope.scrolltop; // jump to top when sticky nav menu toggle is clicked
2588
+
2589
+ this.settings = $scope.settings;
2590
+
2591
+ $scope.index = 0;
2592
+
2593
+ var outerHeight = function(el){
2594
+ var height = el.offsetHeight;
2595
+ var style = el.currentStyle || getComputedStyle(el);
2596
+
2597
+ height += parseInt(style.marginTop, 10) + parseInt(style.marginBottom, 10);
2598
+ return height;
2599
+ };
2600
+
2601
+
2602
+ var sections = [];
2603
+
2604
+ this.addSection = function(section){
2605
+ sections.push(section);
2606
+ };
2607
+
2608
+ this.removeSection = function(section){
2609
+ var index = sections.indexOf(section);
2610
+ if (index > -1) {
2611
+ sections.splice(index, 1);
2612
+ }
2613
+ };
2614
+
2615
+ var dir = /rtl/i.test($document.find('html').attr('dir')) ? 'right' : 'left';
2616
+
2617
+ $scope.$watch('index', function(index){
2618
+ for(var i = 0; i < sections.length; i++){
2619
+ sections[i].move(dir, index);
2620
+ }
2621
+ });
2622
+
2623
+ this.toggle = function(on){
2624
+ $scope.toggle(on);
2625
+ for(var i = 0; i < sections.length; i++){
2626
+ sections[i].reset();
2627
+ }
2628
+ $scope.index = 0;
2629
+ $scope.height = '';
2630
+ $scope.$apply();
2631
+ };
2632
+
2633
+ this.back = function(event) {
2634
+ if($scope.index < 1 || !mediaQueries.topbarBreakpoint()){
2635
+ return;
2636
+ }
2637
+
2638
+ var $link = angular.element(event.currentTarget);
2639
+ var $movedLi = closest($link, 'li.moved');
2640
+ var $previousLevelUl = $movedLi.parent();
2641
+ $scope.index = $scope.index -1;
2642
+
2643
+ if($scope.index === 0){
2644
+ $scope.height = '';
2645
+ } else {
2646
+ $scope.height = $scope.originalHeight + outerHeight($previousLevelUl[0]);
2647
+ }
2648
+
2649
+ $timeout(function () {
2650
+ $movedLi.removeClass('moved');
2651
+ }, 300);
2652
+ };
2653
+
2654
+ this.forward = function(event) {
2655
+ if(!mediaQueries.topbarBreakpoint()){
2656
+ return false;
2657
+ }
2658
+
2659
+ var $link = angular.element(event.currentTarget);
2660
+ var $selectedLi = closest($link, 'li');
2661
+ $selectedLi.addClass('moved');
2662
+ $scope.height = $scope.originalHeight + outerHeight($link.parent()[0].querySelector('ul'));
2663
+ $scope.index = $scope.index + 1;
2664
+ $scope.$apply();
2665
+ };
2666
+
2667
+ }]
2668
+ };
2669
+ }])
2670
+ .directive('toggleTopBar', ['closest', function (closest) {
2671
+ return {
2672
+ scope: {},
2673
+ require: '^topBar',
2674
+ restrict: 'A',
2675
+ replace: true,
2676
+ templateUrl: 'template/topbar/toggle-top-bar.html',
2677
+ transclude: true,
2678
+ link: function ($scope, element, attrs, topBar) {
2679
+ element.bind('click', function(event) {
2680
+ var li = closest(angular.element(event.currentTarget), 'li');
2681
+ if(!li.hasClass('back') && !li.hasClass('has-dropdown')) {
2682
+ topBar.toggle();
2683
+ }
2684
+ });
2685
+
2686
+ $scope.$on('$destroy', function(){
2687
+ element.unbind('click');
2688
+ });
2689
+ }
2690
+ };
2691
+ }])
2692
+ .directive('topBarSection', ['$compile', 'closest', function ($compile, closest) {
2693
+ return {
2694
+ scope: {},
2695
+ require: '^topBar',
2696
+ restrict: 'EA',
2697
+ replace: true,
2698
+ templateUrl: 'template/topbar/top-bar-section.html',
2699
+ transclude: true,
2700
+ link: function ($scope, element, attrs, topBar) {
2701
+ var section = element;
2702
+
2703
+ $scope.reset = function(){
2704
+ angular.element(section[0].querySelectorAll('li.moved')).removeClass('moved');
2705
+ };
2706
+
2707
+ $scope.move = function(dir, index){
2708
+ if(dir === 'left'){
2709
+ section.css({"left": index * -100 + '%'});
2710
+ }
2711
+ else {
2712
+ section.css({"right": index * -100 + '%'});
2713
+ }
2714
+ };
2715
+
2716
+ topBar.addSection($scope);
2717
+
2718
+ $scope.$on("$destroy", function(){
2719
+ topBar.removeSection($scope);
2720
+ });
2721
+
2722
+ // Top level links close menu on click
2723
+ var links = section[0].querySelectorAll('li>a');
2724
+ angular.forEach(links, function(link){
2725
+ var $link = angular.element(link);
2726
+ var li = closest($link, 'li');
2727
+ if(li.hasClass('has-dropdown') || li.hasClass('back') || li.hasClass('title')){
2728
+ return;
2729
+ }
2730
+
2731
+ $link.bind('click', function(){
2732
+ topBar.toggle(false);
2733
+ });
2734
+
2735
+ $scope.$on('$destroy', function(){
2736
+ $link.bind('click');
2737
+ });
2738
+ });
2739
+
2740
+ }
2741
+ };
2742
+ }])
2743
+ .directive('hasDropdown', ['mediaQueries', function (mediaQueries) {
2744
+ return {
2745
+ scope: {},
2746
+ require: '^topBar',
2747
+ restrict: 'A',
2748
+ templateUrl: 'template/topbar/has-dropdown.html',
2749
+ replace: true,
2750
+ transclude: true,
2751
+ link: function ($scope, element, attrs, topBar) {
2752
+ $scope.triggerLink = element.children('a')[0];
2753
+
2754
+ var $link = angular.element($scope.triggerLink);
2755
+
2756
+ $link.bind('click', function(event){
2757
+ topBar.forward(event);
2758
+ });
2759
+ $scope.$on('$destroy', function(){
2760
+ $link.unbind('click');
2761
+ });
2762
+
2763
+ element.bind('mouseenter', function() {
2764
+ if(topBar.settings.isHover && !mediaQueries.topbarBreakpoint()){
2765
+ element.addClass('not-click');
2766
+ }
2767
+ });
2768
+ element.bind('click', function(event) {
2769
+ if(!topBar.settings.isHover && !mediaQueries.topbarBreakpoint()){
2770
+ element.toggleClass('not-click');
2771
+ }
2772
+ });
2773
+
2774
+ element.bind('mouseleave', function() {
2775
+ element.removeClass('not-click');
2776
+ });
2777
+
2778
+ $scope.$on('$destroy', function(){
2779
+ element.unbind('click');
2780
+ element.unbind('mouseenter');
2781
+ element.unbind('mouseleave');
2782
+ });
2783
+ },
2784
+ controller: ['$window', '$scope', function($window, $scope) {
2785
+ this.triggerLink = $scope.triggerLink;
2786
+ }]
2787
+ };
2788
+ }])
2789
+ .directive('topBarDropdown', ['$compile', function ($compile) {
2790
+ return {
2791
+ scope: {},
2792
+ require: ['^topBar', '^hasDropdown'],
2793
+ restrict: 'A',
2794
+ replace: true,
2795
+ templateUrl: 'template/topbar/top-bar-dropdown.html',
2796
+ transclude: true,
2797
+ link: function ($scope, element, attrs, ctrls) {
2798
+
2799
+ var topBar = ctrls[0];
2800
+ var hasDropdown = ctrls[1];
2801
+
2802
+ var $link = angular.element(hasDropdown.triggerLink);
2803
+ var url = $link.attr('href');
2804
+
2805
+ $scope.linkText = $link.text();
2806
+
2807
+ $scope.back = function(event){
2808
+ topBar.back(event);
2809
+ };
2810
+
2811
+ // Add back link
2812
+ if (topBar.settings.customBackText) {
2813
+ $scope.backText = topBar.settings.backText;
2814
+ } else {
2815
+ $scope.backText = '&laquo; ' + $link.html();
2816
+ }
2817
+
2818
+ var $titleLi;
2819
+ if (topBar.settings.mobileShowParentLink && url && url.length > 1) {
2820
+ $titleLi = angular.element('<li class="title back js-generated">' +
2821
+ '<h5><a href="#" ng-click="back($event);">{{backText}}</a></h5></li>' +
2822
+ '<li><a class="parent-link js-generated" href="' +
2823
+ url + '">{{linkText}}</a></li>');
2824
+ } else {
2825
+ $titleLi = angular.element('<li class="title back js-generated">' +
2826
+ '<h5><a href="" ng-click="back($event);">{{backText}}</a></h5></li>');
2827
+ }
2828
+
2829
+ $compile($titleLi)($scope);
2830
+ element.prepend($titleLi);
2831
+ }
2832
+ };
2833
+ }]);
2834
+
2835
+ angular.module( 'mm.foundation.tour', [ 'mm.foundation.position', 'mm.foundation.tooltip' ] )
2836
+
2837
+ .service( '$tour', [ '$window', function ( $window ) {
2838
+ var currentIndex = getCurrentStep();
2839
+ var ended = false;
2840
+ var steps = {};
2841
+
2842
+ function getCurrentStep() {
2843
+ return parseInt( $window.localStorage.getItem( 'mm.tour.step' ), 10 );
2844
+ }
2845
+
2846
+ function setCurrentStep(step) {
2847
+ currentIndex = step;
2848
+ $window.localStorage.setItem( 'mm.tour.step', step );
2849
+ }
2850
+
2851
+ this.add = function ( index, attrs ) {
2852
+ steps[ index ] = attrs;
2853
+ };
2854
+
2855
+ this.has = function ( index ) {
2856
+ return !!steps[ index ];
2857
+ };
2858
+
2859
+ this.isActive = function () {
2860
+ return currentIndex > 0;
2861
+ };
2862
+
2863
+ this.current = function ( index ) {
2864
+ if ( index ) {
2865
+ setCurrentStep( currentIndex );
2866
+ } else {
2867
+ return currentIndex;
2868
+ }
2869
+ };
2870
+
2871
+ this.start = function () {
2872
+ setCurrentStep( 1 );
2873
+ };
2874
+
2875
+ this.next = function () {
2876
+ setCurrentStep( currentIndex + 1 );
2877
+ };
2878
+
2879
+ this.end = function () {
2880
+ setCurrentStep( 0 );
2881
+ };
2882
+ }])
2883
+
2884
+ .directive( 'stepTextPopup', ['$tour', function ( $tour ) {
2885
+ return {
2886
+ restrict: 'EA',
2887
+ replace: true,
2888
+ scope: { title: '@', content: '@', placement: '@', animation: '&', isOpen: '&' },
2889
+ templateUrl: 'template/tour/tour.html',
2890
+ link: function (scope, element) {
2891
+ scope.isLastStep = function () {
2892
+ return !$tour.has( $tour.current() + 1 );
2893
+ };
2894
+
2895
+ scope.endTour = function () {
2896
+ element.remove();
2897
+ $tour.end();
2898
+ };
2899
+
2900
+ scope.nextStep = function () {
2901
+ element.remove();
2902
+ $tour.next();
2903
+ };
2904
+ }
2905
+ };
2906
+ }])
2907
+
2908
+ .directive( 'stepText', [ '$position', '$tooltip', '$tour', '$window', function ( $position, $tooltip, $tour, $window ) {
2909
+ function isElementInViewport( element ) {
2910
+ var rect = element[0].getBoundingClientRect();
2911
+
2912
+ return (
2913
+ rect.top >= 0 &&
2914
+ rect.left >= 0 &&
2915
+ rect.bottom <= ($window.innerHeight - 80) &&
2916
+ rect.right <= $window.innerWidth
2917
+ );
2918
+ }
2919
+
2920
+ function show( scope, element, attrs ) {
2921
+ var index = parseInt( attrs.stepIndex, 10);
2922
+
2923
+ if ( $tour.isActive() && index ) {
2924
+ $tour.add( index, attrs );
2925
+
2926
+ if ( index === $tour.current() ) {
2927
+ if ( !isElementInViewport( element ) ) {
2928
+ var offset = $position.offset( element );
2929
+ $window.scrollTo( 0, offset.top - $window.innerHeight / 2 );
2930
+ }
2931
+
2932
+ return true;
2933
+ }
2934
+ }
2935
+
2936
+ return false;
2937
+ }
2938
+
2939
+ return $tooltip( 'stepText', 'step', show );
2940
+ }]);
2941
+
2942
+ angular.module('mm.foundation.typeahead', ['mm.foundation.position', 'mm.foundation.bindHtml'])
2943
+
2944
+ /**
2945
+ * A helper service that can parse typeahead's syntax (string provided by users)
2946
+ * Extracted to a separate service for ease of unit testing
2947
+ */
2948
+ .factory('typeaheadParser', ['$parse', function ($parse) {
2949
+
2950
+ // 00000111000000000000022200000000000000003333333333333330000000000044000
2951
+ var TYPEAHEAD_REGEXP = /^\s*(.*?)(?:\s+as\s+(.*?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+(.*)$/;
2952
+
2953
+ return {
2954
+ parse:function (input) {
2955
+
2956
+ var match = input.match(TYPEAHEAD_REGEXP);
2957
+ if (!match) {
2958
+ throw new Error(
2959
+ "Expected typeahead specification in form of '_modelValue_ (as _label_)? for _item_ in _collection_'" +
2960
+ " but got '" + input + "'.");
2961
+ }
2962
+
2963
+ return {
2964
+ itemName:match[3],
2965
+ source:$parse(match[4]),
2966
+ viewMapper:$parse(match[2] || match[1]),
2967
+ modelMapper:$parse(match[1])
2968
+ };
2969
+ }
2970
+ };
2971
+ }])
2972
+
2973
+ .directive('typeahead', ['$compile', '$parse', '$q', '$timeout', '$document', '$position', 'typeaheadParser',
2974
+ function ($compile, $parse, $q, $timeout, $document, $position, typeaheadParser) {
2975
+
2976
+ var HOT_KEYS = [9, 13, 27, 38, 40];
2977
+
2978
+ return {
2979
+ require:'ngModel',
2980
+ link:function (originalScope, element, attrs, modelCtrl) {
2981
+
2982
+ //SUPPORTED ATTRIBUTES (OPTIONS)
2983
+
2984
+ //minimal no of characters that needs to be entered before typeahead kicks-in
2985
+ var minSearch = originalScope.$eval(attrs.typeaheadMinLength) || 1;
2986
+
2987
+ //minimal wait time after last character typed before typehead kicks-in
2988
+ var waitTime = originalScope.$eval(attrs.typeaheadWaitMs) || 0;
2989
+
2990
+ //should it restrict model values to the ones selected from the popup only?
2991
+ var isEditable = originalScope.$eval(attrs.typeaheadEditable) !== false;
2992
+
2993
+ //binding to a variable that indicates if matches are being retrieved asynchronously
2994
+ var isLoadingSetter = $parse(attrs.typeaheadLoading).assign || angular.noop;
2995
+
2996
+ //a callback executed when a match is selected
2997
+ var onSelectCallback = $parse(attrs.typeaheadOnSelect);
2998
+
2999
+ var inputFormatter = attrs.typeaheadInputFormatter ? $parse(attrs.typeaheadInputFormatter) : undefined;
3000
+
3001
+ var appendToBody = attrs.typeaheadAppendToBody ? $parse(attrs.typeaheadAppendToBody) : false;
3002
+
3003
+ //INTERNAL VARIABLES
3004
+
3005
+ //model setter executed upon match selection
3006
+ var $setModelValue = $parse(attrs.ngModel).assign;
3007
+
3008
+ //expressions used by typeahead
3009
+ var parserResult = typeaheadParser.parse(attrs.typeahead);
3010
+
3011
+ var hasFocus;
3012
+
3013
+ //pop-up element used to display matches
3014
+ var popUpEl = angular.element('<div typeahead-popup></div>');
3015
+ popUpEl.attr({
3016
+ matches: 'matches',
3017
+ active: 'activeIdx',
3018
+ select: 'select(activeIdx)',
3019
+ query: 'query',
3020
+ position: 'position'
3021
+ });
3022
+ //custom item template
3023
+ if (angular.isDefined(attrs.typeaheadTemplateUrl)) {
3024
+ popUpEl.attr('template-url', attrs.typeaheadTemplateUrl);
3025
+ }
3026
+
3027
+ //create a child scope for the typeahead directive so we are not polluting original scope
3028
+ //with typeahead-specific data (matches, query etc.)
3029
+ var scope = originalScope.$new();
3030
+ originalScope.$on('$destroy', function(){
3031
+ scope.$destroy();
3032
+ });
3033
+
3034
+ var resetMatches = function() {
3035
+ scope.matches = [];
3036
+ scope.activeIdx = -1;
3037
+ };
3038
+
3039
+ var getMatchesAsync = function(inputValue) {
3040
+
3041
+ var locals = {$viewValue: inputValue};
3042
+ isLoadingSetter(originalScope, true);
3043
+ $q.when(parserResult.source(originalScope, locals)).then(function(matches) {
3044
+
3045
+ //it might happen that several async queries were in progress if a user were typing fast
3046
+ //but we are interested only in responses that correspond to the current view value
3047
+ if (inputValue === modelCtrl.$viewValue && hasFocus) {
3048
+ if (matches.length > 0) {
3049
+
3050
+ scope.activeIdx = 0;
3051
+ scope.matches.length = 0;
3052
+
3053
+ //transform labels
3054
+ for(var i=0; i<matches.length; i++) {
3055
+ locals[parserResult.itemName] = matches[i];
3056
+ scope.matches.push({
3057
+ label: parserResult.viewMapper(scope, locals),
3058
+ model: matches[i]
3059
+ });
3060
+ }
3061
+
3062
+ scope.query = inputValue;
3063
+ //position pop-up with matches - we need to re-calculate its position each time we are opening a window
3064
+ //with matches as a pop-up might be absolute-positioned and position of an input might have changed on a page
3065
+ //due to other elements being rendered
3066
+ scope.position = appendToBody ? $position.offset(element) : $position.position(element);
3067
+ scope.position.top = scope.position.top + element.prop('offsetHeight');
3068
+
3069
+ } else {
3070
+ resetMatches();
3071
+ }
3072
+ isLoadingSetter(originalScope, false);
3073
+ }
3074
+ }, function(){
3075
+ resetMatches();
3076
+ isLoadingSetter(originalScope, false);
3077
+ });
3078
+ };
3079
+
3080
+ resetMatches();
3081
+
3082
+ //we need to propagate user's query so we can higlight matches
3083
+ scope.query = undefined;
3084
+
3085
+ //Declare the timeout promise var outside the function scope so that stacked calls can be cancelled later
3086
+ var timeoutPromise;
3087
+
3088
+ //plug into $parsers pipeline to open a typeahead on view changes initiated from DOM
3089
+ //$parsers kick-in on all the changes coming from the view as well as manually triggered by $setViewValue
3090
+ modelCtrl.$parsers.unshift(function (inputValue) {
3091
+
3092
+ if (inputValue && inputValue.length >= minSearch) {
3093
+ if (waitTime > 0) {
3094
+ if (timeoutPromise) {
3095
+ $timeout.cancel(timeoutPromise);//cancel previous timeout
3096
+ }
3097
+ timeoutPromise = $timeout(function () {
3098
+ getMatchesAsync(inputValue);
3099
+ }, waitTime);
3100
+ } else {
3101
+ getMatchesAsync(inputValue);
3102
+ }
3103
+ } else {
3104
+ isLoadingSetter(originalScope, false);
3105
+ resetMatches();
3106
+ }
3107
+
3108
+ if (isEditable) {
3109
+ return inputValue;
3110
+ } else {
3111
+ if (!inputValue) {
3112
+ // Reset in case user had typed something previously.
3113
+ modelCtrl.$setValidity('editable', true);
3114
+ return inputValue;
3115
+ } else {
3116
+ modelCtrl.$setValidity('editable', false);
3117
+ return undefined;
3118
+ }
3119
+ }
3120
+ });
3121
+
3122
+ modelCtrl.$formatters.push(function (modelValue) {
3123
+
3124
+ var candidateViewValue, emptyViewValue;
3125
+ var locals = {};
3126
+
3127
+ if (inputFormatter) {
3128
+
3129
+ locals['$model'] = modelValue;
3130
+ return inputFormatter(originalScope, locals);
3131
+
3132
+ } else {
3133
+
3134
+ //it might happen that we don't have enough info to properly render input value
3135
+ //we need to check for this situation and simply return model value if we can't apply custom formatting
3136
+ locals[parserResult.itemName] = modelValue;
3137
+ candidateViewValue = parserResult.viewMapper(originalScope, locals);
3138
+ locals[parserResult.itemName] = undefined;
3139
+ emptyViewValue = parserResult.viewMapper(originalScope, locals);
3140
+
3141
+ return candidateViewValue!== emptyViewValue ? candidateViewValue : modelValue;
3142
+ }
3143
+ });
3144
+
3145
+ scope.select = function (activeIdx) {
3146
+ //called from within the $digest() cycle
3147
+ var locals = {};
3148
+ var model, item;
3149
+
3150
+ locals[parserResult.itemName] = item = scope.matches[activeIdx].model;
3151
+ model = parserResult.modelMapper(originalScope, locals);
3152
+ $setModelValue(originalScope, model);
3153
+ modelCtrl.$setValidity('editable', true);
3154
+
3155
+ onSelectCallback(originalScope, {
3156
+ $item: item,
3157
+ $model: model,
3158
+ $label: parserResult.viewMapper(originalScope, locals)
3159
+ });
3160
+
3161
+ resetMatches();
3162
+
3163
+ //return focus to the input element if a mach was selected via a mouse click event
3164
+ element[0].focus();
3165
+ };
3166
+
3167
+ //bind keyboard events: arrows up(38) / down(40), enter(13) and tab(9), esc(27)
3168
+ element.bind('keydown', function (evt) {
3169
+
3170
+ //typeahead is open and an "interesting" key was pressed
3171
+ if (scope.matches.length === 0 || HOT_KEYS.indexOf(evt.which) === -1) {
3172
+ return;
3173
+ }
3174
+
3175
+ evt.preventDefault();
3176
+
3177
+ if (evt.which === 40) {
3178
+ scope.activeIdx = (scope.activeIdx + 1) % scope.matches.length;
3179
+ scope.$digest();
3180
+
3181
+ } else if (evt.which === 38) {
3182
+ scope.activeIdx = (scope.activeIdx ? scope.activeIdx : scope.matches.length) - 1;
3183
+ scope.$digest();
3184
+
3185
+ } else if (evt.which === 13 || evt.which === 9) {
3186
+ scope.$apply(function () {
3187
+ scope.select(scope.activeIdx);
3188
+ });
3189
+
3190
+ } else if (evt.which === 27) {
3191
+ evt.stopPropagation();
3192
+
3193
+ resetMatches();
3194
+ scope.$digest();
3195
+ }
3196
+ });
3197
+
3198
+ element.bind('blur', function (evt) {
3199
+ hasFocus = false;
3200
+ });
3201
+
3202
+ element.bind('focus', function (evt) {
3203
+ hasFocus = true;
3204
+ });
3205
+
3206
+ // Keep reference to click handler to unbind it.
3207
+ var dismissClickHandler = function (evt) {
3208
+ if (element[0] !== evt.target) {
3209
+ resetMatches();
3210
+ scope.$digest();
3211
+ }
3212
+ };
3213
+
3214
+ $document.bind('click', dismissClickHandler);
3215
+
3216
+ originalScope.$on('$destroy', function(){
3217
+ $document.unbind('click', dismissClickHandler);
3218
+ });
3219
+
3220
+ var $popup = $compile(popUpEl)(scope);
3221
+ if ( appendToBody ) {
3222
+ $document.find('body').append($popup);
3223
+ } else {
3224
+ element.after($popup);
3225
+ }
3226
+ }
3227
+ };
3228
+
3229
+ }])
3230
+
3231
+ .directive('typeaheadPopup', function () {
3232
+ return {
3233
+ restrict:'EA',
3234
+ scope:{
3235
+ matches:'=',
3236
+ query:'=',
3237
+ active:'=',
3238
+ position:'=',
3239
+ select:'&'
3240
+ },
3241
+ replace:true,
3242
+ templateUrl:'template/typeahead/typeahead-popup.html',
3243
+ link:function (scope, element, attrs) {
3244
+
3245
+ scope.templateUrl = attrs.templateUrl;
3246
+
3247
+ scope.isOpen = function () {
3248
+ return scope.matches.length > 0;
3249
+ };
3250
+
3251
+ scope.isActive = function (matchIdx) {
3252
+ return scope.active == matchIdx;
3253
+ };
3254
+
3255
+ scope.selectActive = function (matchIdx) {
3256
+ scope.active = matchIdx;
3257
+ };
3258
+
3259
+ scope.selectMatch = function (activeIdx) {
3260
+ scope.select({activeIdx:activeIdx});
3261
+ };
3262
+ }
3263
+ };
3264
+ })
3265
+
3266
+ .directive('typeaheadMatch', ['$http', '$templateCache', '$compile', '$parse', function ($http, $templateCache, $compile, $parse) {
3267
+ return {
3268
+ restrict:'EA',
3269
+ scope:{
3270
+ index:'=',
3271
+ match:'=',
3272
+ query:'='
3273
+ },
3274
+ link:function (scope, element, attrs) {
3275
+ var tplUrl = $parse(attrs.templateUrl)(scope.$parent) || 'template/typeahead/typeahead-match.html';
3276
+ $http.get(tplUrl, {cache: $templateCache}).success(function(tplContent){
3277
+ element.replaceWith($compile(tplContent.trim())(scope));
3278
+ });
3279
+ }
3280
+ };
3281
+ }])
3282
+
3283
+ .filter('typeaheadHighlight', function() {
3284
+
3285
+ function escapeRegexp(queryToEscape) {
3286
+ return queryToEscape.replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1");
3287
+ }
3288
+
3289
+ return function(matchItem, query) {
3290
+ return query ? matchItem.replace(new RegExp(escapeRegexp(query), 'gi'), '<strong>$&</strong>') : matchItem;
3291
+ };
3292
+ });
3293
+
3294
+ angular.module("template/accordion/accordion-group.html", []).run(["$templateCache", function($templateCache) {
3295
+ $templateCache.put("template/accordion/accordion-group.html",
3296
+ "<dd>\n" +
3297
+ " <a ng-click=\"isOpen = !isOpen\" accordion-transclude=\"heading\">{{heading}}</a>\n" +
3298
+ " <div class=\"content\" ng-style=\"isOpen ? {display: 'block'} : {}\" ng-transclude></div>\n" +
3299
+ "</dd>\n" +
3300
+ "");
3301
+ }]);
3302
+
3303
+ angular.module("template/accordion/accordion.html", []).run(["$templateCache", function($templateCache) {
3304
+ $templateCache.put("template/accordion/accordion.html",
3305
+ "<dl class=\"accordion\" ng-transclude></dl>\n" +
3306
+ "");
3307
+ }]);
3308
+
3309
+ angular.module("template/alert/alert.html", []).run(["$templateCache", function($templateCache) {
3310
+ $templateCache.put("template/alert/alert.html",
3311
+ "<div class='alert-box' ng-class='(type || \"\")'>\n" +
3312
+ " <span ng-transclude></span>\n" +
3313
+ " <a ng-show='closeable' class='close' ng-click='close()'>&times;</a>\n" +
3314
+ "</div>\n" +
3315
+ "");
3316
+ }]);
3317
+
3318
+ angular.module("template/modal/backdrop.html", []).run(["$templateCache", function($templateCache) {
3319
+ $templateCache.put("template/modal/backdrop.html",
3320
+ "<div class=\"reveal-modal-bg fade\" ng-class=\"{in: animate}\" ng-click=\"close($event)\" style=\"display: block\"></div>\n" +
3321
+ "");
3322
+ }]);
3323
+
3324
+ angular.module("template/modal/window.html", []).run(["$templateCache", function($templateCache) {
3325
+ $templateCache.put("template/modal/window.html",
3326
+ "<div tabindex=\"-1\" class=\"reveal-modal fade {{ windowClass }}\"\n" +
3327
+ " ng-class=\"{in: animate}\" ng-click=\"close($event)\"\n" +
3328
+ " style=\"display: block; position: fixed; visibility: visible\">\n" +
3329
+ " <div ng-transclude></div>\n" +
3330
+ "</div>\n" +
3331
+ "");
3332
+ }]);
3333
+
3334
+ angular.module("template/pagination/pager.html", []).run(["$templateCache", function($templateCache) {
3335
+ $templateCache.put("template/pagination/pager.html",
3336
+ "<ul class=\"pagination\">\n" +
3337
+ " <li ng-repeat=\"page in pages\" class=\"arrow\" ng-class=\"{unavailable: page.disabled, left: page.previous, right: page.next}\"><a ng-click=\"selectPage(page.number)\">{{page.text}}</a></li>\n" +
3338
+ "</ul>\n" +
3339
+ "");
3340
+ }]);
3341
+
3342
+ angular.module("template/pagination/pagination.html", []).run(["$templateCache", function($templateCache) {
3343
+ $templateCache.put("template/pagination/pagination.html",
3344
+ "<ul class=\"pagination\">\n" +
3345
+ " <li ng-repeat=\"page in pages\" ng-class=\"{arrow: $first || $last, current: page.active, unavailable: page.disabled}\"><a ng-click=\"selectPage(page.number)\">{{page.text}}</a></li>\n" +
3346
+ "</ul>\n" +
3347
+ "");
3348
+ }]);
3349
+
3350
+ angular.module("template/tooltip/tooltip-html-unsafe-popup.html", []).run(["$templateCache", function($templateCache) {
3351
+ $templateCache.put("template/tooltip/tooltip-html-unsafe-popup.html",
3352
+ "<span class=\"tooltip tip-{{placement}}\"\n" +
3353
+ " ng-class=\"{ in: isOpen(), fade: animation() }\"\n" +
3354
+ " style=\"width: auto\">\n" +
3355
+ " <span bind-html-unsafe=\"content\"></span>\n" +
3356
+ " <span class=\"nub\"></span>\n" +
3357
+ "</span>\n" +
3358
+ "");
3359
+ }]);
3360
+
3361
+ angular.module("template/tooltip/tooltip-popup.html", []).run(["$templateCache", function($templateCache) {
3362
+ $templateCache.put("template/tooltip/tooltip-popup.html",
3363
+ "<span class=\"tooltip tip-{{placement}}\"\n" +
3364
+ " ng-class=\"{ in: isOpen(), fade: animation() }\"\n" +
3365
+ " style=\"width: auto\">\n" +
3366
+ " <span ng-bind=\"content\"></span>\n" +
3367
+ " <span class=\"nub\"></span>\n" +
3368
+ "</span>\n" +
3369
+ "");
3370
+ }]);
3371
+
3372
+ angular.module("template/popover/popover.html", []).run(["$templateCache", function($templateCache) {
3373
+ $templateCache.put("template/popover/popover.html",
3374
+ "<div class=\"joyride-tip-guide\" ng-class=\"{ in: isOpen(), fade: animation() }\">\n" +
3375
+ " <span class=\"joyride-nub\" ng-class=\"{\n" +
3376
+ " bottom: placement === 'top',\n" +
3377
+ " left: placement === 'right',\n" +
3378
+ " right: placement === 'left',\n" +
3379
+ " top: placement === 'bottom'\n" +
3380
+ " }\"></span>\n" +
3381
+ " <div class=\"joyride-content-wrapper\">\n" +
3382
+ " <h4 ng-bind=\"title\" ng-show=\"title\"></h4>\n" +
3383
+ " <p ng-bind=\"content\"></p>\n" +
3384
+ " </div>\n" +
3385
+ "</div>\n" +
3386
+ "");
3387
+ }]);
3388
+
3389
+ angular.module("template/progressbar/bar.html", []).run(["$templateCache", function($templateCache) {
3390
+ $templateCache.put("template/progressbar/bar.html",
3391
+ "<span class=\"meter\" ng-transclude></span>\n" +
3392
+ "");
3393
+ }]);
3394
+
3395
+ angular.module("template/progressbar/progress.html", []).run(["$templateCache", function($templateCache) {
3396
+ $templateCache.put("template/progressbar/progress.html",
3397
+ "<div class=\"progress\" ng-class=\"type\" ng-transclude></div>\n" +
3398
+ "");
3399
+ }]);
3400
+
3401
+ angular.module("template/progressbar/progressbar.html", []).run(["$templateCache", function($templateCache) {
3402
+ $templateCache.put("template/progressbar/progressbar.html",
3403
+ "<div class=\"progress\" ng-class=\"type\">\n" +
3404
+ " <span class=\"meter\" ng-transclude></span>\n" +
3405
+ "</div>\n" +
3406
+ "");
3407
+ }]);
3408
+
3409
+ angular.module("template/rating/rating.html", []).run(["$templateCache", function($templateCache) {
3410
+ $templateCache.put("template/rating/rating.html",
3411
+ "<span ng-mouseleave=\"reset()\">\n" +
3412
+ " <i ng-repeat=\"r in range\" ng-mouseenter=\"enter($index + 1)\" ng-click=\"rate($index + 1)\" class=\"fa\"\n" +
3413
+ " ng-class=\"$index < val && (r.stateOn || 'fa-star') || (r.stateOff || 'fa-star-o')\"></i>\n" +
3414
+ "</span>\n" +
3415
+ "");
3416
+ }]);
3417
+
3418
+ angular.module("template/tabs/tab.html", []).run(["$templateCache", function($templateCache) {
3419
+ $templateCache.put("template/tabs/tab.html",
3420
+ "<dd ng-class=\"{active: active}\">\n" +
3421
+ " <a ng-click=\"select()\" tab-heading-transclude>{{heading}}</a>\n" +
3422
+ "</dd>\n" +
3423
+ "");
3424
+ }]);
3425
+
3426
+ angular.module("template/tabs/tabset.html", []).run(["$templateCache", function($templateCache) {
3427
+ $templateCache.put("template/tabs/tabset.html",
3428
+ "<div class=\"tabbable\">\n" +
3429
+ " <dl class=\"tabs\" ng-class=\"{'vertical': vertical}\" ng-transclude></dl>\n" +
3430
+ " <div class=\"tabs-content\" ng-class=\"{'vertical': vertical}\">\n" +
3431
+ " <div class=\"content\" \n" +
3432
+ " ng-repeat=\"tab in tabs\" \n" +
3433
+ " ng-class=\"{active: tab.active}\">\n" +
3434
+ " <div tab-content-transclude=\"tab\"></div>\n" +
3435
+ " </div>\n" +
3436
+ " </div>\n" +
3437
+ "</div>\n" +
3438
+ "");
3439
+ }]);
3440
+
3441
+ angular.module("template/topbar/has-dropdown.html", []).run(["$templateCache", function($templateCache) {
3442
+ $templateCache.put("template/topbar/has-dropdown.html",
3443
+ "<li class=\"has-dropdown\" ng-transclude></li>");
3444
+ }]);
3445
+
3446
+ angular.module("template/topbar/toggle-top-bar.html", []).run(["$templateCache", function($templateCache) {
3447
+ $templateCache.put("template/topbar/toggle-top-bar.html",
3448
+ "<li class=\"toggle-topbar menu-icon\" ng-transclude></li>");
3449
+ }]);
3450
+
3451
+ angular.module("template/topbar/top-bar-dropdown.html", []).run(["$templateCache", function($templateCache) {
3452
+ $templateCache.put("template/topbar/top-bar-dropdown.html",
3453
+ "<ul class=\"dropdown\" ng-transclude></ul>");
3454
+ }]);
3455
+
3456
+ angular.module("template/topbar/top-bar-section.html", []).run(["$templateCache", function($templateCache) {
3457
+ $templateCache.put("template/topbar/top-bar-section.html",
3458
+ "<section class=\"top-bar-section\" ng-transclude></section>");
3459
+ }]);
3460
+
3461
+ angular.module("template/topbar/top-bar.html", []).run(["$templateCache", function($templateCache) {
3462
+ $templateCache.put("template/topbar/top-bar.html",
3463
+ "<nav class=\"top-bar\" ng-transclude></nav>");
3464
+ }]);
3465
+
3466
+ angular.module("template/tour/tour.html", []).run(["$templateCache", function($templateCache) {
3467
+ $templateCache.put("template/tour/tour.html",
3468
+ "<div class=\"joyride-tip-guide\" ng-class=\"{ in: isOpen(), fade: animation() }\">\n" +
3469
+ " <span class=\"joyride-nub\" ng-class=\"{\n" +
3470
+ " bottom: placement === 'top',\n" +
3471
+ " left: placement === 'right',\n" +
3472
+ " right: placement === 'left',\n" +
3473
+ " top: placement === 'bottom'\n" +
3474
+ " }\"></span>\n" +
3475
+ " <div class=\"joyride-content-wrapper\">\n" +
3476
+ " <h4 ng-bind=\"title\" ng-show=\"title\"></h4>\n" +
3477
+ " <p ng-bind=\"content\"></p>\n" +
3478
+ " <a class=\"small button joyride-next-tip\" ng-show=\"!isLastStep()\" ng-click=\"nextStep()\">Next</a>\n" +
3479
+ " <a class=\"small button joyride-next-tip\" ng-show=\"isLastStep()\" ng-click=\"endTour()\">End</a>\n" +
3480
+ " <a class=\"joyride-close-tip\" ng-click=\"endTour()\">&times;</a>\n" +
3481
+ " </div>\n" +
3482
+ "</div>\n" +
3483
+ "");
3484
+ }]);
3485
+
3486
+ angular.module("template/typeahead/typeahead-match.html", []).run(["$templateCache", function($templateCache) {
3487
+ $templateCache.put("template/typeahead/typeahead-match.html",
3488
+ "<a tabindex=\"-1\" bind-html-unsafe=\"match.label | typeaheadHighlight:query\"></a>");
3489
+ }]);
3490
+
3491
+ angular.module("template/typeahead/typeahead-popup.html", []).run(["$templateCache", function($templateCache) {
3492
+ $templateCache.put("template/typeahead/typeahead-popup.html",
3493
+ "<ul class=\"f-dropdown\" ng-style=\"{display: isOpen()&&'block' || 'none', top: position.top+'px', left: position.left+'px'}\">\n" +
3494
+ " <li ng-repeat=\"match in matches\" ng-class=\"{active: isActive($index) }\" ng-mouseenter=\"selectActive($index)\" ng-click=\"selectMatch($index)\">\n" +
3495
+ " <div typeahead-match index=\"$index\" match=\"match\" query=\"query\" template-url=\"templateUrl\"></div>\n" +
3496
+ " </li>\n" +
3497
+ "</ul>\n" +
3498
+ "");
3499
+ }]);