angularjs-foundation-rails 0.3.1.2

Sign up to get free protection for your applications and to get access to all the features.
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
+ }]);