diff --git a/mm-foundation-0.9.2.js b/mm-foundation-0.9.2.js
new file mode 100644
index 0000000..dddb6bc
--- /dev/null
+++ b/mm-foundation-0.9.2.js
@@ -0,0 +1,3411 @@
+/*
+ * angular-mm-foundation
+ * http://pineconellc.github.io/angular-foundation/
+
+ * Version: 0.9.2 - 2016-05-16
+ * License: MIT
+ * (c) Pinecone, LLC
+ */
+angular.module("mm.foundation", ["mm.foundation.accordion","mm.foundation.alert","mm.foundation.bindHtml","mm.foundation.buttons","mm.foundation.position","mm.foundation.mediaQueries","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"]);
+angular.module('mm.foundation.accordion', [])
+
+.constant('accordionConfig', {
+ closeOthers: true
+})
+
+.controller('AccordionController', ['$scope', '$attrs', 'accordionConfig', function ($scope, $attrs, accordionConfig) {
+
+ // This array keeps track of the accordion groups
+ this.groups = [];
+
+ // Ensure that all the groups in this accordion are closed, unless close-others explicitly says not to
+ this.closeOthers = function(openGroup) {
+ var closeOthers = angular.isDefined($attrs.closeOthers) ? $scope.$eval($attrs.closeOthers) : accordionConfig.closeOthers;
+ if ( closeOthers ) {
+ angular.forEach(this.groups, function (group) {
+ if ( group !== openGroup ) {
+ group.isOpen = false;
+ }
+ });
+ }
+ };
+
+ // This is called from the accordion-group directive to add itself to the accordion
+ this.addGroup = function(groupScope) {
+ var that = this;
+ this.groups.push(groupScope);
+
+ groupScope.$on('$destroy', function (event) {
+ that.removeGroup(groupScope);
+ });
+ };
+
+ // This is called from the accordion-group directive when to remove itself
+ this.removeGroup = function(group) {
+ var index = this.groups.indexOf(group);
+ if ( index !== -1 ) {
+ this.groups.splice(index, 1);
+ }
+ };
+
+}])
+
+// The accordion directive simply sets up the directive controller
+// and adds an accordion CSS class to itself element.
+.directive('accordion', function () {
+ return {
+ restrict:'EA',
+ controller:'AccordionController',
+ transclude: true,
+ replace: false,
+ templateUrl: 'template/accordion/accordion.html'
+ };
+})
+
+// The accordion-group directive indicates a block of html that will expand and collapse in an accordion
+.directive('accordionGroup', ['$parse', function($parse) {
+ return {
+ require:'^accordion', // We need this directive to be inside an accordion
+ restrict:'EA',
+ transclude:true, // It transcludes the contents of the directive into the template
+ replace: true, // The element containing the directive will be replaced with the template
+ templateUrl:'template/accordion/accordion-group.html',
+ scope: { // Create an isolated scope and interpolate the heading attribute onto this scope
+ heading: '@',
+ cbOpen: '&toggleOpen'
+ },
+ controller: function() {
+ this.setHeading = function(element) {
+ this.heading = element;
+ };
+ },
+ link: function(scope, element, attrs, accordionCtrl) {
+ var getIsOpen, setIsOpen;
+
+ accordionCtrl.addGroup(scope);
+
+ scope.isOpen = false;
+
+ if ( attrs.isOpen ) {
+ getIsOpen = $parse(attrs.isOpen);
+ setIsOpen = getIsOpen.assign;
+
+ scope.$parent.$watch(getIsOpen, function(value) {
+ scope.isOpen = !!value;
+ });
+ }
+
+ scope.$watch('isOpen', function(value) {
+ if ( value ) {
+ accordionCtrl.closeOthers(scope);
+ scope.cbOpen();
+ }
+ if ( setIsOpen ) {
+ setIsOpen(scope.$parent, value);
+ }
+ });
+ }
+ };
+}])
+
+// Use accordion-heading below an accordion-group to provide a heading containing HTML
+//
+// Heading containing HTML -
+//
+.directive('accordionHeading', function() {
+ return {
+ restrict: 'EA',
+ transclude: true, // Grab the contents to be used as the heading
+ template: '', // In effect remove this element!
+ replace: true,
+ require: '^accordionGroup',
+ compile: function(element, attr, transclude) {
+ return function link(scope, element, attr, accordionGroupCtrl) {
+ // Pass the heading to the accordion-group controller
+ // so that it can be transcluded into the right place in the template
+ // [The second parameter to transclude causes the elements to be cloned so that they work in ng-repeat]
+ accordionGroupCtrl.setHeading(transclude(scope, function() {}));
+ };
+ }
+ };
+})
+
+// Use in the accordion-group template to indicate where you want the heading to be transcluded
+// You must provide the property on the accordion-group controller that will hold the transcluded element
+//
+.directive('accordionTransclude', function() {
+ return {
+ require: '^accordionGroup',
+ link: function(scope, element, attr, controller) {
+ scope.$watch(function() { return controller[attr.accordionTransclude]; }, function(heading) {
+ if ( heading ) {
+ element.html('');
+ element.append(heading);
+ }
+ });
+ }
+ };
+});
+
+angular.module("mm.foundation.alert", [])
+
+.controller('AlertController', ['$scope', '$attrs', function ($scope, $attrs) {
+ $scope.closeable = 'close' in $attrs && typeof $attrs.close !== "undefined";
+}])
+
+.directive('alert', function () {
+ return {
+ restrict:'EA',
+ controller:'AlertController',
+ templateUrl:'template/alert/alert.html',
+ transclude:true,
+ replace:true,
+ scope: {
+ type: '=',
+ close: '&'
+ }
+ };
+});
+
+angular.module('mm.foundation.bindHtml', [])
+
+ .directive('bindHtmlUnsafe', function () {
+ return function (scope, element, attr) {
+ element.addClass('ng-binding').data('$binding', attr.bindHtmlUnsafe);
+ scope.$watch(attr.bindHtmlUnsafe, function bindHtmlUnsafeWatchAction(value) {
+ element.html(value || '');
+ });
+ };
+ });
+
+angular.module('mm.foundation.buttons', [])
+
+.constant('buttonConfig', {
+ activeClass: 'active',
+ toggleEvent: 'click'
+})
+
+.controller('ButtonsController', ['buttonConfig', function(buttonConfig) {
+ this.activeClass = buttonConfig.activeClass;
+ this.toggleEvent = buttonConfig.toggleEvent;
+}])
+
+.directive('btnRadio', function () {
+ return {
+ require: ['btnRadio', 'ngModel'],
+ controller: 'ButtonsController',
+ link: function (scope, element, attrs, ctrls) {
+ var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1];
+
+ //model -> UI
+ ngModelCtrl.$render = function () {
+ element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, scope.$eval(attrs.btnRadio)));
+ };
+
+ //ui->model
+ element.bind(buttonsCtrl.toggleEvent, function () {
+ if (!element.hasClass(buttonsCtrl.activeClass)) {
+ scope.$apply(function () {
+ ngModelCtrl.$setViewValue(scope.$eval(attrs.btnRadio));
+ ngModelCtrl.$render();
+ });
+ }
+ });
+ }
+ };
+})
+
+.directive('btnCheckbox', function () {
+ return {
+ require: ['btnCheckbox', 'ngModel'],
+ controller: 'ButtonsController',
+ link: function (scope, element, attrs, ctrls) {
+ var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1];
+
+ function getTrueValue() {
+ return getCheckboxValue(attrs.btnCheckboxTrue, true);
+ }
+
+ function getFalseValue() {
+ return getCheckboxValue(attrs.btnCheckboxFalse, false);
+ }
+
+ function getCheckboxValue(attributeValue, defaultValue) {
+ var val = scope.$eval(attributeValue);
+ return angular.isDefined(val) ? val : defaultValue;
+ }
+
+ //model -> UI
+ ngModelCtrl.$render = function () {
+ element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, getTrueValue()));
+ };
+
+ //ui->model
+ element.bind(buttonsCtrl.toggleEvent, function () {
+ scope.$apply(function () {
+ ngModelCtrl.$setViewValue(element.hasClass(buttonsCtrl.activeClass) ? getFalseValue() : getTrueValue());
+ ngModelCtrl.$render();
+ });
+ });
+ }
+ };
+});
+
+angular.module('mm.foundation.position', [])
+
+/**
+ * A set of utility methods that can be use to retrieve position of DOM elements.
+ * It is meant to be used where we need to absolute-position DOM elements in
+ * relation to other, existing elements (this is the case for tooltips, popovers,
+ * typeahead suggestions etc.).
+ */
+ .factory('$position', ['$document', '$window', function ($document, $window) {
+
+ function getStyle(el, cssprop) {
+ if (el.currentStyle) { //IE
+ return el.currentStyle[cssprop];
+ } else if ($window.getComputedStyle) {
+ return $window.getComputedStyle(el)[cssprop];
+ }
+ // finally try and get inline style
+ return el.style[cssprop];
+ }
+
+ /**
+ * Checks if a given element is statically positioned
+ * @param element - raw DOM element
+ */
+ function isStaticPositioned(element) {
+ return (getStyle(element, "position") || 'static' ) === 'static';
+ }
+
+ /**
+ * returns the closest, non-statically positioned parentOffset of a given element
+ * @param element
+ */
+ var parentOffsetEl = function (element) {
+ var docDomEl = $document[0];
+ var offsetParent = element.offsetParent || docDomEl;
+ while (offsetParent && offsetParent !== docDomEl && isStaticPositioned(offsetParent) ) {
+ offsetParent = offsetParent.offsetParent;
+ }
+ return offsetParent || docDomEl;
+ };
+
+ return {
+ /**
+ * Provides read-only equivalent of jQuery's position function:
+ * http://api.jquery.com/position/
+ */
+ position: function (element) {
+ var elBCR = this.offset(element);
+ var offsetParentBCR = { top: 0, left: 0 };
+ var offsetParentEl = parentOffsetEl(element[0]);
+ if (offsetParentEl != $document[0]) {
+ offsetParentBCR = this.offset(angular.element(offsetParentEl));
+ offsetParentBCR.top += offsetParentEl.clientTop - offsetParentEl.scrollTop;
+ offsetParentBCR.left += offsetParentEl.clientLeft - offsetParentEl.scrollLeft;
+ }
+
+ var boundingClientRect = element[0].getBoundingClientRect();
+ return {
+ width: boundingClientRect.width || element.prop('offsetWidth'),
+ height: boundingClientRect.height || element.prop('offsetHeight'),
+ top: elBCR.top - offsetParentBCR.top,
+ left: elBCR.left - offsetParentBCR.left
+ };
+ },
+
+ /**
+ * Provides read-only equivalent of jQuery's offset function:
+ * http://api.jquery.com/offset/
+ */
+ offset: function (element) {
+ var boundingClientRect = element[0].getBoundingClientRect();
+ return {
+ width: boundingClientRect.width || element.prop('offsetWidth'),
+ height: boundingClientRect.height || element.prop('offsetHeight'),
+ top: boundingClientRect.top + ($window.pageYOffset || $document[0].body.scrollTop || $document[0].documentElement.scrollTop),
+ left: boundingClientRect.left + ($window.pageXOffset || $document[0].body.scrollLeft || $document[0].documentElement.scrollLeft)
+ };
+ }
+ };
+ }]);
+
+angular.module("mm.foundation.mediaQueries", [])
+ .factory('matchMedia', ['$document', '$window', function($document, $window) {
+ // MatchMedia for IE <= 9
+ return $window.matchMedia || (function matchMedia(doc, undefined){
+ var bool,
+ docElem = doc.documentElement,
+ refNode = docElem.firstElementChild || docElem.firstChild,
+ // fakeBody required for
+ fakeBody = doc.createElement("body"),
+ div = doc.createElement("div");
+
+ div.id = "mq-test-1";
+ div.style.cssText = "position:absolute;top:-100em";
+ fakeBody.style.background = "none";
+ fakeBody.appendChild(div);
+
+ return function (q) {
+ div.innerHTML = "";
+ docElem.insertBefore(fakeBody, refNode);
+ bool = div.offsetWidth === 42;
+ docElem.removeChild(fakeBody);
+ return {
+ matches: bool,
+ media: q
+ };
+ };
+
+ }($document[0]));
+ }])
+ .factory('mediaQueries', ['$document', 'matchMedia', function($document, matchMedia) {
+ var head = angular.element($document[0].querySelector('head'));
+ head.append(' ');
+ head.append(' ');
+ head.append(' ');
+ head.append(' ');
+
+ var regex = /^[\/\\'"]+|(;\s?})+|[\/\\'"]+$/g;
+ var queries = {
+ topbar: getComputedStyle(head[0].querySelector('meta.foundation-mq-topbar')).fontFamily.replace(regex, ''),
+ small : getComputedStyle(head[0].querySelector('meta.foundation-mq-small')).fontFamily.replace(regex, ''),
+ medium : getComputedStyle(head[0].querySelector('meta.foundation-mq-medium')).fontFamily.replace(regex, ''),
+ large : getComputedStyle(head[0].querySelector('meta.foundation-mq-large')).fontFamily.replace(regex, '')
+ };
+
+ return {
+ topbarBreakpoint: function () {
+ return !matchMedia(queries.topbar).matches;
+ },
+ small: function () {
+ return matchMedia(queries.small).matches;
+ },
+ medium: function () {
+ return matchMedia(queries.medium).matches;
+ },
+ large: function () {
+ return matchMedia(queries.large).matches;
+ }
+ };
+ }]);
+
+/*
+ * dropdownToggle - Provides dropdown menu functionality
+ * @restrict class or attribute
+ * @example:
+
+ My Dropdown Menu
+
+ */
+angular.module('mm.foundation.dropdownToggle', [ 'mm.foundation.position', 'mm.foundation.mediaQueries' ])
+
+.controller('DropdownToggleController', ['$scope', '$attrs', 'mediaQueries', function($scope, $attrs, mediaQueries) {
+ this.small = function() {
+ return mediaQueries.small() && !mediaQueries.medium();
+ };
+}])
+
+.directive('dropdownToggle', ['$document', '$window', '$location', '$position', function ($document, $window, $location, $position) {
+ var openElement = null,
+ closeMenu = angular.noop;
+ return {
+ restrict: 'CA',
+ controller: 'DropdownToggleController',
+ link: function(scope, element, attrs, controller) {
+ var parent = element.parent(),
+ dropdown = angular.element($document[0].querySelector(attrs.dropdownToggle));
+
+ var parentHasDropdown = function() {
+ return parent.hasClass('has-dropdown');
+ };
+
+ var onClick = function (event) {
+ dropdown = angular.element($document[0].querySelector(attrs.dropdownToggle));
+ var elementWasOpen = (element === openElement);
+
+ event.preventDefault();
+ event.stopPropagation();
+
+ if (!!openElement) {
+ closeMenu();
+ }
+
+ if (!elementWasOpen && !element.hasClass('disabled') && !element.prop('disabled')) {
+ dropdown.css('display', 'block'); // We display the element so that offsetParent is populated
+ dropdown.addClass('f-open-dropdown');
+
+ var offset = $position.offset(element);
+ var parentOffset = $position.offset(angular.element(dropdown[0].offsetParent));
+ var dropdownWidth = dropdown.prop('offsetWidth');
+ var css = {
+ top: offset.top - parentOffset.top + offset.height + 'px'
+ };
+
+ if (controller.small()) {
+ css.left = Math.max((parentOffset.width - dropdownWidth) / 2, 8) + 'px';
+ css.position = 'absolute';
+ css.width = '95%';
+ css['max-width'] = 'none';
+ }
+ else {
+ var left = Math.round(offset.left - parentOffset.left);
+ var rightThreshold = $window.innerWidth - dropdownWidth - 8;
+ if (left > rightThreshold) {
+ left = rightThreshold;
+ dropdown.removeClass('left').addClass('right');
+ }
+ css.left = left + 'px';
+ css.position = null;
+ css['max-width'] = null;
+ }
+
+ dropdown.css(css);
+ element.addClass('expanded');
+
+ if (parentHasDropdown()) {
+ parent.addClass('hover');
+ }
+
+ openElement = element;
+
+ closeMenu = function (event) {
+ $document.off('click', closeMenu);
+ dropdown.css('display', 'none');
+ dropdown.removeClass('f-open-dropdown');
+ element.removeClass('expanded');
+ closeMenu = angular.noop;
+ openElement = null;
+ if (parent.hasClass('hover')) {
+ parent.removeClass('hover');
+ }
+ };
+ $document.on('click', closeMenu);
+ }
+ };
+
+ if (dropdown) {
+ dropdown.css('display', 'none');
+ }
+
+ scope.$watch('$location.path', function() { closeMenu(); });
+
+ element.on('click', onClick);
+ element.on('$destroy', function() {
+ element.off('click', onClick);
+ });
+ }
+ };
+}]);
+
+/**
+ * @ngdoc service
+ * @name mm.foundation.interchange
+ * @description
+ *
+ * Package containing all services and directives
+ * about the `interchange` module
+ */
+angular.module('mm.foundation.interchange', ['mm.foundation.mediaQueries'])
+
+ /**
+ * @ngdoc function
+ * @name mm.foundation.interchange.interchageQuery
+ * @function interchageQuery
+ * @description
+ *
+ * this service inject meta tags objects in the head
+ * to get the list of media queries from Foundation
+ * stylesheets.
+ *
+ * @return {object} Queries list name => mediaQuery
+ */
+ .factory('interchangeQueries', ['$document', function ($document) {
+ var element,
+ mediaSize,
+ formatList = {
+ 'default': 'only screen',
+ landscape : 'only screen and (orientation: landscape)',
+ portrait : 'only screen and (orientation: portrait)',
+ retina : 'only screen and (-webkit-min-device-pixel-ratio: 2),' +
+ 'only screen and (min--moz-device-pixel-ratio: 2),' +
+ 'only screen and (-o-min-device-pixel-ratio: 2/1),' +
+ 'only screen and (min-device-pixel-ratio: 2),' +
+ 'only screen and (min-resolution: 192dpi),' +
+ 'only screen and (min-resolution: 2dppx)'
+ },
+ classPrefix = 'foundation-mq-',
+ classList = ['small', 'medium', 'large', 'xlarge', 'xxlarge'],
+ head = angular.element($document[0].querySelector('head'));
+
+ for (var i = 0; i < classList.length; i++) {
+ head.append(' ');
+ element = getComputedStyle(head[0].querySelector('meta.' + classPrefix + classList[i]));
+ mediaSize = element.fontFamily.replace(/^[\/\\'"]+|(;\s?})+|[\/\\'"]+$/g, '');
+ formatList[classList[i]] = mediaSize;
+ }
+ return formatList;
+ }])
+
+ /**
+ * @ngdoc function
+ * @name mm.foundation.interchange.interchangeQueriesManager
+ * @function interchangeQueriesManager
+ * @description
+ *
+ * interface to add and remove named queries
+ * in the interchangeQueries list
+ */
+ .factory('interchangeQueriesManager', ['interchangeQueries', function (interchangeQueries) {
+ return {
+ /**
+ * @ngdoc method
+ * @name interchangeQueriesManager#add
+ * @methodOf mm.foundation.interchange.interchangeQueriesManager
+ * @description
+ *
+ * Add a custom media query in the `interchangeQueries`
+ * factory. This method does not allow to update an existing
+ * media query.
+ *
+ * @param {string} name MediaQuery name
+ * @param {string} media MediaQuery
+ * @returns {boolean} True if the insert is a success
+ */
+ add: function (name, media) {
+ if (!name || !media ||
+ !angular.isString(name) || !angular.isString(media) ||
+ !!interchangeQueries[name]) {
+ return false;
+ }
+ interchangeQueries[name] = media;
+ return true;
+ }
+ };
+ }])
+
+ /**
+ * @ngdoc function
+ * @name mm.foundation.interchange.interchangeTools
+ * @function interchangeTools
+ * @description
+ *
+ * Tools to help with the `interchange` module.
+ */
+ .factory('interchangeTools', ['$window', 'matchMedia', 'interchangeQueries', function ($window, matchMedia, namedQueries) {
+
+ /**
+ * @ngdoc method
+ * @name interchangeTools#parseAttribute
+ * @methodOf mm.foundation.interchange.interchangeTools
+ * @description
+ *
+ * Attribute parser to transform an `interchange` attribute
+ * value to an object with media query (name or query) as key,
+ * and file to use as value.
+ *
+ * ```
+ * {
+ * small: 'bridge-500.jpg',
+ * large: 'bridge-1200.jpg'
+ * }
+ * ```
+ *
+ * @param {string} value Interchange query string
+ * @returns {object} Attribute parsed
+ */
+ var parseAttribute = function (value) {
+ var raw = value.split(/\[(.*?)\]/),
+ i = raw.length,
+ breaker = /^(.+)\,\ \((.+)\)$/,
+ breaked,
+ output = {};
+
+ while (i--) {
+ if (raw[i].replace(/[\W\d]+/, '').length > 4) {
+ breaked = breaker.exec(raw[i]);
+ if (!!breaked && breaked.length === 3) {
+ output[breaked[2]] = breaked[1];
+ }
+ }
+ }
+ return output;
+ };
+
+ /**
+ * @ngdoc method
+ * @name interchangeTools#findCurrentMediaFile
+ * @methodOf mm.foundation.interchange.interchangeTools
+ * @description
+ *
+ * Find the current item to display from a file list
+ * (object returned by `parseAttribute`) and the
+ * current page dimensions.
+ *
+ * ```
+ * {
+ * small: 'bridge-500.jpg',
+ * large: 'bridge-1200.jpg'
+ * }
+ * ```
+ *
+ * @param {object} files Parsed version of `interchange` attribute
+ * @returns {string} File to display (or `undefined`)
+ */
+ var findCurrentMediaFile = function (files) {
+ var file, media, match;
+ for (file in files) {
+ media = namedQueries[file] || file;
+ match = matchMedia(media);
+ if (match.matches) {
+ return files[file];
+ }
+ }
+ return;
+ };
+
+ return {
+ parseAttribute: parseAttribute,
+ findCurrentMediaFile: findCurrentMediaFile
+ };
+ }])
+
+ /**
+ * @ngdoc directive
+ * @name mm.foundation.interchange.directive:interchange
+ * @restrict A
+ * @element DIV|IMG
+ * @priority 450
+ * @scope true
+ * @description
+ *
+ * Interchange directive, following the same features as
+ * ZURB documentation. The directive is splitted in 3 parts.
+ *
+ * 1. This directive use `compile` and not `link` for a simple
+ * reason: if the method is applied on a DIV element to
+ * display a template, the compile method will inject an ng-include.
+ * Because using a `templateUrl` or `template` to do it wouldn't
+ * be appropriate for all cases (`IMG` or dynamic backgrounds).
+ * And doing it in `link` is too late to be applied.
+ *
+ * 2. In the `compile:post`, the attribute is parsed to find
+ * out the type of content to display.
+ *
+ * 3. At the start and on event `resize`, the method `replace`
+ * is called to reevaluate which file is supposed to be displayed
+ * and update the value if necessary. The methd will also
+ * trigger a `replace` event.
+ */
+ .directive('interchange', ['$window', '$rootScope', 'interchangeTools', function ($window, $rootScope, interchangeTools) {
+
+ var pictureFilePattern = /[A-Za-z0-9-_]+\.(jpg|jpeg|png|gif|bmp|tiff)\ *,/i;
+
+ return {
+ restrict: 'A',
+ scope: true,
+ priority: 450,
+ compile: function compile($element, attrs) {
+ // Set up the attribute to update
+ if ($element[0].nodeName === 'DIV' && !pictureFilePattern.test(attrs.interchange)) {
+ $element.html(' ');
+ }
+
+ return {
+ pre: function preLink($scope, $element, attrs) {},
+ post: function postLink($scope, $element, attrs) {
+ var currentFile, nodeName;
+
+ // Set up the attribute to update
+ nodeName = $element && $element[0] && $element[0].nodeName;
+ $scope.fileMap = interchangeTools.parseAttribute(attrs.interchange);
+
+ // Find the type of interchange
+ switch (nodeName) {
+ case 'DIV':
+ // If the tag is a div, we test the current file to see if it's picture
+ currentFile = interchangeTools.findCurrentMediaFile($scope.fileMap);
+ if (/[A-Za-z0-9-_]+\.(jpg|jpeg|png|gif|bmp|tiff)$/i.test(currentFile)) {
+ $scope.type = 'background';
+ }
+ else {
+ $scope.type = 'include';
+ }
+ break;
+
+ case 'IMG':
+ $scope.type = 'image';
+ break;
+
+ default:
+ return;
+ }
+
+ var replace = function (e) {
+ // The the new file to display (exit if the same)
+ var currentFile = interchangeTools.findCurrentMediaFile($scope.fileMap);
+ if (!!$scope.currentFile && $scope.currentFile === currentFile) {
+ return;
+ }
+
+ // Set up the new file
+ $scope.currentFile = currentFile;
+ switch ($scope.type) {
+ case 'image':
+ $element.attr('src', $scope.currentFile);
+ break;
+
+ case 'background':
+ $element.css('background-image', 'url(' + $scope.currentFile + ')');
+ break;
+ }
+
+ // Trigger events
+ $rootScope.$emit('replace', $element, $scope);
+ if (!!e) {
+ $scope.$apply();
+ }
+ };
+
+ // Start
+ replace();
+ $window.addEventListener('resize', replace);
+ $scope.$on('$destroy', function () {
+ $window.removeEventListener('resize', replace);
+ });
+ }
+ };
+ }
+ };
+ }]);
+
+angular.module('mm.foundation.transition', [])
+
+/**
+ * $transition service provides a consistent interface to trigger CSS 3 transitions and to be informed when they complete.
+ * @param {DOMElement} element The DOMElement that will be animated.
+ * @param {string|object|function} trigger The thing that will cause the transition to start:
+ * - As a string, it represents the css class to be added to the element.
+ * - As an object, it represents a hash of style attributes to be applied to the element.
+ * - As a function, it represents a function to be called that will cause the transition to occur.
+ * @return {Promise} A promise that is resolved when the transition finishes.
+ */
+.factory('$transition', ['$q', '$timeout', '$rootScope', function($q, $timeout, $rootScope) {
+
+ var $transition = function(element, trigger, options) {
+ options = options || {};
+ var deferred = $q.defer();
+ var endEventName = $transition[options.animation ? "animationEndEventName" : "transitionEndEventName"];
+
+ var transitionEndHandler = function(event) {
+ $rootScope.$apply(function() {
+ element.unbind(endEventName, transitionEndHandler);
+ deferred.resolve(element);
+ });
+ };
+
+ if (endEventName) {
+ element.bind(endEventName, transitionEndHandler);
+ }
+
+ // Wrap in a timeout to allow the browser time to update the DOM before the transition is to occur
+ $timeout(function() {
+ if ( angular.isString(trigger) ) {
+ element.addClass(trigger);
+ } else if ( angular.isFunction(trigger) ) {
+ trigger(element);
+ } else if ( angular.isObject(trigger) ) {
+ element.css(trigger);
+ }
+ //If browser does not support transitions, instantly resolve
+ if ( !endEventName ) {
+ deferred.resolve(element);
+ }
+ });
+
+ // Add our custom cancel function to the promise that is returned
+ // We can call this if we are about to run a new transition, which we know will prevent this transition from ending,
+ // i.e. it will therefore never raise a transitionEnd event for that transition
+ deferred.promise.cancel = function() {
+ if ( endEventName ) {
+ element.unbind(endEventName, transitionEndHandler);
+ }
+ deferred.reject('Transition cancelled');
+ };
+
+ return deferred.promise;
+ };
+
+ // Work out the name of the transitionEnd event
+ var transElement = document.createElement('trans');
+ var transitionEndEventNames = {
+ 'WebkitTransition': 'webkitTransitionEnd',
+ 'MozTransition': 'transitionend',
+ 'OTransition': 'oTransitionEnd',
+ 'transition': 'transitionend'
+ };
+ var animationEndEventNames = {
+ 'WebkitTransition': 'webkitAnimationEnd',
+ 'MozTransition': 'animationend',
+ 'OTransition': 'oAnimationEnd',
+ 'transition': 'animationend'
+ };
+ function findEndEventName(endEventNames) {
+ for (var name in endEventNames){
+ if (transElement.style[name] !== undefined) {
+ return endEventNames[name];
+ }
+ }
+ }
+ $transition.transitionEndEventName = findEndEventName(transitionEndEventNames);
+ $transition.animationEndEventName = findEndEventName(animationEndEventNames);
+ return $transition;
+}]);
+
+angular.module('mm.foundation.modal', ['mm.foundation.transition'])
+
+/**
+ * A helper, internal data structure that acts as a map but also allows getting / removing
+ * elements in the LIFO order
+ */
+ .factory('$$stackedMap', function () {
+ return {
+ createNew: function () {
+ var stack = [];
+
+ return {
+ add: function (key, value) {
+ stack.push({
+ key: key,
+ value: value
+ });
+ },
+ get: function (key) {
+ for (var i = 0; i < stack.length; i++) {
+ if (key == stack[i].key) {
+ return stack[i];
+ }
+ }
+ },
+ keys: function() {
+ var keys = [];
+ for (var i = 0; i < stack.length; i++) {
+ keys.push(stack[i].key);
+ }
+ return keys;
+ },
+ top: function () {
+ return stack[stack.length - 1];
+ },
+ remove: function (key) {
+ var idx = -1;
+ for (var i = 0; i < stack.length; i++) {
+ if (key == stack[i].key) {
+ idx = i;
+ break;
+ }
+ }
+ return stack.splice(idx, 1)[0];
+ },
+ removeTop: function () {
+ return stack.splice(stack.length - 1, 1)[0];
+ },
+ length: function () {
+ return stack.length;
+ }
+ };
+ }
+ };
+ })
+
+/**
+ * A helper directive for the $modal service. It creates a backdrop element.
+ */
+ .directive('modalBackdrop', ['$modalStack', '$timeout', function ($modalStack, $timeout) {
+ return {
+ restrict: 'EA',
+ replace: true,
+ templateUrl: 'template/modal/backdrop.html',
+ link: function (scope) {
+
+ scope.animate = false;
+
+ //trigger CSS transitions
+ $timeout(function () {
+ scope.animate = true;
+ });
+
+ scope.close = function (evt) {
+ var modal = $modalStack.getTop();
+ if (modal && modal.value.backdrop && modal.value.backdrop != 'static' && (evt.target === evt.currentTarget)) {
+ evt.preventDefault();
+ evt.stopPropagation();
+ $modalStack.dismiss(modal.key, 'backdrop click');
+ }
+ };
+ }
+ };
+ }])
+
+ .directive('modalWindow', ['$modalStack', '$timeout', function ($modalStack, $timeout) {
+ return {
+ restrict: 'EA',
+ scope: {
+ index: '@',
+ animate: '='
+ },
+ replace: true,
+ transclude: true,
+ templateUrl: 'template/modal/window.html',
+ link: function (scope, element, attrs) {
+ scope.windowClass = attrs.windowClass || '';
+
+ $timeout(function () {
+ // trigger CSS transitions
+ scope.animate = true;
+
+ // If the modal contains any autofocus elements refocus onto the first one
+ if (element[0].querySelectorAll('[autofocus]').length > 0) {
+ element[0].querySelectorAll('[autofocus]')[0].focus();
+ }
+ else{
+ // otherwise focus the freshly-opened modal
+ element[0].focus();
+ }
+ });
+ }
+ };
+ }])
+
+ .factory('$modalStack', ['$window', '$transition', '$timeout', '$document', '$compile', '$rootScope', '$$stackedMap',
+ function ($window, $transition, $timeout, $document, $compile, $rootScope, $$stackedMap) {
+
+ var OPENED_MODAL_CLASS = 'modal-open';
+
+ var backdropDomEl, backdropScope, cssTop;
+ var openedWindows = $$stackedMap.createNew();
+ var $modalStack = {};
+
+ function backdropIndex() {
+ var topBackdropIndex = -1;
+ var opened = openedWindows.keys();
+ for (var i = 0; i < opened.length; i++) {
+ if (openedWindows.get(opened[i]).value.backdrop) {
+ topBackdropIndex = i;
+ }
+ }
+ return topBackdropIndex;
+ }
+
+ $rootScope.$watch(backdropIndex, function(newBackdropIndex){
+ if (backdropScope) {
+ backdropScope.index = newBackdropIndex;
+ }
+ });
+
+ function removeModalWindow(modalInstance) {
+ var parent = $document.find(modalInstance.options.parent).eq(0);
+ var modalWindow = openedWindows.get(modalInstance).value;
+
+ //clean up the stack
+ openedWindows.remove(modalInstance);
+
+ //remove window DOM element
+ removeAfterAnimate(modalWindow.modalDomEl, modalWindow.modalScope, 300, function() {
+ modalWindow.modalScope.$destroy();
+ parent.toggleClass(OPENED_MODAL_CLASS, openedWindows.length() > 0);
+ checkRemoveBackdrop();
+ });
+ }
+
+ function checkRemoveBackdrop() {
+ //remove backdrop if no longer needed
+ if (backdropDomEl && backdropIndex() == -1) {
+ var backdropScopeRef = backdropScope;
+ removeAfterAnimate(backdropDomEl, backdropScope, 150, function () {
+ backdropScopeRef.$destroy();
+ backdropScopeRef = null;
+ });
+ backdropDomEl = undefined;
+ backdropScope = undefined;
+ }
+ }
+
+ function removeAfterAnimate(domEl, scope, emulateTime, done) {
+ // Closing animation
+ scope.animate = false;
+
+ var transitionEndEventName = $transition.transitionEndEventName;
+ if (transitionEndEventName) {
+ // transition out
+ var timeout = $timeout(afterAnimating, emulateTime);
+
+ domEl.bind(transitionEndEventName, function () {
+ $timeout.cancel(timeout);
+ afterAnimating();
+ scope.$apply();
+ });
+ } else {
+ // Ensure this call is async
+ $timeout(afterAnimating, 0);
+ }
+
+ function afterAnimating() {
+ if (afterAnimating.done) {
+ return;
+ }
+ afterAnimating.done = true;
+
+ domEl.remove();
+ if (done) {
+ done();
+ }
+ }
+ }
+
+ function calculateModalTop(modalElement, offset) {
+ if (angular.isUndefined(offset)) {
+ offset = 0;
+ }
+ var scrollY = $window.pageYOffset || 0;
+ return offset + scrollY;
+ }
+
+ $document.bind('keydown', function (evt) {
+ var modal;
+
+ if (evt.which === 27) {
+ modal = openedWindows.top();
+ if (modal && modal.value.keyboard) {
+ $rootScope.$apply(function () {
+ $modalStack.dismiss(modal.key);
+ });
+ }
+ }
+ });
+
+ $modalStack.open = function (modalInstance, modal) {
+ modalInstance.options = {
+ deferred: modal.deferred,
+ modalScope: modal.scope,
+ backdrop: modal.backdrop,
+ keyboard: modal.keyboard,
+ parent: modal.parent
+ };
+ openedWindows.add(modalInstance, modalInstance.options);
+
+ var parent = $document.find(modal.parent).eq(0),
+ currBackdropIndex = backdropIndex();
+
+ if (currBackdropIndex >= 0 && !backdropDomEl) {
+ backdropScope = $rootScope.$new(true);
+ backdropScope.index = currBackdropIndex;
+ backdropDomEl = $compile('
')(backdropScope);
+ parent.append(backdropDomEl);
+ }
+
+ // Create a faux modal div just to measure its
+ // distance to top
+ var faux = angular.element('
');
+ parent.append(faux[0]);
+ cssTop = parseInt($window.getComputedStyle(faux[0]).top) || 0;
+ var openAt = calculateModalTop(faux, cssTop);
+ faux.remove();
+
+ var angularDomEl = angular.element('
')
+ .attr({
+ 'window-class': modal.windowClass,
+ 'index': openedWindows.length() - 1,
+ 'animate': 'animate'
+ });
+ angularDomEl.html(modal.content);
+
+ var modalDomEl = $compile(angularDomEl)(modal.scope);
+ openedWindows.top().value.modalDomEl = modalDomEl;
+ parent.append(modalDomEl);
+ parent.addClass(OPENED_MODAL_CLASS);
+ };
+
+ $modalStack.reposition = function (modalInstance) {
+ var modalWindow = openedWindows.get(modalInstance).value;
+ if (modalWindow) {
+ var modalDomEl = modalWindow.modalDomEl;
+ var top = calculateModalTop(modalDomEl, cssTop);
+ modalDomEl.css('top', top + "px");
+ }
+ };
+
+ $modalStack.close = function (modalInstance, result) {
+ var modalWindow = openedWindows.get(modalInstance);
+ if (modalWindow) {
+ modalWindow.value.deferred.resolve(result);
+ removeModalWindow(modalInstance);
+ }
+ };
+
+ $modalStack.dismiss = function (modalInstance, reason) {
+ var modalWindow = openedWindows.get(modalInstance);
+ if (modalWindow) {
+ modalWindow.value.deferred.reject(reason);
+ removeModalWindow(modalInstance);
+ }
+ };
+
+ $modalStack.dismissAll = function (reason) {
+ var topModal = this.getTop();
+ while (topModal) {
+ this.dismiss(topModal.key, reason);
+ topModal = this.getTop();
+ }
+ };
+
+ $modalStack.getTop = function () {
+ return openedWindows.top();
+ };
+
+ return $modalStack;
+ }])
+
+ .provider('$modal', function () {
+
+ var $modalProvider = {
+ options: {
+ backdrop: true, //can be also false or 'static'
+ keyboard: true
+ },
+ $get: ['$injector', '$rootScope', '$q', '$http', '$templateCache', '$controller', '$modalStack',
+ function ($injector, $rootScope, $q, $http, $templateCache, $controller, $modalStack) {
+
+ var $modal = {};
+
+ function getTemplatePromise(options) {
+ return options.template ? $q.when(options.template) :
+ $http.get(options.templateUrl, {cache: $templateCache}).then(function (result) {
+ return result.data;
+ });
+ }
+
+ function getResolvePromises(resolves) {
+ var promisesArr = [];
+ angular.forEach(resolves, function (value, key) {
+ if (angular.isFunction(value) || angular.isArray(value)) {
+ promisesArr.push($q.when($injector.invoke(value)));
+ }
+ });
+ return promisesArr;
+ }
+
+ $modal.open = function (modalOptions) {
+
+ var modalResultDeferred = $q.defer();
+ var modalOpenedDeferred = $q.defer();
+
+ //prepare an instance of a modal to be injected into controllers and returned to a caller
+ var modalInstance = {
+ result: modalResultDeferred.promise,
+ opened: modalOpenedDeferred.promise,
+ close: function (result) {
+ $modalStack.close(modalInstance, result);
+ },
+ dismiss: function (reason) {
+ $modalStack.dismiss(modalInstance, reason);
+ },
+ reposition: function () {
+ $modalStack.reposition(modalInstance);
+ }
+ };
+
+ //merge and clean up options
+ modalOptions = angular.extend({}, $modalProvider.options, modalOptions);
+ modalOptions.resolve = modalOptions.resolve || {};
+
+ //verify options
+ if (!modalOptions.template && !modalOptions.templateUrl) {
+ throw new Error('One of template or templateUrl options is required.');
+ }
+
+ var templateAndResolvePromise =
+ $q.all([getTemplatePromise(modalOptions)].concat(getResolvePromises(modalOptions.resolve)));
+
+
+ templateAndResolvePromise.then(function resolveSuccess(tplAndVars) {
+
+ var modalScope = (modalOptions.scope || $rootScope).$new();
+ modalScope.$close = modalInstance.close;
+ modalScope.$dismiss = modalInstance.dismiss;
+
+ var ctrlInstance, ctrlLocals = {};
+ var resolveIter = 1;
+
+ //controllers
+ if (modalOptions.controller) {
+ ctrlLocals.$scope = modalScope;
+ ctrlLocals.$modalInstance = modalInstance;
+ angular.forEach(modalOptions.resolve, function (value, key) {
+ ctrlLocals[key] = tplAndVars[resolveIter++];
+ });
+
+ ctrlInstance = $controller(modalOptions.controller, ctrlLocals);
+ if (modalOptions.controllerAs) {
+ modalScope[modalOptions.controllerAs] = ctrlInstance;
+ }
+ }
+
+ $modalStack.open(modalInstance, {
+ scope: modalScope,
+ deferred: modalResultDeferred,
+ content: tplAndVars[0],
+ backdrop: modalOptions.backdrop,
+ keyboard: modalOptions.keyboard,
+ windowClass: modalOptions.windowClass,
+ parent: modalOptions.parent || 'body'
+ });
+
+ }, function resolveError(reason) {
+ modalResultDeferred.reject(reason);
+ });
+
+ templateAndResolvePromise.then(function () {
+ modalOpenedDeferred.resolve(true);
+ }, function () {
+ modalOpenedDeferred.reject(false);
+ });
+
+ return modalInstance;
+ };
+
+ return $modal;
+ }]
+ };
+
+ return $modalProvider;
+ });
+
+angular.module("mm.foundation.offcanvas", [])
+ .directive('offCanvasWrap', ['$window', function ($window) {
+ return {
+ scope: {},
+ restrict: 'C',
+ link: function ($scope, element, attrs) {
+ var win = angular.element($window);
+ var sidebar = $scope.sidebar = element;
+
+ $scope.hide = function () {
+ sidebar.removeClass('move-left');
+ sidebar.removeClass('move-right');
+ };
+
+ win.bind("resize.body", $scope.hide);
+
+ $scope.$on('$destroy', function() {
+ win.unbind("resize.body", $scope.hide);
+ });
+
+ },
+ controller: ['$scope', function($scope) {
+
+ this.leftToggle = function() {
+ $scope.sidebar.toggleClass("move-right");
+ };
+
+ this.rightToggle = function() {
+ $scope.sidebar.toggleClass("move-left");
+ };
+
+ this.hide = function() {
+ $scope.hide();
+ };
+ }]
+ };
+ }])
+ .directive('leftOffCanvasToggle', [function () {
+ return {
+ require: '^offCanvasWrap',
+ restrict: 'C',
+ link: function ($scope, element, attrs, offCanvasWrap) {
+ element.on('click', function () {
+ offCanvasWrap.leftToggle();
+ });
+ }
+ };
+ }])
+ .directive('rightOffCanvasToggle', [function () {
+ return {
+ require: '^offCanvasWrap',
+ restrict: 'C',
+ link: function ($scope, element, attrs, offCanvasWrap) {
+ element.on('click', function () {
+ offCanvasWrap.rightToggle();
+ });
+ }
+ };
+ }])
+ .directive('exitOffCanvas', [function () {
+ return {
+ require: '^offCanvasWrap',
+ restrict: 'C',
+ link: function ($scope, element, attrs, offCanvasWrap) {
+ element.on('click', function () {
+ offCanvasWrap.hide();
+ });
+ }
+ };
+ }])
+ .directive('offCanvasList', [function () {
+ return {
+ require: '^offCanvasWrap',
+ restrict: 'C',
+ link: function ($scope, element, attrs, offCanvasWrap) {
+ element.on('click', function () {
+ offCanvasWrap.hide();
+ });
+ }
+ };
+ }]);
+
+angular.module('mm.foundation.pagination', [])
+
+.controller('PaginationController', ['$scope', '$attrs', '$parse', '$interpolate', function ($scope, $attrs, $parse, $interpolate) {
+ var self = this,
+ setNumPages = $attrs.numPages ? $parse($attrs.numPages).assign : angular.noop;
+
+ this.init = function(defaultItemsPerPage) {
+ if ($attrs.itemsPerPage) {
+ $scope.$parent.$watch($parse($attrs.itemsPerPage), function(value) {
+ self.itemsPerPage = parseInt(value, 10);
+ $scope.totalPages = self.calculateTotalPages();
+ });
+ } else {
+ this.itemsPerPage = defaultItemsPerPage;
+ }
+ };
+
+ this.noPrevious = function() {
+ return this.page === 1;
+ };
+ this.noNext = function() {
+ return this.page === $scope.totalPages;
+ };
+
+ this.isActive = function(page) {
+ return this.page === page;
+ };
+
+ this.calculateTotalPages = function() {
+ var totalPages = this.itemsPerPage < 1 ? 1 : Math.ceil($scope.totalItems / this.itemsPerPage);
+ return Math.max(totalPages || 0, 1);
+ };
+
+ this.getAttributeValue = function(attribute, defaultValue, interpolate) {
+ return angular.isDefined(attribute) ? (interpolate ? $interpolate(attribute)($scope.$parent) : $scope.$parent.$eval(attribute)) : defaultValue;
+ };
+
+ this.render = function() {
+ this.page = parseInt($scope.page, 10) || 1;
+ if (this.page > 0 && this.page <= $scope.totalPages) {
+ $scope.pages = this.getPages(this.page, $scope.totalPages);
+ }
+ };
+
+ $scope.selectPage = function(page) {
+ if ( ! self.isActive(page) && page > 0 && page <= $scope.totalPages) {
+ $scope.page = page;
+ $scope.onSelectPage({ page: page });
+ }
+ };
+
+ $scope.$watch('page', function() {
+ self.render();
+ });
+
+ $scope.$watch('totalItems', function() {
+ $scope.totalPages = self.calculateTotalPages();
+ });
+
+ $scope.$watch('totalPages', function(value) {
+ setNumPages($scope.$parent, value); // Readonly variable
+
+ if ( self.page > value ) {
+ $scope.selectPage(value);
+ } else {
+ self.render();
+ }
+ });
+}])
+
+.constant('paginationConfig', {
+ itemsPerPage: 10,
+ boundaryLinks: false,
+ directionLinks: true,
+ firstText: 'First',
+ previousText: 'Previous',
+ nextText: 'Next',
+ lastText: 'Last',
+ rotate: true
+})
+
+.directive('pagination', ['$parse', 'paginationConfig', function($parse, config) {
+ return {
+ restrict: 'EA',
+ scope: {
+ page: '=',
+ totalItems: '=',
+ onSelectPage:' &'
+ },
+ controller: 'PaginationController',
+ templateUrl: 'template/pagination/pagination.html',
+ replace: true,
+ link: function(scope, element, attrs, paginationCtrl) {
+
+ // Setup configuration parameters
+ var maxSize,
+ boundaryLinks = paginationCtrl.getAttributeValue(attrs.boundaryLinks, config.boundaryLinks ),
+ directionLinks = paginationCtrl.getAttributeValue(attrs.directionLinks, config.directionLinks ),
+ firstText = paginationCtrl.getAttributeValue(attrs.firstText, config.firstText, true),
+ previousText = paginationCtrl.getAttributeValue(attrs.previousText, config.previousText, true),
+ nextText = paginationCtrl.getAttributeValue(attrs.nextText, config.nextText, true),
+ lastText = paginationCtrl.getAttributeValue(attrs.lastText, config.lastText, true),
+ rotate = paginationCtrl.getAttributeValue(attrs.rotate, config.rotate);
+
+ paginationCtrl.init(config.itemsPerPage);
+
+ if (attrs.maxSize) {
+ scope.$parent.$watch($parse(attrs.maxSize), function(value) {
+ maxSize = parseInt(value, 10);
+ paginationCtrl.render();
+ });
+ }
+
+ // Create page object used in template
+ function makePage(number, text, isActive, isDisabled) {
+ return {
+ number: number,
+ text: text,
+ active: isActive,
+ disabled: isDisabled
+ };
+ }
+
+ paginationCtrl.getPages = function(currentPage, totalPages) {
+ var pages = [];
+
+ // Default page limits
+ var startPage = 1, endPage = totalPages;
+ var isMaxSized = ( angular.isDefined(maxSize) && maxSize < totalPages );
+
+ // recompute if maxSize
+ if ( isMaxSized ) {
+ if ( rotate ) {
+ // Current page is displayed in the middle of the visible ones
+ startPage = Math.max(currentPage - Math.floor(maxSize/2), 1);
+ endPage = startPage + maxSize - 1;
+
+ // Adjust if limit is exceeded
+ if (endPage > totalPages) {
+ endPage = totalPages;
+ startPage = endPage - maxSize + 1;
+ }
+ } else {
+ // Visible pages are paginated with maxSize
+ startPage = ((Math.ceil(currentPage / maxSize) - 1) * maxSize) + 1;
+
+ // Adjust last page if limit is exceeded
+ endPage = Math.min(startPage + maxSize - 1, totalPages);
+ }
+ }
+
+ // Add page number links
+ for (var number = startPage; number <= endPage; number++) {
+ var page = makePage(number, number, paginationCtrl.isActive(number), false);
+ pages.push(page);
+ }
+
+ // Add links to move between page sets
+ if ( isMaxSized && ! rotate ) {
+ if ( startPage > 1 ) {
+ var previousPageSet = makePage(startPage - 1, '...', false, false);
+ pages.unshift(previousPageSet);
+ }
+
+ if ( endPage < totalPages ) {
+ var nextPageSet = makePage(endPage + 1, '...', false, false);
+ pages.push(nextPageSet);
+ }
+ }
+
+ // Add previous & next links
+ if (directionLinks) {
+ var previousPage = makePage(currentPage - 1, previousText, false, paginationCtrl.noPrevious());
+ pages.unshift(previousPage);
+
+ var nextPage = makePage(currentPage + 1, nextText, false, paginationCtrl.noNext());
+ pages.push(nextPage);
+ }
+
+ // Add first & last links
+ if (boundaryLinks) {
+ var firstPage = makePage(1, firstText, false, paginationCtrl.noPrevious());
+ pages.unshift(firstPage);
+
+ var lastPage = makePage(totalPages, lastText, false, paginationCtrl.noNext());
+ pages.push(lastPage);
+ }
+
+ return pages;
+ };
+ }
+ };
+}])
+
+.constant('pagerConfig', {
+ itemsPerPage: 10,
+ previousText: '« Previous',
+ nextText: 'Next »',
+ align: true
+})
+
+.directive('pager', ['pagerConfig', function(config) {
+ return {
+ restrict: 'EA',
+ scope: {
+ page: '=',
+ totalItems: '=',
+ onSelectPage:' &'
+ },
+ controller: 'PaginationController',
+ templateUrl: 'template/pagination/pager.html',
+ replace: true,
+ link: function(scope, element, attrs, paginationCtrl) {
+
+ // Setup configuration parameters
+ var previousText = paginationCtrl.getAttributeValue(attrs.previousText, config.previousText, true),
+ nextText = paginationCtrl.getAttributeValue(attrs.nextText, config.nextText, true),
+ align = paginationCtrl.getAttributeValue(attrs.align, config.align);
+
+ paginationCtrl.init(config.itemsPerPage);
+
+ // Create page object used in template
+ function makePage(number, text, isDisabled, isPrevious, isNext) {
+ return {
+ number: number,
+ text: text,
+ disabled: isDisabled,
+ previous: ( align && isPrevious ),
+ next: ( align && isNext )
+ };
+ }
+
+ paginationCtrl.getPages = function(currentPage) {
+ return [
+ makePage(currentPage - 1, previousText, paginationCtrl.noPrevious(), true, false),
+ makePage(currentPage + 1, nextText, paginationCtrl.noNext(), false, true)
+ ];
+ };
+ }
+ };
+}]);
+
+/**
+ * The following features are still outstanding: animation as a
+ * function, placement as a function, inside, support for more triggers than
+ * just mouse enter/leave, html tooltips, and selector delegation.
+ */
+angular.module( 'mm.foundation.tooltip', [ 'mm.foundation.position', 'mm.foundation.bindHtml' ] )
+
+/**
+ * The $tooltip service creates tooltip- and popover-like directives as well as
+ * houses global options for them.
+ */
+.provider( '$tooltip', function () {
+ // The default options tooltip and popover.
+ var defaultOptions = {
+ placement: 'top',
+ animation: true,
+ popupDelay: 0
+ };
+
+ // Default hide triggers for each show trigger
+ var triggerMap = {
+ 'mouseenter': 'mouseleave',
+ 'click': 'click',
+ 'focus': 'blur'
+ };
+
+ // The options specified to the provider globally.
+ var globalOptions = {};
+
+ /**
+ * `options({})` allows global configuration of all tooltips in the
+ * application.
+ *
+ * var app = angular.module( 'App', ['mm.foundation.tooltip'], function( $tooltipProvider ) {
+ * // place tooltips left instead of top by default
+ * $tooltipProvider.options( { placement: 'left' } );
+ * });
+ */
+ this.options = function( value ) {
+ angular.extend( globalOptions, value );
+ };
+
+ /**
+ * This allows you to extend the set of trigger mappings available. E.g.:
+ *
+ * $tooltipProvider.setTriggers( 'openTrigger': 'closeTrigger' );
+ */
+ this.setTriggers = function setTriggers ( triggers ) {
+ angular.extend( triggerMap, triggers );
+ };
+
+ /**
+ * This is a helper function for translating camel-case to snake-case.
+ */
+ function snake_case(name){
+ var regexp = /[A-Z]/g;
+ var separator = '-';
+ return name.replace(regexp, function(letter, pos) {
+ return (pos ? separator : '') + letter.toLowerCase();
+ });
+ }
+
+ /**
+ * Returns the actual instance of the $tooltip service.
+ * TODO support multiple triggers
+ */
+ this.$get = [ '$window', '$compile', '$timeout', '$parse', '$document', '$position', '$interpolate', function ( $window, $compile, $timeout, $parse, $document, $position, $interpolate ) {
+ return function $tooltip ( type, prefix, defaultTriggerShow ) {
+ var options = angular.extend( {}, defaultOptions, globalOptions );
+
+ /**
+ * Returns an object of show and hide triggers.
+ *
+ * If a trigger is supplied,
+ * it is used to show the tooltip; otherwise, it will use the `trigger`
+ * option passed to the `$tooltipProvider.options` method; else it will
+ * default to the trigger supplied to this directive factory.
+ *
+ * The hide trigger is based on the show trigger. If the `trigger` option
+ * was passed to the `$tooltipProvider.options` method, it will use the
+ * mapped trigger from `triggerMap` or the passed trigger if the map is
+ * undefined; otherwise, it uses the `triggerMap` value of the show
+ * trigger; else it will just use the show trigger.
+ */
+ function getTriggers ( trigger ) {
+ var show = trigger || options.trigger || defaultTriggerShow;
+ var hide = triggerMap[show] || show;
+ return {
+ show: show,
+ hide: hide
+ };
+ }
+
+ var directiveName = snake_case( type );
+
+ var startSym = $interpolate.startSymbol();
+ var endSym = $interpolate.endSymbol();
+ var template =
+ ''+
+ '
';
+
+ return {
+ restrict: 'EA',
+ scope: true,
+ compile: function (tElem, tAttrs) {
+ var tooltipLinker = $compile( template );
+
+ return function link ( scope, element, attrs ) {
+ var tooltip;
+ var transitionTimeout;
+ var popupTimeout;
+ var appendToBody = angular.isDefined( options.appendToBody ) ? options.appendToBody : false;
+ var triggers = getTriggers( undefined );
+ var hasRegisteredTriggers = false;
+ var hasEnableExp = angular.isDefined(attrs[prefix+'Enable']);
+
+ var positionTooltip = function (){
+ var position,
+ ttWidth,
+ ttHeight,
+ ttPosition;
+ // Get the position of the directive element.
+ position = appendToBody ? $position.offset( element ) : $position.position( element );
+
+ // Get the height and width of the tooltip so we can center it.
+ ttWidth = tooltip.prop( 'offsetWidth' );
+ ttHeight = tooltip.prop( 'offsetHeight' );
+
+ // Calculate the tooltip's top and left coordinates to center it with
+ // this directive.
+ switch ( scope.tt_placement ) {
+ case 'right':
+ ttPosition = {
+ top: position.top + position.height / 2 - ttHeight / 2,
+ left: position.left + position.width + 10
+ };
+ break;
+ case 'bottom':
+ ttPosition = {
+ top: position.top + position.height + 10,
+ left: position.left
+ };
+ break;
+ case 'left':
+ ttPosition = {
+ top: position.top + position.height / 2 - ttHeight / 2,
+ left: position.left - ttWidth - 10
+ };
+ break;
+ default:
+ ttPosition = {
+ top: position.top - ttHeight - 10,
+ left: position.left
+ };
+ break;
+ }
+
+ ttPosition.top += 'px';
+ ttPosition.left += 'px';
+
+ // Now set the calculated positioning.
+ tooltip.css( ttPosition );
+
+ };
+
+ // By default, the tooltip is not open.
+ // TODO add ability to start tooltip opened
+ scope.tt_isOpen = false;
+
+ function toggleTooltipBind () {
+ if ( ! scope.tt_isOpen ) {
+ showTooltipBind();
+ } else {
+ hideTooltipBind();
+ }
+ }
+
+ // Show the tooltip with delay if specified, otherwise show it immediately
+ function showTooltipBind() {
+ if(hasEnableExp && !scope.$eval(attrs[prefix+'Enable'])) {
+ return;
+ }
+ if ( scope.tt_popupDelay ) {
+ popupTimeout = $timeout( show, scope.tt_popupDelay, false );
+ popupTimeout.then(function(reposition){reposition();});
+ } else {
+ show()();
+ }
+ }
+
+ function hideTooltipBind () {
+ scope.$apply(function () {
+ hide();
+ });
+ }
+
+ // Show the tooltip popup element.
+ function show() {
+
+
+ // Don't show empty tooltips.
+ if ( ! scope.tt_content ) {
+ return angular.noop;
+ }
+
+ createTooltip();
+
+ // If there is a pending remove transition, we must cancel it, lest the
+ // tooltip be mysteriously removed.
+ if ( transitionTimeout ) {
+ $timeout.cancel( transitionTimeout );
+ }
+
+ // Set the initial positioning.
+ tooltip.css({ top: 0, left: 0, display: 'block' });
+
+ // Now we add it to the DOM because need some info about it. But it's not
+ // visible yet anyway.
+ if ( appendToBody ) {
+ $document.find( 'body' ).append( tooltip );
+ } else {
+ element.after( tooltip );
+ }
+
+ positionTooltip();
+
+ // And show the tooltip.
+ scope.tt_isOpen = true;
+ scope.$digest(); // digest required as $apply is not called
+
+ // Return positioning function as promise callback for correct
+ // positioning after draw.
+ return positionTooltip;
+ }
+
+ // Hide the tooltip popup element.
+ function hide() {
+ // First things first: we don't show it anymore.
+ scope.tt_isOpen = false;
+
+ //if tooltip is going to be shown after delay, we must cancel this
+ $timeout.cancel( popupTimeout );
+
+ // And now we remove it from the DOM. However, if we have animation, we
+ // need to wait for it to expire beforehand.
+ // FIXME: this is a placeholder for a port of the transitions library.
+ if ( scope.tt_animation ) {
+ transitionTimeout = $timeout(removeTooltip, 500);
+ } else {
+ removeTooltip();
+ }
+ }
+
+ function createTooltip() {
+ // There can only be one tooltip element per directive shown at once.
+ if (tooltip) {
+ removeTooltip();
+ }
+ tooltip = tooltipLinker(scope, function () {});
+
+ // Get contents rendered into the tooltip
+ scope.$digest();
+ }
+
+ function removeTooltip() {
+ if (tooltip) {
+ tooltip.remove();
+ tooltip = null;
+ }
+ }
+
+ /**
+ * Observe the relevant attributes.
+ */
+ attrs.$observe( type, function ( val ) {
+ scope.tt_content = val;
+
+ if (!val && scope.tt_isOpen ) {
+ hide();
+ }
+ });
+
+ attrs.$observe( prefix+'Title', function ( val ) {
+ scope.tt_title = val;
+ });
+
+ attrs[prefix+'Placement'] = attrs[prefix+'Placement'] || null;
+
+ attrs.$observe( prefix+'Placement', function ( val ) {
+ scope.tt_placement = angular.isDefined( val ) && val ? val : options.placement;
+ });
+
+ attrs[prefix+'PopupDelay'] = attrs[prefix+'PopupDelay'] || null;
+
+ attrs.$observe( prefix+'PopupDelay', function ( val ) {
+ var delay = parseInt( val, 10 );
+ scope.tt_popupDelay = ! isNaN(delay) ? delay : options.popupDelay;
+ });
+
+ var unregisterTriggers = function() {
+ if ( hasRegisteredTriggers ) {
+ if ( angular.isFunction( triggers.show ) ) {
+ unregisterTriggerFunction();
+ } else {
+ element.unbind( triggers.show, showTooltipBind );
+ element.unbind( triggers.hide, hideTooltipBind );
+ }
+ }
+ };
+
+ var unregisterTriggerFunction = function () {};
+
+ attrs[prefix+'Trigger'] = attrs[prefix+'Trigger'] || null;
+
+ attrs.$observe( prefix+'Trigger', function ( val ) {
+ unregisterTriggers();
+ unregisterTriggerFunction();
+
+ triggers = getTriggers( val );
+
+ if ( angular.isFunction( triggers.show ) ) {
+ unregisterTriggerFunction = scope.$watch( function () {
+ return triggers.show( scope, element, attrs );
+ }, function ( val ) {
+ return val ? $timeout( show ) : $timeout( hide );
+ });
+ } else {
+ if ( triggers.show === triggers.hide ) {
+ element.bind( triggers.show, toggleTooltipBind );
+ } else {
+ element.bind( triggers.show, showTooltipBind );
+ element.bind( triggers.hide, hideTooltipBind );
+ }
+ }
+
+ hasRegisteredTriggers = true;
+ });
+
+ var animation = scope.$eval(attrs[prefix + 'Animation']);
+ scope.tt_animation = angular.isDefined(animation) ? !!animation : options.animation;
+
+ attrs.$observe( prefix+'AppendToBody', function ( val ) {
+ appendToBody = angular.isDefined( val ) ? $parse( val )( scope ) : appendToBody;
+ });
+
+ // if a tooltip is attached to we need to remove it on
+ // location change as its parent scope will probably not be destroyed
+ // by the change.
+ if ( appendToBody ) {
+ scope.$on('$locationChangeSuccess', function closeTooltipOnLocationChangeSuccess () {
+ if ( scope.tt_isOpen ) {
+ hide();
+ }
+ });
+ }
+
+ // Make sure tooltip is destroyed and removed.
+ scope.$on('$destroy', function onDestroyTooltip() {
+ $timeout.cancel( transitionTimeout );
+ $timeout.cancel( popupTimeout );
+ unregisterTriggers();
+ unregisterTriggerFunction();
+ removeTooltip();
+ });
+ };
+ }
+ };
+ };
+ }];
+})
+
+.directive( 'tooltipPopup', function () {
+ return {
+ restrict: 'EA',
+ replace: true,
+ scope: { content: '@', placement: '@', animation: '&', isOpen: '&' },
+ templateUrl: 'template/tooltip/tooltip-popup.html'
+ };
+})
+
+.directive( 'tooltip', [ '$tooltip', function ( $tooltip ) {
+ return $tooltip( 'tooltip', 'tooltip', 'mouseenter' );
+}])
+
+.directive( 'tooltipHtmlUnsafePopup', function () {
+ return {
+ restrict: 'EA',
+ replace: true,
+ scope: { content: '@', placement: '@', animation: '&', isOpen: '&' },
+ templateUrl: 'template/tooltip/tooltip-html-unsafe-popup.html'
+ };
+})
+
+.directive( 'tooltipHtmlUnsafe', [ '$tooltip', function ( $tooltip ) {
+ return $tooltip( 'tooltipHtmlUnsafe', 'tooltip', 'mouseenter' );
+}]);
+
+/**
+ * The following features are still outstanding: popup delay, animation as a
+ * function, placement as a function, inside, support for more triggers than
+ * just mouse enter/leave, html popovers, and selector delegatation.
+ */
+angular.module( 'mm.foundation.popover', [ 'mm.foundation.tooltip' ] )
+
+.directive( 'popoverPopup', function () {
+ return {
+ restrict: 'EA',
+ replace: true,
+ scope: { title: '@', content: '@', placement: '@', animation: '&', isOpen: '&' },
+ templateUrl: 'template/popover/popover.html'
+ };
+})
+
+.directive( 'popover', [ '$tooltip', function ( $tooltip ) {
+ return $tooltip( 'popover', 'popover', 'click' );
+}]);
+
+angular.module('mm.foundation.progressbar', ['mm.foundation.transition'])
+
+.constant('progressConfig', {
+ animate: true,
+ max: 100
+})
+
+.controller('ProgressController', ['$scope', '$attrs', 'progressConfig', '$transition', function($scope, $attrs, progressConfig, $transition) {
+ var self = this,
+ bars = [],
+ max = angular.isDefined($attrs.max) ? $scope.$parent.$eval($attrs.max) : progressConfig.max,
+ animate = angular.isDefined($attrs.animate) ? $scope.$parent.$eval($attrs.animate) : progressConfig.animate;
+
+ this.addBar = function(bar, element) {
+ var oldValue = 0, index = bar.$parent.$index;
+ if ( angular.isDefined(index) && bars[index] ) {
+ oldValue = bars[index].value;
+ }
+ bars.push(bar);
+
+ this.update(element, bar.value, oldValue);
+
+ bar.$watch('value', function(value, oldValue) {
+ if (value !== oldValue) {
+ self.update(element, value, oldValue);
+ }
+ });
+
+ bar.$on('$destroy', function() {
+ self.removeBar(bar);
+ });
+ };
+
+ // Update bar element width
+ this.update = function(element, newValue, oldValue) {
+ var percent = this.getPercentage(newValue);
+
+ if (animate) {
+ element.css('width', this.getPercentage(oldValue) + '%');
+ $transition(element, {width: percent + '%'});
+ } else {
+ element.css({'transition': 'none', 'width': percent + '%'});
+ }
+ };
+
+ this.removeBar = function(bar) {
+ bars.splice(bars.indexOf(bar), 1);
+ };
+
+ this.getPercentage = function(value) {
+ return Math.round(100 * value / max);
+ };
+}])
+
+.directive('progress', function() {
+ return {
+ restrict: 'EA',
+ replace: true,
+ transclude: true,
+ controller: 'ProgressController',
+ require: 'progress',
+ scope: {},
+ template: '
'
+ //templateUrl: 'template/progressbar/progress.html' // Works in AngularJS 1.2
+ };
+})
+
+.directive('bar', function() {
+ return {
+ restrict: 'EA',
+ replace: true,
+ transclude: true,
+ require: '^progress',
+ scope: {
+ value: '=',
+ type: '@'
+ },
+ templateUrl: 'template/progressbar/bar.html',
+ link: function(scope, element, attrs, progressCtrl) {
+ progressCtrl.addBar(scope, element);
+ }
+ };
+})
+
+.directive('progressbar', function() {
+ return {
+ restrict: 'EA',
+ replace: true,
+ transclude: true,
+ controller: 'ProgressController',
+ scope: {
+ value: '=',
+ type: '@'
+ },
+ templateUrl: 'template/progressbar/progressbar.html',
+ link: function(scope, element, attrs, progressCtrl) {
+ progressCtrl.addBar(scope, angular.element(element.children()[0]));
+ }
+ };
+});
+
+angular.module('mm.foundation.rating', [])
+
+.constant('ratingConfig', {
+ max: 5,
+ stateOn: null,
+ stateOff: null
+})
+
+.controller('RatingController', ['$scope', '$attrs', '$parse', 'ratingConfig', function($scope, $attrs, $parse, ratingConfig) {
+
+ this.maxRange = angular.isDefined($attrs.max) ? $scope.$parent.$eval($attrs.max) : ratingConfig.max;
+ this.stateOn = angular.isDefined($attrs.stateOn) ? $scope.$parent.$eval($attrs.stateOn) : ratingConfig.stateOn;
+ this.stateOff = angular.isDefined($attrs.stateOff) ? $scope.$parent.$eval($attrs.stateOff) : ratingConfig.stateOff;
+
+ this.createRateObjects = function(states) {
+ var defaultOptions = {
+ stateOn: this.stateOn,
+ stateOff: this.stateOff
+ };
+
+ for (var i = 0, n = states.length; i < n; i++) {
+ states[i] = angular.extend({ index: i }, defaultOptions, states[i]);
+ }
+ return states;
+ };
+
+ // Get objects used in template
+ $scope.range = angular.isDefined($attrs.ratingStates) ? this.createRateObjects(angular.copy($scope.$parent.$eval($attrs.ratingStates))): this.createRateObjects(new Array(this.maxRange));
+
+ $scope.rate = function(value) {
+ if ( $scope.value !== value && !$scope.readonly ) {
+ $scope.value = value;
+ }
+ };
+
+ $scope.enter = function(value) {
+ if ( ! $scope.readonly ) {
+ $scope.val = value;
+ }
+ $scope.onHover({value: value});
+ };
+
+ $scope.reset = function() {
+ $scope.val = angular.copy($scope.value);
+ $scope.onLeave();
+ };
+
+ $scope.$watch('value', function(value) {
+ $scope.val = value;
+ });
+
+ $scope.readonly = false;
+ if ($attrs.readonly) {
+ $scope.$parent.$watch($parse($attrs.readonly), function(value) {
+ $scope.readonly = !!value;
+ });
+ }
+}])
+
+.directive('rating', function() {
+ return {
+ restrict: 'EA',
+ scope: {
+ value: '=',
+ onHover: '&',
+ onLeave: '&'
+ },
+ controller: 'RatingController',
+ templateUrl: 'template/rating/rating.html',
+ replace: true
+ };
+});
+
+
+/**
+ * @ngdoc overview
+ * @name mm.foundation.tabs
+ *
+ * @description
+ * AngularJS version of the tabs directive.
+ */
+
+angular.module('mm.foundation.tabs', [])
+
+.controller('TabsetController', ['$scope', function TabsetCtrl($scope) {
+ var ctrl = this,
+ tabs = ctrl.tabs = $scope.tabs = [];
+
+ if (angular.isUndefined($scope.openOnLoad)) { $scope.openOnLoad = true; }
+
+ ctrl.select = function(tab) {
+ angular.forEach(tabs, function(tab) {
+ tab.active = false;
+ });
+ tab.active = true;
+ };
+
+ ctrl.addTab = function addTab(tab) {
+ tabs.push(tab);
+ if ($scope.openOnLoad && (tabs.length === 1 || tab.active)) {
+ ctrl.select(tab);
+ }
+ };
+
+ ctrl.removeTab = function removeTab(tab) {
+ var index = tabs.indexOf(tab);
+ //Select a new tab if the tab to be removed is selected
+ if (tab.active && tabs.length > 1) {
+ //If this is the last tab, select the previous tab. else, the next tab.
+ var newActiveIndex = index == tabs.length - 1 ? index - 1 : index + 1;
+ ctrl.select(tabs[newActiveIndex]);
+ }
+ tabs.splice(index, 1);
+ };
+}])
+
+/**
+ * @ngdoc directive
+ * @name mm.foundation.tabs.directive:tabset
+ * @restrict EA
+ *
+ * @description
+ * Tabset is the outer container for the tabs directive
+ *
+ * @param {boolean=} vertical Whether or not to use vertical styling for the tabs.
+ * @param {boolean=} justified Whether or not to use justified styling for the tabs.
+ *
+ * @example
+
+
+
+ First Content!
+ Second Content!
+
+
+
+ First Vertical Content!
+ Second Vertical Content!
+
+
+ First Justified Content!
+ Second Justified Content!
+
+
+
+ */
+.directive('tabset', function() {
+ return {
+ restrict: 'EA',
+ transclude: true,
+ replace: true,
+ scope: {
+ openOnLoad: '=?'
+ },
+ controller: 'TabsetController',
+ templateUrl: 'template/tabs/tabset.html',
+ link: function(scope, element, attrs) {
+ scope.vertical = angular.isDefined(attrs.vertical) ? scope.$parent.$eval(attrs.vertical) : false;
+ scope.justified = angular.isDefined(attrs.justified) ? scope.$parent.$eval(attrs.justified) : false;
+ scope.type = angular.isDefined(attrs.type) ? scope.$parent.$eval(attrs.type) : 'tabs';
+ }
+ };
+})
+
+/**
+ * @ngdoc directive
+ * @name mm.foundation.tabs.directive:tab
+ * @restrict EA
+ *
+ * @param {string=} heading The visible heading, or title, of the tab. Set HTML headings with {@link mm.foundation.tabs.directive:tabHeading tabHeading}.
+ * @param {string=} select An expression to evaluate when the tab is selected.
+ * @param {boolean=} active A binding, telling whether or not this tab is selected.
+ * @param {boolean=} disabled A binding, telling whether or not this tab is disabled.
+ *
+ * @description
+ * Creates a tab with a heading and content. Must be placed within a {@link mm.foundation.tabs.directive:tabset tabset}.
+ *
+ * @example
+
+
+
+
+ Select item 1, using active binding
+
+
+ Enable/disable item 2, using disabled binding
+
+
+
+ First Tab
+
+ Alert me!
+ Second Tab, with alert callback and html heading!
+
+
+ {{item.content}}
+
+
+
+
+
+ function TabsDemoCtrl($scope) {
+ $scope.items = [
+ { title:"Dynamic Title 1", content:"Dynamic Item 0" },
+ { title:"Dynamic Title 2", content:"Dynamic Item 1", disabled: true }
+ ];
+
+ $scope.alertMe = function() {
+ setTimeout(function() {
+ alert("You've selected the alert tab!");
+ });
+ };
+ };
+
+
+ */
+
+/**
+ * @ngdoc directive
+ * @name mm.foundation.tabs.directive:tabHeading
+ * @restrict EA
+ *
+ * @description
+ * Creates an HTML heading for a {@link mm.foundation.tabs.directive:tab tab}. Must be placed as a child of a tab element.
+ *
+ * @example
+
+
+
+
+ HTML in my titles?!
+ And some content, too!
+
+
+ Icon heading?!?
+ That's right.
+
+
+
+
+ */
+.directive('tab', ['$parse', function($parse) {
+ return {
+ require: '^tabset',
+ restrict: 'EA',
+ replace: true,
+ templateUrl: 'template/tabs/tab.html',
+ transclude: true,
+ scope: {
+ heading: '@',
+ onSelect: '&select', //This callback is called in contentHeadingTransclude
+ //once it inserts the tab's content into the dom
+ onDeselect: '&deselect'
+ },
+ controller: function() {
+ //Empty controller so other directives can require being 'under' a tab
+ },
+ compile: function(elm, attrs, transclude) {
+ return function postLink(scope, elm, attrs, tabsetCtrl) {
+ var getActive, setActive;
+ if (attrs.active) {
+ getActive = $parse(attrs.active);
+ setActive = getActive.assign;
+ scope.$parent.$watch(getActive, function updateActive(value, oldVal) {
+ // Avoid re-initializing scope.active as it is already initialized
+ // below. (watcher is called async during init with value ===
+ // oldVal)
+ if (value !== oldVal) {
+ scope.active = !!value;
+ }
+ });
+ scope.active = getActive(scope.$parent);
+ } else {
+ setActive = getActive = angular.noop;
+ }
+
+ scope.$watch('active', function(active) {
+ if( !angular.isFunction(setActive) ){
+ return;
+ }
+ // Note this watcher also initializes and assigns scope.active to the
+ // attrs.active expression.
+ setActive(scope.$parent, active);
+ if (active) {
+ tabsetCtrl.select(scope);
+ scope.onSelect();
+ } else {
+ scope.onDeselect();
+ }
+ });
+
+ scope.disabled = false;
+ if ( attrs.disabled ) {
+ scope.$parent.$watch($parse(attrs.disabled), function(value) {
+ scope.disabled = !! value;
+ });
+ }
+
+ scope.select = function() {
+ if ( ! scope.disabled ) {
+ scope.active = true;
+ }
+ };
+
+ tabsetCtrl.addTab(scope);
+ scope.$on('$destroy', function() {
+ tabsetCtrl.removeTab(scope);
+ });
+
+
+ //We need to transclude later, once the content container is ready.
+ //when this link happens, we're inside a tab heading.
+ scope.$transcludeFn = transclude;
+ };
+ }
+ };
+}])
+
+.directive('tabHeadingTransclude', [function() {
+ return {
+ restrict: 'A',
+ require: '^tab',
+ link: function(scope, elm, attrs, tabCtrl) {
+ scope.$watch('headingElement', function updateHeadingElement(heading) {
+ if (heading) {
+ elm.html('');
+ elm.append(heading);
+ }
+ });
+ }
+ };
+}])
+
+.directive('tabContentTransclude', function() {
+ return {
+ restrict: 'A',
+ require: '^tabset',
+ link: function(scope, elm, attrs) {
+ var tab = scope.$eval(attrs.tabContentTransclude);
+
+ //Now our tab is ready to be transcluded: both the tab heading area
+ //and the tab content area are loaded. Transclude 'em both.
+ tab.$transcludeFn(tab.$parent, function(contents) {
+ angular.forEach(contents, function(node) {
+ if (isTabHeading(node)) {
+ //Let tabHeadingTransclude know.
+ tab.headingElement = node;
+ } else {
+ elm.append(node);
+ }
+ });
+ });
+ }
+ };
+ function isTabHeading(node) {
+ return node.tagName && (
+ node.hasAttribute('tab-heading') ||
+ node.hasAttribute('data-tab-heading') ||
+ node.tagName.toLowerCase() === 'tab-heading' ||
+ node.tagName.toLowerCase() === 'data-tab-heading'
+ );
+ }
+})
+
+;
+
+angular.module("mm.foundation.topbar", ['mm.foundation.mediaQueries'])
+.factory('closest', [function() {
+ return function(el, selector) {
+ var matchesSelector = function (node, selector) {
+ var nodes = (node.parentNode || node.document).querySelectorAll(selector);
+ var i = -1;
+ while (nodes[++i] && nodes[i] != node){}
+ return !!nodes[i];
+ };
+
+ var element = el[0];
+ while (element) {
+ if (matchesSelector(element, selector)) {
+ return angular.element(element);
+ } else {
+ element = element.parentElement;
+ }
+ }
+ return false;
+ };
+}])
+.directive('topBar', ['$timeout','$compile', '$window', '$document', 'mediaQueries',
+ function($timeout, $compile, $window, $document, mediaQueries) {
+ return {
+ scope: {
+ stickyClass : '@',
+ backText: '@',
+ stickyOn : '=',
+ customBackText: '=',
+ isHover: '=',
+ mobileShowParentLink: '=',
+ scrolltop : '=',
+ },
+ restrict: 'EA',
+ replace: true,
+ templateUrl: 'template/topbar/top-bar.html',
+ transclude: true,
+ controller: ['$window', '$scope', 'closest', function($window, $scope, closest) {
+ $scope.settings = {};
+ $scope.settings.stickyClass = $scope.stickyClass || 'sticky';
+ $scope.settings.backText = $scope.backText || 'Back';
+ $scope.settings.stickyOn = $scope.stickyOn || 'all';
+
+ $scope.settings.customBackText = $scope.customBackText === undefined ? true : $scope.customBackText;
+ $scope.settings.isHover = $scope.isHover === undefined ? true : $scope.isHover;
+ $scope.settings.mobileShowParentLink = $scope.mobileShowParentLink === undefined ? true : $scope.mobileShowParentLink;
+ $scope.settings.scrolltop = $scope.scrolltop === undefined ? true : $scope.scrolltop; // jump to top when sticky nav menu toggle is clicked
+
+ this.settings = $scope.settings;
+
+ $scope.index = 0;
+
+ var outerHeight = function(el) {
+ var height = el.offsetHeight;
+ var style = el.currentStyle || getComputedStyle(el);
+
+ height += parseInt(style.marginTop, 10) + parseInt(style.marginBottom, 10);
+ return height;
+ };
+
+
+ var sections = [];
+
+ this.addSection = function(section) {
+ sections.push(section);
+ };
+
+ this.removeSection = function(section) {
+ var index = sections.indexOf(section);
+ if (index > -1) {
+ sections.splice(index, 1);
+ }
+ };
+
+ var dir = /rtl/i.test($document.find('html').attr('dir')) ? 'right' : 'left';
+
+ $scope.$watch('index', function(index) {
+ for(var i = 0; i < sections.length; i++){
+ sections[i].move(dir, index);
+ }
+ });
+
+ this.toggle = function(on) {
+ $scope.toggle(on);
+ for(var i = 0; i < sections.length; i++){
+ sections[i].reset();
+ }
+ $scope.index = 0;
+ $scope.height = '';
+ $scope.$apply();
+ };
+
+ this.back = function(event) {
+ if($scope.index < 1 || !mediaQueries.topbarBreakpoint()){
+ return;
+ }
+
+ var $link = angular.element(event.currentTarget);
+ var $movedLi = closest($link, 'li.moved');
+ var $previousLevelUl = $movedLi.parent();
+ $scope.index = $scope.index -1;
+
+ if($scope.index === 0){
+ $scope.height = '';
+ } else {
+ $scope.height = $scope.originalHeight + outerHeight($previousLevelUl[0]);
+ }
+
+ $timeout(function() {
+ $movedLi.removeClass('moved');
+ }, 300);
+ };
+
+ this.forward = function(event) {
+ if(!mediaQueries.topbarBreakpoint()){
+ return false;
+ }
+
+ var $link = angular.element(event.currentTarget);
+ var $selectedLi = closest($link, 'li');
+ $selectedLi.addClass('moved');
+ $scope.height = $scope.originalHeight + outerHeight($link.parent()[0].querySelector('ul'));
+ $scope.index = $scope.index + 1;
+ $scope.$apply();
+ };
+
+ }],
+ link: function(scope, element, attrs) {
+ var topbar = scope.topbar = element;
+ var topbarContainer = topbar.parent();
+ var body = angular.element($document[0].querySelector('body'));
+ var lastBreakpoint = mediaQueries.topbarBreakpoint();
+
+ var isSticky = scope.isSticky = function() {
+ var sticky = topbarContainer.hasClass(scope.settings.stickyClass);
+ if (sticky && scope.settings.stickyOn === 'all') {
+ return true;
+ } else if (sticky && mediaQueries.small() && scope.settings.stickyOn === 'small') {
+ return true;
+ } else if (sticky && mediaQueries.medium() && scope.settings.stickyOn === 'medium') {
+ return true;
+ } else if (sticky && mediaQueries.large() && scope.settings.stickyOn === 'large') {
+ return true;
+ }
+ return false;
+ };
+
+ var updateStickyPositioning = function() {
+ if (!scope.stickyTopbar || !scope.isSticky()) {
+ return;
+ }
+
+ var distance = stickyoffset;
+
+ if ($window.pageYOffset > distance && !topbarContainer.hasClass('fixed')) {
+ topbarContainer.addClass('fixed');
+ body.css('padding-top', scope.originalHeight + 'px');
+ } else if ($window.pageYOffset <= distance && topbarContainer.hasClass('fixed')) {
+ topbarContainer.removeClass('fixed');
+ body.css('padding-top', '');
+ }
+ };
+
+ var onResize = function() {
+ var currentBreakpoint = mediaQueries.topbarBreakpoint();
+ if(lastBreakpoint === currentBreakpoint){
+ return;
+ }
+ lastBreakpoint = mediaQueries.topbarBreakpoint();
+
+ topbar.removeClass('expanded');
+ topbar.parent().removeClass('expanded');
+ scope.height = '';
+
+ var sections = angular.element(topbar[0].querySelectorAll('section'));
+ angular.forEach(sections, function(section) {
+ angular.element(section.querySelectorAll('li.moved')).removeClass('moved');
+ });
+
+ scope.$apply();
+ };
+
+ var onScroll = function() {
+ updateStickyPositioning();
+ scope.$apply();
+ };
+
+ scope.toggle = function(on) {
+ if(!mediaQueries.topbarBreakpoint()){
+ return false;
+ }
+
+ var expand = (on === undefined) ? !topbar.hasClass('expanded') : on;
+
+ if (expand) {
+ topbar.addClass('expanded');
+ }
+ else {
+ topbar.removeClass('expanded');
+ }
+
+ if (scope.settings.scrolltop) {
+ if (!expand && topbar.hasClass('fixed')) {
+ topbar.parent().addClass('fixed');
+ topbar.removeClass('fixed');
+ body.css('padding-top', scope.originalHeight + 'px');
+ } else if (expand && topbar.parent().hasClass('fixed')) {
+ topbar.parent().removeClass('fixed');
+ topbar.addClass('fixed');
+ body.css('padding-top', '');
+ $window.scrollTo(0,0);
+ }
+ } else {
+ if(isSticky()) {
+ topbar.parent().addClass('fixed');
+ }
+
+ if(topbar.parent().hasClass('fixed')) {
+ if (!expand) {
+ topbar.removeClass('fixed');
+ topbar.parent().removeClass('expanded');
+ updateStickyPositioning();
+ } else {
+ topbar.addClass('fixed');
+ topbar.parent().addClass('expanded');
+ body.css('padding-top', scope.originalHeight + 'px');
+ }
+ }
+ }
+ };
+
+ if(topbarContainer.hasClass('fixed') || isSticky() ) {
+ scope.stickyTopbar = true;
+ scope.height = topbarContainer[0].offsetHeight;
+ var stickyoffset = topbarContainer[0].getBoundingClientRect().top + $window.pageYOffset;
+ } else {
+ scope.height = topbar[0].offsetHeight;
+ }
+
+ scope.originalHeight = scope.height;
+
+ scope.$watch('height', function(h) {
+ if(h){
+ topbar.css('height', h + 'px');
+ } else {
+ topbar.css('height', '');
+ }
+ });
+
+ angular.element($window).bind('resize', onResize);
+ angular.element($window).bind('scroll', onScroll);
+
+ scope.$on('$destroy', function() {
+ angular.element($window).unbind('resize', onResize);
+ angular.element($window).unbind('scroll', onScroll);
+ });
+
+ if (topbarContainer.hasClass('fixed')) {
+ body.css('padding-top', scope.originalHeight + 'px');
+ }
+ }
+ };
+ }]
+)
+.directive('toggleTopBar', ['closest', function (closest) {
+ return {
+ scope: {},
+ require: '^topBar',
+ restrict: 'A',
+ replace: true,
+ templateUrl: 'template/topbar/toggle-top-bar.html',
+ transclude: true,
+ link: function(scope, element, attrs, topBar) {
+ element.bind('click', function(event) {
+ var li = closest(angular.element(event.currentTarget), 'li');
+ if(!li.hasClass('back') && !li.hasClass('has-dropdown')) {
+ topBar.toggle();
+ }
+ });
+
+ scope.$on('$destroy', function() {
+ element.unbind('click');
+ });
+ }
+ };
+}])
+.directive('topBarSection', ['$compile', 'closest', function($compile, closest) {
+ return {
+ scope: {},
+ require: '^topBar',
+ restrict: 'EA',
+ replace: true,
+ templateUrl: 'template/topbar/top-bar-section.html',
+ transclude: true,
+ link: function(scope, element, attrs, topBar) {
+ var section = element;
+
+ scope.reset = function() {
+ angular.element(section[0].querySelectorAll('li.moved')).removeClass('moved');
+ };
+
+ scope.move = function(dir, index) {
+ if(dir === 'left'){
+ section.css({"left": index * -100 + '%'});
+ }
+ else {
+ section.css({"right": index * -100 + '%'});
+ }
+ };
+
+ topBar.addSection(scope);
+
+ scope.$on("$destroy", function() {
+ topBar.removeSection(scope);
+ });
+
+ // Top level links close menu on click
+ var links = section[0].querySelectorAll('li>a');
+ angular.forEach(links, function(link) {
+ var $link = angular.element(link);
+ var li = closest($link, 'li');
+ if (li.hasClass('has-dropdown') || li.hasClass('back') || li.hasClass('title')) {
+ return;
+ }
+
+ $link.bind('click', function() {
+ topBar.toggle(false);
+ });
+
+ scope.$on('$destroy', function() {
+ $link.bind('click');
+ });
+ });
+ }
+ };
+}])
+.directive('hasDropdown', ['mediaQueries', function (mediaQueries) {
+ return {
+ scope: {},
+ require: '^topBar',
+ restrict: 'A',
+ templateUrl: 'template/topbar/has-dropdown.html',
+ replace: true,
+ transclude: true,
+ link: function(scope, element, attrs, topBar) {
+ scope.triggerLink = element.children('a')[0];
+
+ var $link = angular.element(scope.triggerLink);
+
+ $link.bind('click', function(event) {
+ topBar.forward(event);
+ });
+ scope.$on('$destroy', function() {
+ $link.unbind('click');
+ });
+
+ element.bind('mouseenter', function() {
+ if(topBar.settings.isHover && !mediaQueries.topbarBreakpoint()){
+ element.addClass('not-click');
+ }
+ });
+ element.bind('click', function(event) {
+ if(!topBar.settings.isHover && !mediaQueries.topbarBreakpoint()){
+ element.toggleClass('not-click');
+ }
+ });
+
+ element.bind('mouseleave', function() {
+ element.removeClass('not-click');
+ });
+
+ scope.$on('$destroy', function() {
+ element.unbind('click');
+ element.unbind('mouseenter');
+ element.unbind('mouseleave');
+ });
+ },
+ controller: ['$window', '$scope', function($window, $scope) {
+ this.triggerLink = $scope.triggerLink;
+ }]
+ };
+}])
+.directive('topBarDropdown', ['$compile', function($compile) {
+ return {
+ scope: {},
+ require: ['^topBar', '^hasDropdown'],
+ restrict: 'A',
+ replace: true,
+ templateUrl: 'template/topbar/top-bar-dropdown.html',
+ transclude: true,
+ link: function(scope, element, attrs, ctrls) {
+ var topBar = ctrls[0];
+ var hasDropdown = ctrls[1];
+ var $link = angular.element(hasDropdown.triggerLink);
+ var url = $link.attr('href');
+ var $titleLi;
+
+ scope.linkText = $link.text();
+
+ scope.back = function(event) {
+ topBar.back(event);
+ };
+
+ // Add back link
+ if (topBar.settings.customBackText) {
+ scope.backText = topBar.settings.backText;
+ } else {
+ scope.backText = '« ' + $link.html();
+ }
+
+ if (topBar.settings.mobileShowParentLink && url && url.length > 1) {
+ $titleLi = angular.element('' +
+ ' ' +
+ '{{linkText}} ');
+ } else {
+ $titleLi = angular.element('' +
+ ' ');
+ }
+
+ $compile($titleLi)(scope);
+ element.prepend($titleLi);
+ }
+ };
+}]);
+
+angular.module( 'mm.foundation.tour', [ 'mm.foundation.position', 'mm.foundation.tooltip' ] )
+
+.service( '$tour', [ '$window', function ( $window ) {
+ var currentIndex = getStoredCurrentStep();
+ var ended = false;
+ var steps = {};
+
+ function getStoredCurrentStep() {
+ try {
+ return parseInt( $window.localStorage.getItem( 'mm.tour.step' ), 10 );
+ } catch(e) {
+ if (e.name !== "SecurityError") {
+ throw e;
+ }
+ }
+ }
+
+ function storeCurrentStep() {
+ try {
+ $window.localStorage.setItem( 'mm.tour.step', currentIndex );
+ } catch(e) {
+ if (e.name !== "SecurityError") {
+ throw e;
+ }
+ }
+ }
+
+ function setCurrentStep(step) {
+ currentIndex = step;
+ storeCurrentStep();
+ }
+
+ this.add = function ( index, attrs ) {
+ steps[ index ] = attrs;
+ };
+
+ this.has = function ( index ) {
+ return !!steps[ index ];
+ };
+
+ this.isActive = function () {
+ return currentIndex > 0;
+ };
+
+ this.current = function ( index ) {
+ if ( index ) {
+ setCurrentStep( currentIndex );
+ } else {
+ return currentIndex;
+ }
+ };
+
+ this.start = function () {
+ setCurrentStep( 1 );
+ };
+
+ this.next = function () {
+ setCurrentStep( currentIndex + 1 );
+ };
+
+ this.end = function () {
+ setCurrentStep( 0 );
+ };
+}])
+
+.directive( 'stepTextPopup', ['$tour', function ( $tour ) {
+ return {
+ restrict: 'EA',
+ replace: true,
+ scope: { title: '@', content: '@', placement: '@', animation: '&', isOpen: '&' },
+ templateUrl: 'template/tour/tour.html',
+ link: function (scope, element) {
+ scope.isLastStep = function () {
+ return !$tour.has( $tour.current() + 1 );
+ };
+
+ scope.endTour = function () {
+ element.remove();
+ $tour.end();
+ };
+
+ scope.nextStep = function () {
+ element.remove();
+ $tour.next();
+ };
+
+ scope.$on('$locationChangeSuccess', scope.endTour);
+ }
+ };
+}])
+
+.directive( 'stepText', [ '$position', '$tooltip', '$tour', '$window', function ( $position, $tooltip, $tour, $window ) {
+ function isElementInViewport( element ) {
+ var rect = element[0].getBoundingClientRect();
+
+ return (
+ rect.top >= 0 &&
+ rect.left >= 0 &&
+ rect.bottom <= ($window.innerHeight - 80) &&
+ rect.right <= $window.innerWidth
+ );
+ }
+
+ function show( scope, element, attrs ) {
+ var index = parseInt( attrs.stepIndex, 10);
+
+ if ( $tour.isActive() && index ) {
+ $tour.add( index, attrs );
+
+ if ( index === $tour.current() ) {
+ if ( !isElementInViewport( element ) ) {
+ var offset = $position.offset( element );
+ $window.scrollTo( 0, offset.top - $window.innerHeight / 2 );
+ }
+
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ return $tooltip( 'stepText', 'step', show );
+}]);
+
+angular.module('mm.foundation.typeahead', ['mm.foundation.position', 'mm.foundation.bindHtml'])
+
+/**
+ * A helper service that can parse typeahead's syntax (string provided by users)
+ * Extracted to a separate service for ease of unit testing
+ */
+ .factory('typeaheadParser', ['$parse', function ($parse) {
+
+ // 00000111000000000000022200000000000000003333333333333330000000000044000
+ var TYPEAHEAD_REGEXP = /^\s*(.*?)(?:\s+as\s+(.*?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+(.*)$/;
+
+ return {
+ parse:function (input) {
+
+ var match = input.match(TYPEAHEAD_REGEXP);
+ if (!match) {
+ throw new Error(
+ "Expected typeahead specification in form of '_modelValue_ (as _label_)? for _item_ in _collection_'" +
+ " but got '" + input + "'.");
+ }
+
+ return {
+ itemName:match[3],
+ source:$parse(match[4]),
+ viewMapper:$parse(match[2] || match[1]),
+ modelMapper:$parse(match[1])
+ };
+ }
+ };
+}])
+
+ .directive('typeahead', ['$compile', '$parse', '$q', '$timeout', '$document', '$position', 'typeaheadParser',
+ function ($compile, $parse, $q, $timeout, $document, $position, typeaheadParser) {
+
+ var HOT_KEYS = [9, 13, 27, 38, 40];
+
+ return {
+ require:'ngModel',
+ link:function (originalScope, element, attrs, modelCtrl) {
+
+ //SUPPORTED ATTRIBUTES (OPTIONS)
+
+ //minimal no of characters that needs to be entered before typeahead kicks-in
+ var minSearch = originalScope.$eval(attrs.typeaheadMinLength) || 1;
+
+ //minimal wait time after last character typed before typehead kicks-in
+ var waitTime = originalScope.$eval(attrs.typeaheadWaitMs) || 0;
+
+ //should it restrict model values to the ones selected from the popup only?
+ var isEditable = originalScope.$eval(attrs.typeaheadEditable) !== false;
+
+ //binding to a variable that indicates if matches are being retrieved asynchronously
+ var isLoadingSetter = $parse(attrs.typeaheadLoading).assign || angular.noop;
+
+ //a callback executed when a match is selected
+ var onSelectCallback = $parse(attrs.typeaheadOnSelect);
+
+ var inputFormatter = attrs.typeaheadInputFormatter ? $parse(attrs.typeaheadInputFormatter) : undefined;
+
+ var appendToBody = attrs.typeaheadAppendToBody ? $parse(attrs.typeaheadAppendToBody) : false;
+
+ //INTERNAL VARIABLES
+
+ //model setter executed upon match selection
+ var $setModelValue = $parse(attrs.ngModel).assign;
+
+ //expressions used by typeahead
+ var parserResult = typeaheadParser.parse(attrs.typeahead);
+
+ var hasFocus;
+
+ //pop-up element used to display matches
+ var popUpEl = angular.element('
');
+ popUpEl.attr({
+ matches: 'matches',
+ active: 'activeIdx',
+ select: 'select(activeIdx)',
+ query: 'query',
+ position: 'position'
+ });
+ //custom item template
+ if (angular.isDefined(attrs.typeaheadTemplateUrl)) {
+ popUpEl.attr('template-url', attrs.typeaheadTemplateUrl);
+ }
+
+ //create a child scope for the typeahead directive so we are not polluting original scope
+ //with typeahead-specific data (matches, query etc.)
+ var scope = originalScope.$new();
+ originalScope.$on('$destroy', function(){
+ scope.$destroy();
+ });
+
+ var resetMatches = function() {
+ scope.matches = [];
+ scope.activeIdx = -1;
+ };
+
+ var getMatchesAsync = function(inputValue) {
+
+ var locals = {$viewValue: inputValue};
+ isLoadingSetter(originalScope, true);
+ $q.when(parserResult.source(originalScope, locals)).then(function(matches) {
+
+ //it might happen that several async queries were in progress if a user were typing fast
+ //but we are interested only in responses that correspond to the current view value
+ if (inputValue === modelCtrl.$viewValue && hasFocus) {
+ if (matches.length > 0) {
+
+ scope.activeIdx = 0;
+ scope.matches.length = 0;
+
+ //transform labels
+ for(var i=0; i= minSearch) {
+ if (waitTime > 0) {
+ if (timeoutPromise) {
+ $timeout.cancel(timeoutPromise);//cancel previous timeout
+ }
+ timeoutPromise = $timeout(function () {
+ getMatchesAsync(inputValue);
+ }, waitTime);
+ } else {
+ getMatchesAsync(inputValue);
+ }
+ } else {
+ isLoadingSetter(originalScope, false);
+ resetMatches();
+ }
+
+ if (isEditable) {
+ return inputValue;
+ } else {
+ if (!inputValue) {
+ // Reset in case user had typed something previously.
+ modelCtrl.$setValidity('editable', true);
+ return inputValue;
+ } else {
+ modelCtrl.$setValidity('editable', false);
+ return undefined;
+ }
+ }
+ });
+
+ modelCtrl.$formatters.push(function (modelValue) {
+
+ var candidateViewValue, emptyViewValue;
+ var locals = {};
+
+ if (inputFormatter) {
+
+ locals['$model'] = modelValue;
+ return inputFormatter(originalScope, locals);
+
+ } else {
+
+ //it might happen that we don't have enough info to properly render input value
+ //we need to check for this situation and simply return model value if we can't apply custom formatting
+ locals[parserResult.itemName] = modelValue;
+ candidateViewValue = parserResult.viewMapper(originalScope, locals);
+ locals[parserResult.itemName] = undefined;
+ emptyViewValue = parserResult.viewMapper(originalScope, locals);
+
+ return candidateViewValue!== emptyViewValue ? candidateViewValue : modelValue;
+ }
+ });
+
+ scope.select = function (activeIdx) {
+ //called from within the $digest() cycle
+ var locals = {};
+ var model, item;
+
+ locals[parserResult.itemName] = item = scope.matches[activeIdx].model;
+ model = parserResult.modelMapper(originalScope, locals);
+ $setModelValue(originalScope, model);
+ modelCtrl.$setValidity('editable', true);
+
+ onSelectCallback(originalScope, {
+ $item: item,
+ $model: model,
+ $label: parserResult.viewMapper(originalScope, locals)
+ });
+
+ resetMatches();
+
+ //return focus to the input element if a mach was selected via a mouse click event
+ element[0].focus();
+ };
+
+ //bind keyboard events: arrows up(38) / down(40), enter(13) and tab(9), esc(27)
+ element.bind('keydown', function (evt) {
+
+ //typeahead is open and an "interesting" key was pressed
+ if (scope.matches.length === 0 || HOT_KEYS.indexOf(evt.which) === -1) {
+ return;
+ }
+
+ evt.preventDefault();
+
+ if (evt.which === 40) {
+ scope.activeIdx = (scope.activeIdx + 1) % scope.matches.length;
+ scope.$digest();
+
+ } else if (evt.which === 38) {
+ scope.activeIdx = (scope.activeIdx ? scope.activeIdx : scope.matches.length) - 1;
+ scope.$digest();
+
+ } else if (evt.which === 13 || evt.which === 9) {
+ scope.$apply(function () {
+ scope.select(scope.activeIdx);
+ });
+
+ } else if (evt.which === 27) {
+ evt.stopPropagation();
+
+ resetMatches();
+ scope.$digest();
+ }
+ });
+
+ element.bind('blur', function (evt) {
+ hasFocus = false;
+ });
+
+ element.bind('focus', function (evt) {
+ hasFocus = true;
+ });
+
+ // Keep reference to click handler to unbind it.
+ var dismissClickHandler = function (evt) {
+ if (element[0] !== evt.target) {
+ resetMatches();
+ scope.$digest();
+ }
+ };
+
+ $document.bind('click', dismissClickHandler);
+
+ originalScope.$on('$destroy', function(){
+ $document.unbind('click', dismissClickHandler);
+ });
+
+ var $popup = $compile(popUpEl)(scope);
+ if ( appendToBody ) {
+ $document.find('body').append($popup);
+ } else {
+ element.after($popup);
+ }
+ }
+ };
+
+}])
+
+ .directive('typeaheadPopup', function () {
+ return {
+ restrict:'EA',
+ scope:{
+ matches:'=',
+ query:'=',
+ active:'=',
+ position:'=',
+ select:'&'
+ },
+ replace:true,
+ templateUrl:'template/typeahead/typeahead-popup.html',
+ link:function (scope, element, attrs) {
+
+ scope.templateUrl = attrs.templateUrl;
+
+ scope.isOpen = function () {
+ return scope.matches.length > 0;
+ };
+
+ scope.isActive = function (matchIdx) {
+ return scope.active == matchIdx;
+ };
+
+ scope.selectActive = function (matchIdx) {
+ scope.active = matchIdx;
+ };
+
+ scope.selectMatch = function (activeIdx) {
+ scope.select({activeIdx:activeIdx});
+ };
+ }
+ };
+ })
+
+ .directive('typeaheadMatch', ['$http', '$templateCache', '$compile', '$parse', function ($http, $templateCache, $compile, $parse) {
+ return {
+ restrict:'EA',
+ scope:{
+ index:'=',
+ match:'=',
+ query:'='
+ },
+ link:function (scope, element, attrs) {
+ var tplUrl = $parse(attrs.templateUrl)(scope.$parent) || 'template/typeahead/typeahead-match.html';
+ $http.get(tplUrl, {cache: $templateCache}).success(function(tplContent){
+ element.replaceWith($compile(tplContent.trim())(scope));
+ });
+ }
+ };
+ }])
+
+ .filter('typeaheadHighlight', function() {
+
+ function escapeRegexp(queryToEscape) {
+ return queryToEscape.replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1");
+ }
+
+ return function(matchItem, query) {
+ return query ? matchItem.replace(new RegExp(escapeRegexp(query), 'gi'), '$& ') : matchItem;
+ };
+ });
diff --git a/mm-foundation-0.9.2.min.js b/mm-foundation-0.9.2.min.js
new file mode 100644
index 0000000..dead293
--- /dev/null
+++ b/mm-foundation-0.9.2.min.js
@@ -0,0 +1,10 @@
+/*
+ * angular-mm-foundation
+ * http://pineconellc.github.io/angular-foundation/
+
+ * Version: 0.9.2 - 2016-05-16
+ * License: MIT
+ * (c) Pinecone, LLC
+ */
+angular.module("mm.foundation",["mm.foundation.accordion","mm.foundation.alert","mm.foundation.bindHtml","mm.foundation.buttons","mm.foundation.position","mm.foundation.mediaQueries","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"]),angular.module("mm.foundation.accordion",[]).constant("accordionConfig",{closeOthers:!0}).controller("AccordionController",["$scope","$attrs","accordionConfig",function(a,b,c){this.groups=[],this.closeOthers=function(d){var e=angular.isDefined(b.closeOthers)?a.$eval(b.closeOthers):c.closeOthers;e&&angular.forEach(this.groups,function(a){a!==d&&(a.isOpen=!1)})},this.addGroup=function(a){var b=this;this.groups.push(a),a.$on("$destroy",function(c){b.removeGroup(a)})},this.removeGroup=function(a){var b=this.groups.indexOf(a);-1!==b&&this.groups.splice(b,1)}}]).directive("accordion",function(){return{restrict:"EA",controller:"AccordionController",transclude:!0,replace:!1,templateUrl:"template/accordion/accordion.html"}}).directive("accordionGroup",["$parse",function(a){return{require:"^accordion",restrict:"EA",transclude:!0,replace:!0,templateUrl:"template/accordion/accordion-group.html",scope:{heading:"@",cbOpen:"&toggleOpen"},controller:function(){this.setHeading=function(a){this.heading=a}},link:function(b,c,d,e){var f,g;e.addGroup(b),b.isOpen=!1,d.isOpen&&(f=a(d.isOpen),g=f.assign,b.$parent.$watch(f,function(a){b.isOpen=!!a})),b.$watch("isOpen",function(a){a&&(e.closeOthers(b),b.cbOpen()),g&&g(b.$parent,a)})}}}]).directive("accordionHeading",function(){return{restrict:"EA",transclude:!0,template:"",replace:!0,require:"^accordionGroup",compile:function(a,b,c){return function(a,b,d,e){e.setHeading(c(a,function(){}))}}}}).directive("accordionTransclude",function(){return{require:"^accordionGroup",link:function(a,b,c,d){a.$watch(function(){return d[c.accordionTransclude]},function(a){a&&(b.html(""),b.append(a))})}}}),angular.module("mm.foundation.alert",[]).controller("AlertController",["$scope","$attrs",function(a,b){a.closeable="close"in b&&"undefined"!=typeof b.close}]).directive("alert",function(){return{restrict:"EA",controller:"AlertController",templateUrl:"template/alert/alert.html",transclude:!0,replace:!0,scope:{type:"=",close:"&"}}}),angular.module("mm.foundation.bindHtml",[]).directive("bindHtmlUnsafe",function(){return function(a,b,c){b.addClass("ng-binding").data("$binding",c.bindHtmlUnsafe),a.$watch(c.bindHtmlUnsafe,function(a){b.html(a||"")})}}),angular.module("mm.foundation.buttons",[]).constant("buttonConfig",{activeClass:"active",toggleEvent:"click"}).controller("ButtonsController",["buttonConfig",function(a){this.activeClass=a.activeClass,this.toggleEvent=a.toggleEvent}]).directive("btnRadio",function(){return{require:["btnRadio","ngModel"],controller:"ButtonsController",link:function(a,b,c,d){var e=d[0],f=d[1];f.$render=function(){b.toggleClass(e.activeClass,angular.equals(f.$modelValue,a.$eval(c.btnRadio)))},b.bind(e.toggleEvent,function(){b.hasClass(e.activeClass)||a.$apply(function(){f.$setViewValue(a.$eval(c.btnRadio)),f.$render()})})}}}).directive("btnCheckbox",function(){return{require:["btnCheckbox","ngModel"],controller:"ButtonsController",link:function(a,b,c,d){function e(){return g(c.btnCheckboxTrue,!0)}function f(){return g(c.btnCheckboxFalse,!1)}function g(b,c){var d=a.$eval(b);return angular.isDefined(d)?d:c}var h=d[0],i=d[1];i.$render=function(){b.toggleClass(h.activeClass,angular.equals(i.$modelValue,e()))},b.bind(h.toggleEvent,function(){a.$apply(function(){i.$setViewValue(b.hasClass(h.activeClass)?f():e()),i.$render()})})}}}),angular.module("mm.foundation.position",[]).factory("$position",["$document","$window",function(a,b){function c(a,c){return a.currentStyle?a.currentStyle[c]:b.getComputedStyle?b.getComputedStyle(a)[c]:a.style[c]}function d(a){return"static"===(c(a,"position")||"static")}var e=function(b){for(var c=a[0],e=b.offsetParent||c;e&&e!==c&&d(e);)e=e.offsetParent;return e||c};return{position:function(b){var c=this.offset(b),d={top:0,left:0},f=e(b[0]);f!=a[0]&&(d=this.offset(angular.element(f)),d.top+=f.clientTop-f.scrollTop,d.left+=f.clientLeft-f.scrollLeft);var g=b[0].getBoundingClientRect();return{width:g.width||b.prop("offsetWidth"),height:g.height||b.prop("offsetHeight"),top:c.top-d.top,left:c.left-d.left}},offset:function(c){var d=c[0].getBoundingClientRect();return{width:d.width||c.prop("offsetWidth"),height:d.height||c.prop("offsetHeight"),top:d.top+(b.pageYOffset||a[0].body.scrollTop||a[0].documentElement.scrollTop),left:d.left+(b.pageXOffset||a[0].body.scrollLeft||a[0].documentElement.scrollLeft)}}}}]),angular.module("mm.foundation.mediaQueries",[]).factory("matchMedia",["$document","$window",function(a,b){return b.matchMedia||function(a,b){var c,d=a.documentElement,e=d.firstElementChild||d.firstChild,f=a.createElement("body"),g=a.createElement("div");return g.id="mq-test-1",g.style.cssText="position:absolute;top:-100em",f.style.background="none",f.appendChild(g),function(a){return g.innerHTML='',d.insertBefore(f,e),c=42===g.offsetWidth,d.removeChild(f),{matches:c,media:a}}}(a[0])}]).factory("mediaQueries",["$document","matchMedia",function(a,b){var c=angular.element(a[0].querySelector("head"));c.append(' '),c.append(' '),c.append(' '),c.append(' ');var d=/^[\/\\'"]+|(;\s?})+|[\/\\'"]+$/g,e={topbar:getComputedStyle(c[0].querySelector("meta.foundation-mq-topbar")).fontFamily.replace(d,""),small:getComputedStyle(c[0].querySelector("meta.foundation-mq-small")).fontFamily.replace(d,""),medium:getComputedStyle(c[0].querySelector("meta.foundation-mq-medium")).fontFamily.replace(d,""),large:getComputedStyle(c[0].querySelector("meta.foundation-mq-large")).fontFamily.replace(d,"")};return{topbarBreakpoint:function(){return!b(e.topbar).matches},small:function(){return b(e.small).matches},medium:function(){return b(e.medium).matches},large:function(){return b(e.large).matches}}}]),angular.module("mm.foundation.dropdownToggle",["mm.foundation.position","mm.foundation.mediaQueries"]).controller("DropdownToggleController",["$scope","$attrs","mediaQueries",function(a,b,c){this.small=function(){return c.small()&&!c.medium()}}]).directive("dropdownToggle",["$document","$window","$location","$position",function(a,b,c,d){var e=null,f=angular.noop;return{restrict:"CA",controller:"DropdownToggleController",link:function(c,g,h,i){var j=g.parent(),k=angular.element(a[0].querySelector(h.dropdownToggle)),l=function(){return j.hasClass("has-dropdown")},m=function(c){k=angular.element(a[0].querySelector(h.dropdownToggle));var m=g===e;if(c.preventDefault(),c.stopPropagation(),e&&f(),!m&&!g.hasClass("disabled")&&!g.prop("disabled")){k.css("display","block"),k.addClass("f-open-dropdown");var n=d.offset(g),o=d.offset(angular.element(k[0].offsetParent)),p=k.prop("offsetWidth"),q={top:n.top-o.top+n.height+"px"};if(i.small())q.left=Math.max((o.width-p)/2,8)+"px",q.position="absolute",q.width="95%",q["max-width"]="none";else{var r=Math.round(n.left-o.left),s=b.innerWidth-p-8;r>s&&(r=s,k.removeClass("left").addClass("right")),q.left=r+"px",q.position=null,q["max-width"]=null}k.css(q),g.addClass("expanded"),l()&&j.addClass("hover"),e=g,f=function(b){a.off("click",f),k.css("display","none"),k.removeClass("f-open-dropdown"),g.removeClass("expanded"),f=angular.noop,e=null,j.hasClass("hover")&&j.removeClass("hover")},a.on("click",f)}};k&&k.css("display","none"),c.$watch("$location.path",function(){f()}),g.on("click",m),g.on("$destroy",function(){g.off("click",m)})}}}]),angular.module("mm.foundation.interchange",["mm.foundation.mediaQueries"]).factory("interchangeQueries",["$document",function(a){for(var b,c,d={"default":"only screen",landscape:"only screen and (orientation: landscape)",portrait:"only screen and (orientation: portrait)",retina:"only screen and (-webkit-min-device-pixel-ratio: 2),only screen and (min--moz-device-pixel-ratio: 2),only screen and (-o-min-device-pixel-ratio: 2/1),only screen and (min-device-pixel-ratio: 2),only screen and (min-resolution: 192dpi),only screen and (min-resolution: 2dppx)"},e="foundation-mq-",f=["small","medium","large","xlarge","xxlarge"],g=angular.element(a[0].querySelector("head")),h=0;h '),b=getComputedStyle(g[0].querySelector("meta."+e+f[h])),c=b.fontFamily.replace(/^[\/\\'"]+|(;\s?})+|[\/\\'"]+$/g,""),d[f[h]]=c;return d}]).factory("interchangeQueriesManager",["interchangeQueries",function(a){return{add:function(b,c){return b&&c&&angular.isString(b)&&angular.isString(c)&&!a[b]?(a[b]=c,!0):!1}}}]).factory("interchangeTools",["$window","matchMedia","interchangeQueries",function(a,b,c){var d=function(a){for(var b,c=a.split(/\[(.*?)\]/),d=c.length,e=/^(.+)\,\ \((.+)\)$/,f={};d--;)c[d].replace(/[\W\d]+/,"").length>4&&(b=e.exec(c[d]),b&&3===b.length&&(f[b[2]]=b[1]));return f},e=function(a){var d,e,f;for(d in a)if(e=c[d]||d,f=b(e),f.matches)return a[d]};return{parseAttribute:d,findCurrentMediaFile:e}}]).directive("interchange",["$window","$rootScope","interchangeTools",function(a,b,c){var d=/[A-Za-z0-9-_]+\.(jpg|jpeg|png|gif|bmp|tiff)\ *,/i;return{restrict:"A",scope:!0,priority:450,compile:function(e,f){return"DIV"!==e[0].nodeName||d.test(f.interchange)||e.html(' '),{pre:function(a,b,c){},post:function(d,e,f){var g,h;switch(h=e&&e[0]&&e[0].nodeName,d.fileMap=c.parseAttribute(f.interchange),h){case"DIV":g=c.findCurrentMediaFile(d.fileMap),/[A-Za-z0-9-_]+\.(jpg|jpeg|png|gif|bmp|tiff)$/i.test(g)?d.type="background":d.type="include";break;case"IMG":d.type="image";break;default:return}var i=function(a){var f=c.findCurrentMediaFile(d.fileMap);if(!d.currentFile||d.currentFile!==f){switch(d.currentFile=f,d.type){case"image":e.attr("src",d.currentFile);break;case"background":e.css("background-image","url("+d.currentFile+")")}b.$emit("replace",e,d),a&&d.$apply()}};i(),a.addEventListener("resize",i),d.$on("$destroy",function(){a.removeEventListener("resize",i)})}}}}}]),angular.module("mm.foundation.transition",[]).factory("$transition",["$q","$timeout","$rootScope",function(a,b,c){function d(a){for(var b in a)if(void 0!==f.style[b])return a[b]}var e=function(d,f,g){g=g||{};var h=a.defer(),i=e[g.animation?"animationEndEventName":"transitionEndEventName"],j=function(a){c.$apply(function(){d.unbind(i,j),h.resolve(d)})};return i&&d.bind(i,j),b(function(){angular.isString(f)?d.addClass(f):angular.isFunction(f)?f(d):angular.isObject(f)&&d.css(f),i||h.resolve(d)}),h.promise.cancel=function(){i&&d.unbind(i,j),h.reject("Transition cancelled")},h.promise},f=document.createElement("trans"),g={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd",transition:"transitionend"},h={WebkitTransition:"webkitAnimationEnd",MozTransition:"animationend",OTransition:"oAnimationEnd",transition:"animationend"};return e.transitionEndEventName=d(g),e.animationEndEventName=d(h),e}]),angular.module("mm.foundation.modal",["mm.foundation.transition"]).factory("$$stackedMap",function(){return{createNew:function(){var a=[];return{add:function(b,c){a.push({key:b,value:c})},get:function(b){for(var c=0;c0?c[0].querySelectorAll("[autofocus]")[0].focus():c[0].focus()})}}}]).factory("$modalStack",["$window","$transition","$timeout","$document","$compile","$rootScope","$$stackedMap",function(a,b,c,d,e,f,g){function h(){for(var a=-1,b=q.keys(),c=0;c0),j()})}function j(){if(m&&-1==h()){var a=n;k(m,n,150,function(){a.$destroy(),a=null}),m=void 0,n=void 0}}function k(a,d,e,f){function g(){g.done||(g.done=!0,a.remove(),f&&f())}d.animate=!1;var h=b.transitionEndEventName;if(h){var i=c(g,e);a.bind(h,function(){c.cancel(i),g(),d.$apply()})}else c(g,0)}function l(b,c){angular.isUndefined(c)&&(c=0);var d=a.pageYOffset||0;return c+d}var m,n,o,p="modal-open",q=g.createNew(),r={};return f.$watch(h,function(a){n&&(n.index=a)}),d.bind("keydown",function(a){var b;27===a.which&&(b=q.top(),b&&b.value.keyboard&&f.$apply(function(){r.dismiss(b.key)}))}),r.open=function(b,c){b.options={deferred:c.deferred,modalScope:c.scope,backdrop:c.backdrop,keyboard:c.keyboard,parent:c.parent},q.add(b,b.options);var g=d.find(c.parent).eq(0),i=h();i>=0&&!m&&(n=f.$new(!0),n.index=i,m=e("
")(n),g.append(m));var j=angular.element('
');g.append(j[0]),o=parseInt(a.getComputedStyle(j[0]).top)||0;var k=l(j,o);j.remove();var r=angular.element('
').attr({"window-class":c.windowClass,index:q.length()-1,animate:"animate"});r.html(c.content);var s=e(r)(c.scope);q.top().value.modalDomEl=s,g.append(s),g.addClass(p)},r.reposition=function(a){var b=q.get(a).value;if(b){var c=b.modalDomEl,d=l(c,o);c.css("top",d+"px")}},r.close=function(a,b){var c=q.get(a);c&&(c.value.deferred.resolve(b),i(a))},r.dismiss=function(a,b){var c=q.get(a);c&&(c.value.deferred.reject(b),i(a))},r.dismissAll=function(a){for(var b=this.getTop();b;)this.dismiss(b.key,a),b=this.getTop()},r.getTop=function(){return q.top()},r}]).provider("$modal",function(){var a={options:{backdrop:!0,keyboard:!0},$get:["$injector","$rootScope","$q","$http","$templateCache","$controller","$modalStack",function(b,c,d,e,f,g,h){function i(a){return a.template?d.when(a.template):e.get(a.templateUrl,{cache:f}).then(function(a){return a.data})}function j(a){var c=[];return angular.forEach(a,function(a,e){(angular.isFunction(a)||angular.isArray(a))&&c.push(d.when(b.invoke(a)))}),c}var k={};return k.open=function(b){var e=d.defer(),f=d.defer(),k={result:e.promise,opened:f.promise,close:function(a){h.close(k,a)},dismiss:function(a){h.dismiss(k,a)},reposition:function(){h.reposition(k)}};if(b=angular.extend({},a.options,b),b.resolve=b.resolve||{},!b.template&&!b.templateUrl)throw new Error("One of template or templateUrl options is required.");var l=d.all([i(b)].concat(j(b.resolve)));return l.then(function(a){var d=(b.scope||c).$new();d.$close=k.close,d.$dismiss=k.dismiss;var f,i={},j=1;b.controller&&(i.$scope=d,i.$modalInstance=k,angular.forEach(b.resolve,function(b,c){i[c]=a[j++]}),f=g(b.controller,i),b.controllerAs&&(d[b.controllerAs]=f)),h.open(k,{scope:d,deferred:e,content:a[0],backdrop:b.backdrop,keyboard:b.keyboard,windowClass:b.windowClass,parent:b.parent||"body"})},function(a){e.reject(a)}),l.then(function(){f.resolve(!0)},function(){f.reject(!1)}),k},k}]};return a}),angular.module("mm.foundation.offcanvas",[]).directive("offCanvasWrap",["$window",function(a){return{scope:{},restrict:"C",link:function(b,c,d){var e=angular.element(a),f=b.sidebar=c;b.hide=function(){f.removeClass("move-left"),f.removeClass("move-right")},e.bind("resize.body",b.hide),b.$on("$destroy",function(){e.unbind("resize.body",b.hide)})},controller:["$scope",function(a){this.leftToggle=function(){a.sidebar.toggleClass("move-right")},this.rightToggle=function(){a.sidebar.toggleClass("move-left")},this.hide=function(){a.hide()}}]}}]).directive("leftOffCanvasToggle",[function(){return{require:"^offCanvasWrap",restrict:"C",link:function(a,b,c,d){b.on("click",function(){d.leftToggle()})}}}]).directive("rightOffCanvasToggle",[function(){return{require:"^offCanvasWrap",restrict:"C",link:function(a,b,c,d){b.on("click",function(){d.rightToggle()})}}}]).directive("exitOffCanvas",[function(){return{require:"^offCanvasWrap",restrict:"C",link:function(a,b,c,d){b.on("click",function(){d.hide()})}}}]).directive("offCanvasList",[function(){return{require:"^offCanvasWrap",restrict:"C",link:function(a,b,c,d){b.on("click",function(){d.hide()})}}}]),angular.module("mm.foundation.pagination",[]).controller("PaginationController",["$scope","$attrs","$parse","$interpolate",function(a,b,c,d){var e=this,f=b.numPages?c(b.numPages).assign:angular.noop;this.init=function(d){b.itemsPerPage?a.$parent.$watch(c(b.itemsPerPage),function(b){e.itemsPerPage=parseInt(b,10),a.totalPages=e.calculateTotalPages()}):this.itemsPerPage=d},this.noPrevious=function(){return 1===this.page},this.noNext=function(){return this.page===a.totalPages},this.isActive=function(a){return this.page===a},this.calculateTotalPages=function(){var b=this.itemsPerPage<1?1:Math.ceil(a.totalItems/this.itemsPerPage);return Math.max(b||0,1)},this.getAttributeValue=function(b,c,e){return angular.isDefined(b)?e?d(b)(a.$parent):a.$parent.$eval(b):c},this.render=function(){this.page=parseInt(a.page,10)||1,this.page>0&&this.page<=a.totalPages&&(a.pages=this.getPages(this.page,a.totalPages))},a.selectPage=function(b){!e.isActive(b)&&b>0&&b<=a.totalPages&&(a.page=b,a.onSelectPage({page:b}))},a.$watch("page",function(){e.render()}),a.$watch("totalItems",function(){a.totalPages=e.calculateTotalPages()}),a.$watch("totalPages",function(b){f(a.$parent,b),e.page>b?a.selectPage(b):e.render()})}]).constant("paginationConfig",{itemsPerPage:10,boundaryLinks:!1,directionLinks:!0,firstText:"First",previousText:"Previous",nextText:"Next",lastText:"Last",rotate:!0}).directive("pagination",["$parse","paginationConfig",function(a,b){return{restrict:"EA",scope:{page:"=",totalItems:"=",onSelectPage:" &"},controller:"PaginationController",templateUrl:"template/pagination/pagination.html",replace:!0,link:function(c,d,e,f){function g(a,b,c,d){return{number:a,text:b,active:c,disabled:d}}var h,i=f.getAttributeValue(e.boundaryLinks,b.boundaryLinks),j=f.getAttributeValue(e.directionLinks,b.directionLinks),k=f.getAttributeValue(e.firstText,b.firstText,!0),l=f.getAttributeValue(e.previousText,b.previousText,!0),m=f.getAttributeValue(e.nextText,b.nextText,!0),n=f.getAttributeValue(e.lastText,b.lastText,!0),o=f.getAttributeValue(e.rotate,b.rotate);f.init(b.itemsPerPage),e.maxSize&&c.$parent.$watch(a(e.maxSize),function(a){h=parseInt(a,10),f.render()}),f.getPages=function(a,b){var c=[],d=1,e=b,p=angular.isDefined(h)&&b>h;p&&(o?(d=Math.max(a-Math.floor(h/2),1),e=d+h-1,e>b&&(e=b,d=e-h+1)):(d=(Math.ceil(a/h)-1)*h+1,e=Math.min(d+h-1,b)));for(var q=d;e>=q;q++){var r=g(q,q,f.isActive(q),!1);c.push(r)}if(p&&!o){if(d>1){var s=g(d-1,"...",!1,!1);c.unshift(s)}if(b>e){var t=g(e+1,"...",!1,!1);c.push(t)}}if(j){var u=g(a-1,l,!1,f.noPrevious());c.unshift(u);var v=g(a+1,m,!1,f.noNext());c.push(v)}if(i){var w=g(1,k,!1,f.noPrevious());c.unshift(w);var x=g(b,n,!1,f.noNext());c.push(x)}return c}}}}]).constant("pagerConfig",{itemsPerPage:10,previousText:"« Previous",nextText:"Next »",align:!0}).directive("pager",["pagerConfig",function(a){return{restrict:"EA",scope:{page:"=",totalItems:"=",onSelectPage:" &"},controller:"PaginationController",templateUrl:"template/pagination/pager.html",replace:!0,link:function(b,c,d,e){function f(a,b,c,d,e){return{number:a,text:b,disabled:c,previous:i&&d,next:i&&e}}var g=e.getAttributeValue(d.previousText,a.previousText,!0),h=e.getAttributeValue(d.nextText,a.nextText,!0),i=e.getAttributeValue(d.align,a.align);e.init(a.itemsPerPage),e.getPages=function(a){return[f(a-1,g,e.noPrevious(),!0,!1),f(a+1,h,e.noNext(),!1,!0)]}}}}]),angular.module("mm.foundation.tooltip",["mm.foundation.position","mm.foundation.bindHtml"]).provider("$tooltip",function(){function a(a){var b=/[A-Z]/g,c="-";return a.replace(b,function(a,b){return(b?c:"")+a.toLowerCase()})}var b={placement:"top",animation:!0,popupDelay:0},c={mouseenter:"mouseleave",click:"click",focus:"blur"},d={};this.options=function(a){angular.extend(d,a)},this.setTriggers=function(a){angular.extend(c,a)},this.$get=["$window","$compile","$timeout","$parse","$document","$position","$interpolate",function(e,f,g,h,i,j,k){return function(e,l,m){function n(a){var b=a||o.trigger||m,d=c[b]||b;return{show:b,hide:d}}var o=angular.extend({},b,d),p=a(e),q=k.startSymbol(),r=k.endSymbol(),s="
';return{restrict:"EA",scope:!0,compile:function(a,b){var c=f(s);return function(a,b,d){function f(){a.tt_isOpen?m():k()}function k(){(!z||a.$eval(d[l+"Enable"]))&&(a.tt_popupDelay?(v=g(p,a.tt_popupDelay,!1),v.then(function(a){a()})):p()())}function m(){a.$apply(function(){q()})}function p(){return a.tt_content?(r(),u&&g.cancel(u),t.css({top:0,left:0,display:"block"}),w?i.find("body").append(t):b.after(t),A(),a.tt_isOpen=!0,a.$digest(),A):angular.noop}function q(){a.tt_isOpen=!1,g.cancel(v),a.tt_animation?u=g(s,500):s()}function r(){t&&s(),t=c(a,function(){}),a.$digest()}function s(){t&&(t.remove(),t=null)}var t,u,v,w=angular.isDefined(o.appendToBody)?o.appendToBody:!1,x=n(void 0),y=!1,z=angular.isDefined(d[l+"Enable"]),A=function(){var c,d,e,f;switch(c=w?j.offset(b):j.position(b),d=t.prop("offsetWidth"),e=t.prop("offsetHeight"),a.tt_placement){case"right":f={top:c.top+c.height/2-e/2,left:c.left+c.width+10};break;case"bottom":f={top:c.top+c.height+10,left:c.left};break;case"left":f={top:c.top+c.height/2-e/2,left:c.left-d-10};break;default:f={top:c.top-e-10,left:c.left}}f.top+="px",f.left+="px",t.css(f)};a.tt_isOpen=!1,d.$observe(e,function(b){a.tt_content=b,!b&&a.tt_isOpen&&q()}),d.$observe(l+"Title",function(b){a.tt_title=b}),d[l+"Placement"]=d[l+"Placement"]||null,d.$observe(l+"Placement",function(b){a.tt_placement=angular.isDefined(b)&&b?b:o.placement}),d[l+"PopupDelay"]=d[l+"PopupDelay"]||null,d.$observe(l+"PopupDelay",function(b){var c=parseInt(b,10);a.tt_popupDelay=isNaN(c)?o.popupDelay:c});var B=function(){y&&(angular.isFunction(x.show)?C():(b.unbind(x.show,k),b.unbind(x.hide,m)))},C=function(){};d[l+"Trigger"]=d[l+"Trigger"]||null,d.$observe(l+"Trigger",function(c){B(),C(),x=n(c),angular.isFunction(x.show)?C=a.$watch(function(){return x.show(a,b,d)},function(a){return g(a?p:q)}):x.show===x.hide?b.bind(x.show,f):(b.bind(x.show,k),b.bind(x.hide,m)),y=!0});var D=a.$eval(d[l+"Animation"]);a.tt_animation=angular.isDefined(D)?!!D:o.animation,d.$observe(l+"AppendToBody",function(b){w=angular.isDefined(b)?h(b)(a):w}),w&&a.$on("$locationChangeSuccess",function(){a.tt_isOpen&&q()}),a.$on("$destroy",function(){g.cancel(u),g.cancel(v),B(),C(),s()})}}}}}]}).directive("tooltipPopup",function(){return{restrict:"EA",replace:!0,scope:{content:"@",placement:"@",animation:"&",isOpen:"&"},templateUrl:"template/tooltip/tooltip-popup.html"}}).directive("tooltip",["$tooltip",function(a){return a("tooltip","tooltip","mouseenter")}]).directive("tooltipHtmlUnsafePopup",function(){return{restrict:"EA",replace:!0,scope:{content:"@",placement:"@",animation:"&",isOpen:"&"},templateUrl:"template/tooltip/tooltip-html-unsafe-popup.html"}}).directive("tooltipHtmlUnsafe",["$tooltip",function(a){return a("tooltipHtmlUnsafe","tooltip","mouseenter")}]),angular.module("mm.foundation.popover",["mm.foundation.tooltip"]).directive("popoverPopup",function(){return{restrict:"EA",replace:!0,scope:{title:"@",content:"@",placement:"@",animation:"&",isOpen:"&"},templateUrl:"template/popover/popover.html"}}).directive("popover",["$tooltip",function(a){return a("popover","popover","click")}]),angular.module("mm.foundation.progressbar",["mm.foundation.transition"]).constant("progressConfig",{animate:!0,max:100}).controller("ProgressController",["$scope","$attrs","progressConfig","$transition",function(a,b,c,d){var e=this,f=[],g=angular.isDefined(b.max)?a.$parent.$eval(b.max):c.max,h=angular.isDefined(b.animate)?a.$parent.$eval(b.animate):c.animate;this.addBar=function(a,b){var c=0,d=a.$parent.$index;angular.isDefined(d)&&f[d]&&(c=f[d].value),f.push(a),this.update(b,a.value,c),a.$watch("value",function(a,c){a!==c&&e.update(b,a,c)}),a.$on("$destroy",function(){e.removeBar(a)})},this.update=function(a,b,c){var e=this.getPercentage(b);h?(a.css("width",this.getPercentage(c)+"%"),d(a,{width:e+"%"})):a.css({transition:"none",width:e+"%"})},this.removeBar=function(a){f.splice(f.indexOf(a),1)},this.getPercentage=function(a){return Math.round(100*a/g)}}]).directive("progress",function(){return{restrict:"EA",replace:!0,transclude:!0,controller:"ProgressController",require:"progress",scope:{},template:'
'}}).directive("bar",function(){return{restrict:"EA",replace:!0,transclude:!0,require:"^progress",scope:{value:"=",type:"@"},templateUrl:"template/progressbar/bar.html",link:function(a,b,c,d){d.addBar(a,b)}}}).directive("progressbar",function(){return{restrict:"EA",replace:!0,transclude:!0,controller:"ProgressController",scope:{value:"=",type:"@"},templateUrl:"template/progressbar/progressbar.html",link:function(a,b,c,d){d.addBar(a,angular.element(b.children()[0]))}}}),angular.module("mm.foundation.rating",[]).constant("ratingConfig",{max:5,stateOn:null,stateOff:null}).controller("RatingController",["$scope","$attrs","$parse","ratingConfig",function(a,b,c,d){this.maxRange=angular.isDefined(b.max)?a.$parent.$eval(b.max):d.max,this.stateOn=angular.isDefined(b.stateOn)?a.$parent.$eval(b.stateOn):d.stateOn,this.stateOff=angular.isDefined(b.stateOff)?a.$parent.$eval(b.stateOff):d.stateOff,this.createRateObjects=function(a){for(var b={stateOn:this.stateOn,stateOff:this.stateOff},c=0,d=a.length;d>c;c++)a[c]=angular.extend({index:c},b,a[c]);return a},a.range=angular.isDefined(b.ratingStates)?this.createRateObjects(angular.copy(a.$parent.$eval(b.ratingStates))):this.createRateObjects(new Array(this.maxRange)),a.rate=function(b){a.value===b||a.readonly||(a.value=b)},a.enter=function(b){a.readonly||(a.val=b),a.onHover({value:b})},a.reset=function(){a.val=angular.copy(a.value),a.onLeave()},a.$watch("value",function(b){a.val=b}),a.readonly=!1,b.readonly&&a.$parent.$watch(c(b.readonly),function(b){a.readonly=!!b})}]).directive("rating",function(){return{restrict:"EA",scope:{value:"=",onHover:"&",onLeave:"&"},controller:"RatingController",templateUrl:"template/rating/rating.html",replace:!0}}),angular.module("mm.foundation.tabs",[]).controller("TabsetController",["$scope",function(a){var b=this,c=b.tabs=a.tabs=[];angular.isUndefined(a.openOnLoad)&&(a.openOnLoad=!0),b.select=function(a){angular.forEach(c,function(a){a.active=!1}),a.active=!0},b.addTab=function(d){c.push(d),a.openOnLoad&&(1===c.length||d.active)&&b.select(d)},b.removeTab=function(a){var d=c.indexOf(a);if(a.active&&c.length>1){var e=d==c.length-1?d-1:d+1;b.select(c[e])}c.splice(d,1)}}]).directive("tabset",function(){return{restrict:"EA",transclude:!0,replace:!0,scope:{openOnLoad:"=?"},controller:"TabsetController",templateUrl:"template/tabs/tabset.html",link:function(a,b,c){a.vertical=angular.isDefined(c.vertical)?a.$parent.$eval(c.vertical):!1,a.justified=angular.isDefined(c.justified)?a.$parent.$eval(c.justified):!1,a.type=angular.isDefined(c.type)?a.$parent.$eval(c.type):"tabs"}}}).directive("tab",["$parse",function(a){return{require:"^tabset",restrict:"EA",replace:!0,templateUrl:"template/tabs/tab.html",transclude:!0,scope:{heading:"@",onSelect:"&select",onDeselect:"&deselect"},controller:function(){},compile:function(b,c,d){return function(b,c,e,f){var g,h;e.active?(g=a(e.active),h=g.assign,b.$parent.$watch(g,function(a,c){a!==c&&(b.active=!!a)}),b.active=g(b.$parent)):h=g=angular.noop,b.$watch("active",function(a){angular.isFunction(h)&&(h(b.$parent,a),a?(f.select(b),b.onSelect()):b.onDeselect())}),b.disabled=!1,e.disabled&&b.$parent.$watch(a(e.disabled),function(a){b.disabled=!!a}),b.select=function(){b.disabled||(b.active=!0)},f.addTab(b),b.$on("$destroy",function(){f.removeTab(b)}),b.$transcludeFn=d}}}}]).directive("tabHeadingTransclude",[function(){return{restrict:"A",require:"^tab",link:function(a,b,c,d){a.$watch("headingElement",function(a){a&&(b.html(""),b.append(a))})}}}]).directive("tabContentTransclude",function(){function a(a){return a.tagName&&(a.hasAttribute("tab-heading")||a.hasAttribute("data-tab-heading")||"tab-heading"===a.tagName.toLowerCase()||"data-tab-heading"===a.tagName.toLowerCase())}return{restrict:"A",require:"^tabset",link:function(b,c,d){var e=b.$eval(d.tabContentTransclude);e.$transcludeFn(e.$parent,function(b){angular.forEach(b,function(b){a(b)?e.headingElement=b:c.append(b)})})}}}),angular.module("mm.foundation.topbar",["mm.foundation.mediaQueries"]).factory("closest",[function(){return function(a,b){for(var c=function(a,b){for(var c=(a.parentNode||a.document).querySelectorAll(b),d=-1;c[++d]&&c[d]!=a;);return!!c[d]},d=a[0];d;){if(c(d,b))return angular.element(d);d=d.parentElement}return!1}}]).directive("topBar",["$timeout","$compile","$window","$document","mediaQueries",function(a,b,c,d,e){return{scope:{stickyClass:"@",backText:"@",stickyOn:"=",customBackText:"=",isHover:"=",mobileShowParentLink:"=",scrolltop:"="},restrict:"EA",replace:!0,templateUrl:"template/topbar/top-bar.html",transclude:!0,controller:["$window","$scope","closest",function(b,c,f){c.settings={},c.settings.stickyClass=c.stickyClass||"sticky",c.settings.backText=c.backText||"Back",c.settings.stickyOn=c.stickyOn||"all",c.settings.customBackText=void 0===c.customBackText?!0:c.customBackText,c.settings.isHover=void 0===c.isHover?!0:c.isHover,c.settings.mobileShowParentLink=void 0===c.mobileShowParentLink?!0:c.mobileShowParentLink,c.settings.scrolltop=void 0===c.scrolltop?!0:c.scrolltop,this.settings=c.settings,c.index=0;var g=function(a){var b=a.offsetHeight,c=a.currentStyle||getComputedStyle(a);return b+=parseInt(c.marginTop,10)+parseInt(c.marginBottom,10)},h=[];this.addSection=function(a){h.push(a)},this.removeSection=function(a){var b=h.indexOf(a);b>-1&&h.splice(b,1)};var i=/rtl/i.test(d.find("html").attr("dir"))?"right":"left";c.$watch("index",function(a){for(var b=0;bb&&!h.hasClass("fixed")?(h.addClass("fixed"),i.css("padding-top",a.originalHeight+"px")):c.pageYOffset<=b&&h.hasClass("fixed")&&(h.removeClass("fixed"),i.css("padding-top",""))}},m=function(){var b=e.topbarBreakpoint();if(j!==b){j=e.topbarBreakpoint(),g.removeClass("expanded"),g.parent().removeClass("expanded"),a.height="";var c=angular.element(g[0].querySelectorAll("section"));angular.forEach(c,function(a){angular.element(a.querySelectorAll("li.moved")).removeClass("moved")}),a.$apply()}},n=function(){l(),a.$apply()};if(a.toggle=function(b){if(!e.topbarBreakpoint())return!1;var d=void 0===b?!g.hasClass("expanded"):b;d?g.addClass("expanded"):g.removeClass("expanded"),a.settings.scrolltop?!d&&g.hasClass("fixed")?(g.parent().addClass("fixed"),g.removeClass("fixed"),i.css("padding-top",a.originalHeight+"px")):d&&g.parent().hasClass("fixed")&&(g.parent().removeClass("fixed"),g.addClass("fixed"),i.css("padding-top",""),c.scrollTo(0,0)):(k()&&g.parent().addClass("fixed"),g.parent().hasClass("fixed")&&(d?(g.addClass("fixed"),g.parent().addClass("expanded"),i.css("padding-top",a.originalHeight+"px")):(g.removeClass("fixed"),g.parent().removeClass("expanded"),l())))},h.hasClass("fixed")||k()){a.stickyTopbar=!0,a.height=h[0].offsetHeight;var o=h[0].getBoundingClientRect().top+c.pageYOffset}else a.height=g[0].offsetHeight;a.originalHeight=a.height,a.$watch("height",function(a){a?g.css("height",a+"px"):g.css("height","")}),angular.element(c).bind("resize",m),angular.element(c).bind("scroll",n),a.$on("$destroy",function(){angular.element(c).unbind("resize",m),angular.element(c).unbind("scroll",n)}),h.hasClass("fixed")&&i.css("padding-top",a.originalHeight+"px")}}}]).directive("toggleTopBar",["closest",function(a){return{scope:{},require:"^topBar",restrict:"A",replace:!0,templateUrl:"template/topbar/toggle-top-bar.html",transclude:!0,link:function(b,c,d,e){c.bind("click",function(b){var c=a(angular.element(b.currentTarget),"li");c.hasClass("back")||c.hasClass("has-dropdown")||e.toggle()}),b.$on("$destroy",function(){c.unbind("click")})}}}]).directive("topBarSection",["$compile","closest",function(a,b){return{scope:{},require:"^topBar",restrict:"EA",replace:!0,templateUrl:"template/topbar/top-bar-section.html",transclude:!0,link:function(a,c,d,e){var f=c;a.reset=function(){angular.element(f[0].querySelectorAll("li.moved")).removeClass("moved")},a.move=function(a,b){"left"===a?f.css({left:-100*b+"%"}):f.css({right:-100*b+"%"})},e.addSection(a),a.$on("$destroy",function(){e.removeSection(a)});var g=f[0].querySelectorAll("li>a");angular.forEach(g,function(c){var d=angular.element(c),f=b(d,"li");f.hasClass("has-dropdown")||f.hasClass("back")||f.hasClass("title")||(d.bind("click",function(){e.toggle(!1)}),a.$on("$destroy",function(){d.bind("click")}))})}}}]).directive("hasDropdown",["mediaQueries",function(a){return{scope:{},require:"^topBar",restrict:"A",templateUrl:"template/topbar/has-dropdown.html",replace:!0,transclude:!0,link:function(b,c,d,e){b.triggerLink=c.children("a")[0];var f=angular.element(b.triggerLink);f.bind("click",function(a){e.forward(a)}),b.$on("$destroy",function(){f.unbind("click")}),c.bind("mouseenter",function(){e.settings.isHover&&!a.topbarBreakpoint()&&c.addClass("not-click")}),c.bind("click",function(b){e.settings.isHover||a.topbarBreakpoint()||c.toggleClass("not-click")}),c.bind("mouseleave",function(){c.removeClass("not-click")}),b.$on("$destroy",function(){c.unbind("click"),c.unbind("mouseenter"),c.unbind("mouseleave")})},controller:["$window","$scope",function(a,b){this.triggerLink=b.triggerLink}]}}]).directive("topBarDropdown",["$compile",function(a){return{scope:{},require:["^topBar","^hasDropdown"],restrict:"A",replace:!0,templateUrl:"template/topbar/top-bar-dropdown.html",transclude:!0,link:function(b,c,d,e){var f,g=e[0],h=e[1],i=angular.element(h.triggerLink),j=i.attr("href");b.linkText=i.text(),b.back=function(a){g.back(a)},g.settings.customBackText?b.backText=g.settings.backText:b.backText="« "+i.html(),f=g.settings.mobileShowParentLink&&j&&j.length>1?angular.element('{{linkText}} '):angular.element(' '),a(f)(b),c.prepend(f)}}}]),angular.module("mm.foundation.tour",["mm.foundation.position","mm.foundation.tooltip"]).service("$tour",["$window",function(a){function b(){try{return parseInt(a.localStorage.getItem("mm.tour.step"),10)}catch(b){if("SecurityError"!==b.name)throw b}}function c(){try{a.localStorage.setItem("mm.tour.step",e)}catch(b){if("SecurityError"!==b.name)throw b}}function d(a){e=a,c()}var e=b(),f={};this.add=function(a,b){f[a]=b},this.has=function(a){return!!f[a]},this.isActive=function(){return e>0},this.current=function(a){return a?void d(e):e},this.start=function(){d(1)},this.next=function(){d(e+1)},this.end=function(){d(0)}}]).directive("stepTextPopup",["$tour",function(a){return{restrict:"EA",replace:!0,scope:{title:"@",content:"@",placement:"@",animation:"&",isOpen:"&"},templateUrl:"template/tour/tour.html",link:function(b,c){b.isLastStep=function(){return!a.has(a.current()+1)},b.endTour=function(){c.remove(),a.end()},b.nextStep=function(){c.remove(),a.next()},b.$on("$locationChangeSuccess",b.endTour)}}}]).directive("stepText",["$position","$tooltip","$tour","$window",function(a,b,c,d){function e(a){var b=a[0].getBoundingClientRect();return b.top>=0&&b.left>=0&&b.bottom<=d.innerHeight-80&&b.right<=d.innerWidth}function f(b,f,g){var h=parseInt(g.stepIndex,10);if(c.isActive()&&h&&(c.add(h,g),h===c.current())){if(!e(f)){var i=a.offset(f);d.scrollTo(0,i.top-d.innerHeight/2)}return!0}return!1}return b("stepText","step",f)}]),angular.module("mm.foundation.typeahead",["mm.foundation.position","mm.foundation.bindHtml"]).factory("typeaheadParser",["$parse",function(a){var b=/^\s*(.*?)(?:\s+as\s+(.*?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+(.*)$/;return{parse:function(c){var d=c.match(b);if(!d)throw new Error("Expected typeahead specification in form of '_modelValue_ (as _label_)? for _item_ in _collection_' but got '"+c+"'.");return{itemName:d[3],source:a(d[4]),viewMapper:a(d[2]||d[1]),modelMapper:a(d[1])}}}}]).directive("typeahead",["$compile","$parse","$q","$timeout","$document","$position","typeaheadParser",function(a,b,c,d,e,f,g){var h=[9,13,27,38,40];return{require:"ngModel",link:function(i,j,k,l){var m,n=i.$eval(k.typeaheadMinLength)||1,o=i.$eval(k.typeaheadWaitMs)||0,p=i.$eval(k.typeaheadEditable)!==!1,q=b(k.typeaheadLoading).assign||angular.noop,r=b(k.typeaheadOnSelect),s=k.typeaheadInputFormatter?b(k.typeaheadInputFormatter):void 0,t=k.typeaheadAppendToBody?b(k.typeaheadAppendToBody):!1,u=b(k.ngModel).assign,v=g.parse(k.typeahead),w=angular.element("
");w.attr({matches:"matches",active:"activeIdx",select:"select(activeIdx)",query:"query",position:"position"}),angular.isDefined(k.typeaheadTemplateUrl)&&w.attr("template-url",k.typeaheadTemplateUrl);var x=i.$new();i.$on("$destroy",function(){x.$destroy()});var y=function(){x.matches=[],x.activeIdx=-1},z=function(a){var b={$viewValue:a};q(i,!0),c.when(v.source(i,b)).then(function(c){if(a===l.$viewValue&&m)if(c.length>0){x.activeIdx=0,x.matches.length=0;for(var d=0;d=n?o>0?(A&&d.cancel(A),A=d(function(){z(a)},o)):z(a):(q(i,!1),y()),p?a:a?void l.$setValidity("editable",!1):(l.$setValidity("editable",!0),a)}),l.$formatters.push(function(a){var b,c,d={};return s?(d.$model=a,s(i,d)):(d[v.itemName]=a,b=v.viewMapper(i,d),d[v.itemName]=void 0,c=v.viewMapper(i,d),b!==c?b:a)}),x.select=function(a){var b,c,d={};d[v.itemName]=c=x.matches[a].model,b=v.modelMapper(i,d),u(i,b),l.$setValidity("editable",!0),r(i,{$item:c,$model:b,$label:v.viewMapper(i,d)}),y(),j[0].focus()},j.bind("keydown",function(a){0!==x.matches.length&&-1!==h.indexOf(a.which)&&(a.preventDefault(),40===a.which?(x.activeIdx=(x.activeIdx+1)%x.matches.length,x.$digest()):38===a.which?(x.activeIdx=(x.activeIdx?x.activeIdx:x.matches.length)-1,x.$digest()):13===a.which||9===a.which?x.$apply(function(){x.select(x.activeIdx)}):27===a.which&&(a.stopPropagation(),y(),x.$digest()))}),j.bind("blur",function(a){m=!1}),j.bind("focus",function(a){m=!0});var B=function(a){j[0]!==a.target&&(y(),x.$digest())};e.bind("click",B),i.$on("$destroy",function(){e.unbind("click",B)});var C=a(w)(x);t?e.find("body").append(C):j.after(C)}}}]).directive("typeaheadPopup",function(){return{restrict:"EA",scope:{matches:"=",query:"=",active:"=",position:"=",select:"&"},replace:!0,templateUrl:"template/typeahead/typeahead-popup.html",link:function(a,b,c){a.templateUrl=c.templateUrl,a.isOpen=function(){return a.matches.length>0},a.isActive=function(b){return a.active==b},a.selectActive=function(b){a.active=b},a.selectMatch=function(b){a.select({activeIdx:b})}}}}).directive("typeaheadMatch",["$http","$templateCache","$compile","$parse",function(a,b,c,d){return{restrict:"EA",scope:{index:"=",match:"=",query:"="},link:function(e,f,g){var h=d(g.templateUrl)(e.$parent)||"template/typeahead/typeahead-match.html";a.get(h,{cache:b}).success(function(a){f.replaceWith(c(a.trim())(e))})}}}]).filter("typeaheadHighlight",function(){function a(a){return a.replace(/([.?*+^$[\]\\(){}|-])/g,"\\$1")}return function(b,c){return c?b.replace(new RegExp(a(c),"gi"),"$& "):b}});
\ No newline at end of file
diff --git a/mm-foundation-tpls-0.9.2.js b/mm-foundation-tpls-0.9.2.js
new file mode 100644
index 0000000..0e5bdfd
--- /dev/null
+++ b/mm-foundation-tpls-0.9.2.js
@@ -0,0 +1,3818 @@
+/*
+ * angular-mm-foundation
+ * http://pineconellc.github.io/angular-foundation/
+
+ * Version: 0.9.2 - 2016-05-16
+ * License: MIT
+ * (c) Pinecone, LLC
+ */
+angular.module("mm.foundation", ["mm.foundation.tpls", "mm.foundation.accordion","mm.foundation.alert","mm.foundation.bindHtml","mm.foundation.buttons","mm.foundation.position","mm.foundation.mediaQueries","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"]);
+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/tabs/tabset.html.BACKUP.5992.html","template/tabs/tabset.html.BACKUP.7584.html","template/tabs/tabset.html.BACKUP.9724.html","template/tabs/tabset.html.BACKUP.9820.html","template/tabs/tabset.html.BASE.5992.html","template/tabs/tabset.html.BASE.7584.html","template/tabs/tabset.html.BASE.9724.html","template/tabs/tabset.html.BASE.9820.html","template/tabs/tabset.html.LOCAL.5992.html","template/tabs/tabset.html.LOCAL.7584.html","template/tabs/tabset.html.LOCAL.9724.html","template/tabs/tabset.html.LOCAL.9820.html","template/tabs/tabset.html.REMOTE.5992.html","template/tabs/tabset.html.REMOTE.7584.html","template/tabs/tabset.html.REMOTE.9724.html","template/tabs/tabset.html.REMOTE.9820.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"]);
+angular.module('mm.foundation.accordion', [])
+
+.constant('accordionConfig', {
+ closeOthers: true
+})
+
+.controller('AccordionController', ['$scope', '$attrs', 'accordionConfig', function ($scope, $attrs, accordionConfig) {
+
+ // This array keeps track of the accordion groups
+ this.groups = [];
+
+ // Ensure that all the groups in this accordion are closed, unless close-others explicitly says not to
+ this.closeOthers = function(openGroup) {
+ var closeOthers = angular.isDefined($attrs.closeOthers) ? $scope.$eval($attrs.closeOthers) : accordionConfig.closeOthers;
+ if ( closeOthers ) {
+ angular.forEach(this.groups, function (group) {
+ if ( group !== openGroup ) {
+ group.isOpen = false;
+ }
+ });
+ }
+ };
+
+ // This is called from the accordion-group directive to add itself to the accordion
+ this.addGroup = function(groupScope) {
+ var that = this;
+ this.groups.push(groupScope);
+
+ groupScope.$on('$destroy', function (event) {
+ that.removeGroup(groupScope);
+ });
+ };
+
+ // This is called from the accordion-group directive when to remove itself
+ this.removeGroup = function(group) {
+ var index = this.groups.indexOf(group);
+ if ( index !== -1 ) {
+ this.groups.splice(index, 1);
+ }
+ };
+
+}])
+
+// The accordion directive simply sets up the directive controller
+// and adds an accordion CSS class to itself element.
+.directive('accordion', function () {
+ return {
+ restrict:'EA',
+ controller:'AccordionController',
+ transclude: true,
+ replace: false,
+ templateUrl: 'template/accordion/accordion.html'
+ };
+})
+
+// The accordion-group directive indicates a block of html that will expand and collapse in an accordion
+.directive('accordionGroup', ['$parse', function($parse) {
+ return {
+ require:'^accordion', // We need this directive to be inside an accordion
+ restrict:'EA',
+ transclude:true, // It transcludes the contents of the directive into the template
+ replace: true, // The element containing the directive will be replaced with the template
+ templateUrl:'template/accordion/accordion-group.html',
+ scope: { // Create an isolated scope and interpolate the heading attribute onto this scope
+ heading: '@',
+ cbOpen: '&toggleOpen'
+ },
+ controller: function() {
+ this.setHeading = function(element) {
+ this.heading = element;
+ };
+ },
+ link: function(scope, element, attrs, accordionCtrl) {
+ var getIsOpen, setIsOpen;
+
+ accordionCtrl.addGroup(scope);
+
+ scope.isOpen = false;
+
+ if ( attrs.isOpen ) {
+ getIsOpen = $parse(attrs.isOpen);
+ setIsOpen = getIsOpen.assign;
+
+ scope.$parent.$watch(getIsOpen, function(value) {
+ scope.isOpen = !!value;
+ });
+ }
+
+ scope.$watch('isOpen', function(value) {
+ if ( value ) {
+ accordionCtrl.closeOthers(scope);
+ scope.cbOpen();
+ }
+ if ( setIsOpen ) {
+ setIsOpen(scope.$parent, value);
+ }
+ });
+ }
+ };
+}])
+
+// Use accordion-heading below an accordion-group to provide a heading containing HTML
+//
+// Heading containing HTML -
+//
+.directive('accordionHeading', function() {
+ return {
+ restrict: 'EA',
+ transclude: true, // Grab the contents to be used as the heading
+ template: '', // In effect remove this element!
+ replace: true,
+ require: '^accordionGroup',
+ compile: function(element, attr, transclude) {
+ return function link(scope, element, attr, accordionGroupCtrl) {
+ // Pass the heading to the accordion-group controller
+ // so that it can be transcluded into the right place in the template
+ // [The second parameter to transclude causes the elements to be cloned so that they work in ng-repeat]
+ accordionGroupCtrl.setHeading(transclude(scope, function() {}));
+ };
+ }
+ };
+})
+
+// Use in the accordion-group template to indicate where you want the heading to be transcluded
+// You must provide the property on the accordion-group controller that will hold the transcluded element
+//
+.directive('accordionTransclude', function() {
+ return {
+ require: '^accordionGroup',
+ link: function(scope, element, attr, controller) {
+ scope.$watch(function() { return controller[attr.accordionTransclude]; }, function(heading) {
+ if ( heading ) {
+ element.html('');
+ element.append(heading);
+ }
+ });
+ }
+ };
+});
+
+angular.module("mm.foundation.alert", [])
+
+.controller('AlertController', ['$scope', '$attrs', function ($scope, $attrs) {
+ $scope.closeable = 'close' in $attrs && typeof $attrs.close !== "undefined";
+}])
+
+.directive('alert', function () {
+ return {
+ restrict:'EA',
+ controller:'AlertController',
+ templateUrl:'template/alert/alert.html',
+ transclude:true,
+ replace:true,
+ scope: {
+ type: '=',
+ close: '&'
+ }
+ };
+});
+
+angular.module('mm.foundation.bindHtml', [])
+
+ .directive('bindHtmlUnsafe', function () {
+ return function (scope, element, attr) {
+ element.addClass('ng-binding').data('$binding', attr.bindHtmlUnsafe);
+ scope.$watch(attr.bindHtmlUnsafe, function bindHtmlUnsafeWatchAction(value) {
+ element.html(value || '');
+ });
+ };
+ });
+
+angular.module('mm.foundation.buttons', [])
+
+.constant('buttonConfig', {
+ activeClass: 'active',
+ toggleEvent: 'click'
+})
+
+.controller('ButtonsController', ['buttonConfig', function(buttonConfig) {
+ this.activeClass = buttonConfig.activeClass;
+ this.toggleEvent = buttonConfig.toggleEvent;
+}])
+
+.directive('btnRadio', function () {
+ return {
+ require: ['btnRadio', 'ngModel'],
+ controller: 'ButtonsController',
+ link: function (scope, element, attrs, ctrls) {
+ var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1];
+
+ //model -> UI
+ ngModelCtrl.$render = function () {
+ element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, scope.$eval(attrs.btnRadio)));
+ };
+
+ //ui->model
+ element.bind(buttonsCtrl.toggleEvent, function () {
+ if (!element.hasClass(buttonsCtrl.activeClass)) {
+ scope.$apply(function () {
+ ngModelCtrl.$setViewValue(scope.$eval(attrs.btnRadio));
+ ngModelCtrl.$render();
+ });
+ }
+ });
+ }
+ };
+})
+
+.directive('btnCheckbox', function () {
+ return {
+ require: ['btnCheckbox', 'ngModel'],
+ controller: 'ButtonsController',
+ link: function (scope, element, attrs, ctrls) {
+ var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1];
+
+ function getTrueValue() {
+ return getCheckboxValue(attrs.btnCheckboxTrue, true);
+ }
+
+ function getFalseValue() {
+ return getCheckboxValue(attrs.btnCheckboxFalse, false);
+ }
+
+ function getCheckboxValue(attributeValue, defaultValue) {
+ var val = scope.$eval(attributeValue);
+ return angular.isDefined(val) ? val : defaultValue;
+ }
+
+ //model -> UI
+ ngModelCtrl.$render = function () {
+ element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, getTrueValue()));
+ };
+
+ //ui->model
+ element.bind(buttonsCtrl.toggleEvent, function () {
+ scope.$apply(function () {
+ ngModelCtrl.$setViewValue(element.hasClass(buttonsCtrl.activeClass) ? getFalseValue() : getTrueValue());
+ ngModelCtrl.$render();
+ });
+ });
+ }
+ };
+});
+
+angular.module('mm.foundation.position', [])
+
+/**
+ * A set of utility methods that can be use to retrieve position of DOM elements.
+ * It is meant to be used where we need to absolute-position DOM elements in
+ * relation to other, existing elements (this is the case for tooltips, popovers,
+ * typeahead suggestions etc.).
+ */
+ .factory('$position', ['$document', '$window', function ($document, $window) {
+
+ function getStyle(el, cssprop) {
+ if (el.currentStyle) { //IE
+ return el.currentStyle[cssprop];
+ } else if ($window.getComputedStyle) {
+ return $window.getComputedStyle(el)[cssprop];
+ }
+ // finally try and get inline style
+ return el.style[cssprop];
+ }
+
+ /**
+ * Checks if a given element is statically positioned
+ * @param element - raw DOM element
+ */
+ function isStaticPositioned(element) {
+ return (getStyle(element, "position") || 'static' ) === 'static';
+ }
+
+ /**
+ * returns the closest, non-statically positioned parentOffset of a given element
+ * @param element
+ */
+ var parentOffsetEl = function (element) {
+ var docDomEl = $document[0];
+ var offsetParent = element.offsetParent || docDomEl;
+ while (offsetParent && offsetParent !== docDomEl && isStaticPositioned(offsetParent) ) {
+ offsetParent = offsetParent.offsetParent;
+ }
+ return offsetParent || docDomEl;
+ };
+
+ return {
+ /**
+ * Provides read-only equivalent of jQuery's position function:
+ * http://api.jquery.com/position/
+ */
+ position: function (element) {
+ var elBCR = this.offset(element);
+ var offsetParentBCR = { top: 0, left: 0 };
+ var offsetParentEl = parentOffsetEl(element[0]);
+ if (offsetParentEl != $document[0]) {
+ offsetParentBCR = this.offset(angular.element(offsetParentEl));
+ offsetParentBCR.top += offsetParentEl.clientTop - offsetParentEl.scrollTop;
+ offsetParentBCR.left += offsetParentEl.clientLeft - offsetParentEl.scrollLeft;
+ }
+
+ var boundingClientRect = element[0].getBoundingClientRect();
+ return {
+ width: boundingClientRect.width || element.prop('offsetWidth'),
+ height: boundingClientRect.height || element.prop('offsetHeight'),
+ top: elBCR.top - offsetParentBCR.top,
+ left: elBCR.left - offsetParentBCR.left
+ };
+ },
+
+ /**
+ * Provides read-only equivalent of jQuery's offset function:
+ * http://api.jquery.com/offset/
+ */
+ offset: function (element) {
+ var boundingClientRect = element[0].getBoundingClientRect();
+ return {
+ width: boundingClientRect.width || element.prop('offsetWidth'),
+ height: boundingClientRect.height || element.prop('offsetHeight'),
+ top: boundingClientRect.top + ($window.pageYOffset || $document[0].body.scrollTop || $document[0].documentElement.scrollTop),
+ left: boundingClientRect.left + ($window.pageXOffset || $document[0].body.scrollLeft || $document[0].documentElement.scrollLeft)
+ };
+ }
+ };
+ }]);
+
+angular.module("mm.foundation.mediaQueries", [])
+ .factory('matchMedia', ['$document', '$window', function($document, $window) {
+ // MatchMedia for IE <= 9
+ return $window.matchMedia || (function matchMedia(doc, undefined){
+ var bool,
+ docElem = doc.documentElement,
+ refNode = docElem.firstElementChild || docElem.firstChild,
+ // fakeBody required for
+ fakeBody = doc.createElement("body"),
+ div = doc.createElement("div");
+
+ div.id = "mq-test-1";
+ div.style.cssText = "position:absolute;top:-100em";
+ fakeBody.style.background = "none";
+ fakeBody.appendChild(div);
+
+ return function (q) {
+ div.innerHTML = "";
+ docElem.insertBefore(fakeBody, refNode);
+ bool = div.offsetWidth === 42;
+ docElem.removeChild(fakeBody);
+ return {
+ matches: bool,
+ media: q
+ };
+ };
+
+ }($document[0]));
+ }])
+ .factory('mediaQueries', ['$document', 'matchMedia', function($document, matchMedia) {
+ var head = angular.element($document[0].querySelector('head'));
+ head.append(' ');
+ head.append(' ');
+ head.append(' ');
+ head.append(' ');
+
+ var regex = /^[\/\\'"]+|(;\s?})+|[\/\\'"]+$/g;
+ var queries = {
+ topbar: getComputedStyle(head[0].querySelector('meta.foundation-mq-topbar')).fontFamily.replace(regex, ''),
+ small : getComputedStyle(head[0].querySelector('meta.foundation-mq-small')).fontFamily.replace(regex, ''),
+ medium : getComputedStyle(head[0].querySelector('meta.foundation-mq-medium')).fontFamily.replace(regex, ''),
+ large : getComputedStyle(head[0].querySelector('meta.foundation-mq-large')).fontFamily.replace(regex, '')
+ };
+
+ return {
+ topbarBreakpoint: function () {
+ return !matchMedia(queries.topbar).matches;
+ },
+ small: function () {
+ return matchMedia(queries.small).matches;
+ },
+ medium: function () {
+ return matchMedia(queries.medium).matches;
+ },
+ large: function () {
+ return matchMedia(queries.large).matches;
+ }
+ };
+ }]);
+
+/*
+ * dropdownToggle - Provides dropdown menu functionality
+ * @restrict class or attribute
+ * @example:
+
+ My Dropdown Menu
+
+ */
+angular.module('mm.foundation.dropdownToggle', [ 'mm.foundation.position', 'mm.foundation.mediaQueries' ])
+
+.controller('DropdownToggleController', ['$scope', '$attrs', 'mediaQueries', function($scope, $attrs, mediaQueries) {
+ this.small = function() {
+ return mediaQueries.small() && !mediaQueries.medium();
+ };
+}])
+
+.directive('dropdownToggle', ['$document', '$window', '$location', '$position', function ($document, $window, $location, $position) {
+ var openElement = null,
+ closeMenu = angular.noop;
+ return {
+ restrict: 'CA',
+ controller: 'DropdownToggleController',
+ link: function(scope, element, attrs, controller) {
+ var parent = element.parent(),
+ dropdown = angular.element($document[0].querySelector(attrs.dropdownToggle));
+
+ var parentHasDropdown = function() {
+ return parent.hasClass('has-dropdown');
+ };
+
+ var onClick = function (event) {
+ dropdown = angular.element($document[0].querySelector(attrs.dropdownToggle));
+ var elementWasOpen = (element === openElement);
+
+ event.preventDefault();
+ event.stopPropagation();
+
+ if (!!openElement) {
+ closeMenu();
+ }
+
+ if (!elementWasOpen && !element.hasClass('disabled') && !element.prop('disabled')) {
+ dropdown.css('display', 'block'); // We display the element so that offsetParent is populated
+ dropdown.addClass('f-open-dropdown');
+
+ var offset = $position.offset(element);
+ var parentOffset = $position.offset(angular.element(dropdown[0].offsetParent));
+ var dropdownWidth = dropdown.prop('offsetWidth');
+ var css = {
+ top: offset.top - parentOffset.top + offset.height + 'px'
+ };
+
+ if (controller.small()) {
+ css.left = Math.max((parentOffset.width - dropdownWidth) / 2, 8) + 'px';
+ css.position = 'absolute';
+ css.width = '95%';
+ css['max-width'] = 'none';
+ }
+ else {
+ var left = Math.round(offset.left - parentOffset.left);
+ var rightThreshold = $window.innerWidth - dropdownWidth - 8;
+ if (left > rightThreshold) {
+ left = rightThreshold;
+ dropdown.removeClass('left').addClass('right');
+ }
+ css.left = left + 'px';
+ css.position = null;
+ css['max-width'] = null;
+ }
+
+ dropdown.css(css);
+ element.addClass('expanded');
+
+ if (parentHasDropdown()) {
+ parent.addClass('hover');
+ }
+
+ openElement = element;
+
+ closeMenu = function (event) {
+ $document.off('click', closeMenu);
+ dropdown.css('display', 'none');
+ dropdown.removeClass('f-open-dropdown');
+ element.removeClass('expanded');
+ closeMenu = angular.noop;
+ openElement = null;
+ if (parent.hasClass('hover')) {
+ parent.removeClass('hover');
+ }
+ };
+ $document.on('click', closeMenu);
+ }
+ };
+
+ if (dropdown) {
+ dropdown.css('display', 'none');
+ }
+
+ scope.$watch('$location.path', function() { closeMenu(); });
+
+ element.on('click', onClick);
+ element.on('$destroy', function() {
+ element.off('click', onClick);
+ });
+ }
+ };
+}]);
+
+/**
+ * @ngdoc service
+ * @name mm.foundation.interchange
+ * @description
+ *
+ * Package containing all services and directives
+ * about the `interchange` module
+ */
+angular.module('mm.foundation.interchange', ['mm.foundation.mediaQueries'])
+
+ /**
+ * @ngdoc function
+ * @name mm.foundation.interchange.interchageQuery
+ * @function interchageQuery
+ * @description
+ *
+ * this service inject meta tags objects in the head
+ * to get the list of media queries from Foundation
+ * stylesheets.
+ *
+ * @return {object} Queries list name => mediaQuery
+ */
+ .factory('interchangeQueries', ['$document', function ($document) {
+ var element,
+ mediaSize,
+ formatList = {
+ 'default': 'only screen',
+ landscape : 'only screen and (orientation: landscape)',
+ portrait : 'only screen and (orientation: portrait)',
+ retina : 'only screen and (-webkit-min-device-pixel-ratio: 2),' +
+ 'only screen and (min--moz-device-pixel-ratio: 2),' +
+ 'only screen and (-o-min-device-pixel-ratio: 2/1),' +
+ 'only screen and (min-device-pixel-ratio: 2),' +
+ 'only screen and (min-resolution: 192dpi),' +
+ 'only screen and (min-resolution: 2dppx)'
+ },
+ classPrefix = 'foundation-mq-',
+ classList = ['small', 'medium', 'large', 'xlarge', 'xxlarge'],
+ head = angular.element($document[0].querySelector('head'));
+
+ for (var i = 0; i < classList.length; i++) {
+ head.append(' ');
+ element = getComputedStyle(head[0].querySelector('meta.' + classPrefix + classList[i]));
+ mediaSize = element.fontFamily.replace(/^[\/\\'"]+|(;\s?})+|[\/\\'"]+$/g, '');
+ formatList[classList[i]] = mediaSize;
+ }
+ return formatList;
+ }])
+
+ /**
+ * @ngdoc function
+ * @name mm.foundation.interchange.interchangeQueriesManager
+ * @function interchangeQueriesManager
+ * @description
+ *
+ * interface to add and remove named queries
+ * in the interchangeQueries list
+ */
+ .factory('interchangeQueriesManager', ['interchangeQueries', function (interchangeQueries) {
+ return {
+ /**
+ * @ngdoc method
+ * @name interchangeQueriesManager#add
+ * @methodOf mm.foundation.interchange.interchangeQueriesManager
+ * @description
+ *
+ * Add a custom media query in the `interchangeQueries`
+ * factory. This method does not allow to update an existing
+ * media query.
+ *
+ * @param {string} name MediaQuery name
+ * @param {string} media MediaQuery
+ * @returns {boolean} True if the insert is a success
+ */
+ add: function (name, media) {
+ if (!name || !media ||
+ !angular.isString(name) || !angular.isString(media) ||
+ !!interchangeQueries[name]) {
+ return false;
+ }
+ interchangeQueries[name] = media;
+ return true;
+ }
+ };
+ }])
+
+ /**
+ * @ngdoc function
+ * @name mm.foundation.interchange.interchangeTools
+ * @function interchangeTools
+ * @description
+ *
+ * Tools to help with the `interchange` module.
+ */
+ .factory('interchangeTools', ['$window', 'matchMedia', 'interchangeQueries', function ($window, matchMedia, namedQueries) {
+
+ /**
+ * @ngdoc method
+ * @name interchangeTools#parseAttribute
+ * @methodOf mm.foundation.interchange.interchangeTools
+ * @description
+ *
+ * Attribute parser to transform an `interchange` attribute
+ * value to an object with media query (name or query) as key,
+ * and file to use as value.
+ *
+ * ```
+ * {
+ * small: 'bridge-500.jpg',
+ * large: 'bridge-1200.jpg'
+ * }
+ * ```
+ *
+ * @param {string} value Interchange query string
+ * @returns {object} Attribute parsed
+ */
+ var parseAttribute = function (value) {
+ var raw = value.split(/\[(.*?)\]/),
+ i = raw.length,
+ breaker = /^(.+)\,\ \((.+)\)$/,
+ breaked,
+ output = {};
+
+ while (i--) {
+ if (raw[i].replace(/[\W\d]+/, '').length > 4) {
+ breaked = breaker.exec(raw[i]);
+ if (!!breaked && breaked.length === 3) {
+ output[breaked[2]] = breaked[1];
+ }
+ }
+ }
+ return output;
+ };
+
+ /**
+ * @ngdoc method
+ * @name interchangeTools#findCurrentMediaFile
+ * @methodOf mm.foundation.interchange.interchangeTools
+ * @description
+ *
+ * Find the current item to display from a file list
+ * (object returned by `parseAttribute`) and the
+ * current page dimensions.
+ *
+ * ```
+ * {
+ * small: 'bridge-500.jpg',
+ * large: 'bridge-1200.jpg'
+ * }
+ * ```
+ *
+ * @param {object} files Parsed version of `interchange` attribute
+ * @returns {string} File to display (or `undefined`)
+ */
+ var findCurrentMediaFile = function (files) {
+ var file, media, match;
+ for (file in files) {
+ media = namedQueries[file] || file;
+ match = matchMedia(media);
+ if (match.matches) {
+ return files[file];
+ }
+ }
+ return;
+ };
+
+ return {
+ parseAttribute: parseAttribute,
+ findCurrentMediaFile: findCurrentMediaFile
+ };
+ }])
+
+ /**
+ * @ngdoc directive
+ * @name mm.foundation.interchange.directive:interchange
+ * @restrict A
+ * @element DIV|IMG
+ * @priority 450
+ * @scope true
+ * @description
+ *
+ * Interchange directive, following the same features as
+ * ZURB documentation. The directive is splitted in 3 parts.
+ *
+ * 1. This directive use `compile` and not `link` for a simple
+ * reason: if the method is applied on a DIV element to
+ * display a template, the compile method will inject an ng-include.
+ * Because using a `templateUrl` or `template` to do it wouldn't
+ * be appropriate for all cases (`IMG` or dynamic backgrounds).
+ * And doing it in `link` is too late to be applied.
+ *
+ * 2. In the `compile:post`, the attribute is parsed to find
+ * out the type of content to display.
+ *
+ * 3. At the start and on event `resize`, the method `replace`
+ * is called to reevaluate which file is supposed to be displayed
+ * and update the value if necessary. The methd will also
+ * trigger a `replace` event.
+ */
+ .directive('interchange', ['$window', '$rootScope', 'interchangeTools', function ($window, $rootScope, interchangeTools) {
+
+ var pictureFilePattern = /[A-Za-z0-9-_]+\.(jpg|jpeg|png|gif|bmp|tiff)\ *,/i;
+
+ return {
+ restrict: 'A',
+ scope: true,
+ priority: 450,
+ compile: function compile($element, attrs) {
+ // Set up the attribute to update
+ if ($element[0].nodeName === 'DIV' && !pictureFilePattern.test(attrs.interchange)) {
+ $element.html(' ');
+ }
+
+ return {
+ pre: function preLink($scope, $element, attrs) {},
+ post: function postLink($scope, $element, attrs) {
+ var currentFile, nodeName;
+
+ // Set up the attribute to update
+ nodeName = $element && $element[0] && $element[0].nodeName;
+ $scope.fileMap = interchangeTools.parseAttribute(attrs.interchange);
+
+ // Find the type of interchange
+ switch (nodeName) {
+ case 'DIV':
+ // If the tag is a div, we test the current file to see if it's picture
+ currentFile = interchangeTools.findCurrentMediaFile($scope.fileMap);
+ if (/[A-Za-z0-9-_]+\.(jpg|jpeg|png|gif|bmp|tiff)$/i.test(currentFile)) {
+ $scope.type = 'background';
+ }
+ else {
+ $scope.type = 'include';
+ }
+ break;
+
+ case 'IMG':
+ $scope.type = 'image';
+ break;
+
+ default:
+ return;
+ }
+
+ var replace = function (e) {
+ // The the new file to display (exit if the same)
+ var currentFile = interchangeTools.findCurrentMediaFile($scope.fileMap);
+ if (!!$scope.currentFile && $scope.currentFile === currentFile) {
+ return;
+ }
+
+ // Set up the new file
+ $scope.currentFile = currentFile;
+ switch ($scope.type) {
+ case 'image':
+ $element.attr('src', $scope.currentFile);
+ break;
+
+ case 'background':
+ $element.css('background-image', 'url(' + $scope.currentFile + ')');
+ break;
+ }
+
+ // Trigger events
+ $rootScope.$emit('replace', $element, $scope);
+ if (!!e) {
+ $scope.$apply();
+ }
+ };
+
+ // Start
+ replace();
+ $window.addEventListener('resize', replace);
+ $scope.$on('$destroy', function () {
+ $window.removeEventListener('resize', replace);
+ });
+ }
+ };
+ }
+ };
+ }]);
+
+angular.module('mm.foundation.transition', [])
+
+/**
+ * $transition service provides a consistent interface to trigger CSS 3 transitions and to be informed when they complete.
+ * @param {DOMElement} element The DOMElement that will be animated.
+ * @param {string|object|function} trigger The thing that will cause the transition to start:
+ * - As a string, it represents the css class to be added to the element.
+ * - As an object, it represents a hash of style attributes to be applied to the element.
+ * - As a function, it represents a function to be called that will cause the transition to occur.
+ * @return {Promise} A promise that is resolved when the transition finishes.
+ */
+.factory('$transition', ['$q', '$timeout', '$rootScope', function($q, $timeout, $rootScope) {
+
+ var $transition = function(element, trigger, options) {
+ options = options || {};
+ var deferred = $q.defer();
+ var endEventName = $transition[options.animation ? "animationEndEventName" : "transitionEndEventName"];
+
+ var transitionEndHandler = function(event) {
+ $rootScope.$apply(function() {
+ element.unbind(endEventName, transitionEndHandler);
+ deferred.resolve(element);
+ });
+ };
+
+ if (endEventName) {
+ element.bind(endEventName, transitionEndHandler);
+ }
+
+ // Wrap in a timeout to allow the browser time to update the DOM before the transition is to occur
+ $timeout(function() {
+ if ( angular.isString(trigger) ) {
+ element.addClass(trigger);
+ } else if ( angular.isFunction(trigger) ) {
+ trigger(element);
+ } else if ( angular.isObject(trigger) ) {
+ element.css(trigger);
+ }
+ //If browser does not support transitions, instantly resolve
+ if ( !endEventName ) {
+ deferred.resolve(element);
+ }
+ });
+
+ // Add our custom cancel function to the promise that is returned
+ // We can call this if we are about to run a new transition, which we know will prevent this transition from ending,
+ // i.e. it will therefore never raise a transitionEnd event for that transition
+ deferred.promise.cancel = function() {
+ if ( endEventName ) {
+ element.unbind(endEventName, transitionEndHandler);
+ }
+ deferred.reject('Transition cancelled');
+ };
+
+ return deferred.promise;
+ };
+
+ // Work out the name of the transitionEnd event
+ var transElement = document.createElement('trans');
+ var transitionEndEventNames = {
+ 'WebkitTransition': 'webkitTransitionEnd',
+ 'MozTransition': 'transitionend',
+ 'OTransition': 'oTransitionEnd',
+ 'transition': 'transitionend'
+ };
+ var animationEndEventNames = {
+ 'WebkitTransition': 'webkitAnimationEnd',
+ 'MozTransition': 'animationend',
+ 'OTransition': 'oAnimationEnd',
+ 'transition': 'animationend'
+ };
+ function findEndEventName(endEventNames) {
+ for (var name in endEventNames){
+ if (transElement.style[name] !== undefined) {
+ return endEventNames[name];
+ }
+ }
+ }
+ $transition.transitionEndEventName = findEndEventName(transitionEndEventNames);
+ $transition.animationEndEventName = findEndEventName(animationEndEventNames);
+ return $transition;
+}]);
+
+angular.module('mm.foundation.modal', ['mm.foundation.transition'])
+
+/**
+ * A helper, internal data structure that acts as a map but also allows getting / removing
+ * elements in the LIFO order
+ */
+ .factory('$$stackedMap', function () {
+ return {
+ createNew: function () {
+ var stack = [];
+
+ return {
+ add: function (key, value) {
+ stack.push({
+ key: key,
+ value: value
+ });
+ },
+ get: function (key) {
+ for (var i = 0; i < stack.length; i++) {
+ if (key == stack[i].key) {
+ return stack[i];
+ }
+ }
+ },
+ keys: function() {
+ var keys = [];
+ for (var i = 0; i < stack.length; i++) {
+ keys.push(stack[i].key);
+ }
+ return keys;
+ },
+ top: function () {
+ return stack[stack.length - 1];
+ },
+ remove: function (key) {
+ var idx = -1;
+ for (var i = 0; i < stack.length; i++) {
+ if (key == stack[i].key) {
+ idx = i;
+ break;
+ }
+ }
+ return stack.splice(idx, 1)[0];
+ },
+ removeTop: function () {
+ return stack.splice(stack.length - 1, 1)[0];
+ },
+ length: function () {
+ return stack.length;
+ }
+ };
+ }
+ };
+ })
+
+/**
+ * A helper directive for the $modal service. It creates a backdrop element.
+ */
+ .directive('modalBackdrop', ['$modalStack', '$timeout', function ($modalStack, $timeout) {
+ return {
+ restrict: 'EA',
+ replace: true,
+ templateUrl: 'template/modal/backdrop.html',
+ link: function (scope) {
+
+ scope.animate = false;
+
+ //trigger CSS transitions
+ $timeout(function () {
+ scope.animate = true;
+ });
+
+ scope.close = function (evt) {
+ var modal = $modalStack.getTop();
+ if (modal && modal.value.backdrop && modal.value.backdrop != 'static' && (evt.target === evt.currentTarget)) {
+ evt.preventDefault();
+ evt.stopPropagation();
+ $modalStack.dismiss(modal.key, 'backdrop click');
+ }
+ };
+ }
+ };
+ }])
+
+ .directive('modalWindow', ['$modalStack', '$timeout', function ($modalStack, $timeout) {
+ return {
+ restrict: 'EA',
+ scope: {
+ index: '@',
+ animate: '='
+ },
+ replace: true,
+ transclude: true,
+ templateUrl: 'template/modal/window.html',
+ link: function (scope, element, attrs) {
+ scope.windowClass = attrs.windowClass || '';
+
+ $timeout(function () {
+ // trigger CSS transitions
+ scope.animate = true;
+
+ // If the modal contains any autofocus elements refocus onto the first one
+ if (element[0].querySelectorAll('[autofocus]').length > 0) {
+ element[0].querySelectorAll('[autofocus]')[0].focus();
+ }
+ else{
+ // otherwise focus the freshly-opened modal
+ element[0].focus();
+ }
+ });
+ }
+ };
+ }])
+
+ .factory('$modalStack', ['$window', '$transition', '$timeout', '$document', '$compile', '$rootScope', '$$stackedMap',
+ function ($window, $transition, $timeout, $document, $compile, $rootScope, $$stackedMap) {
+
+ var OPENED_MODAL_CLASS = 'modal-open';
+
+ var backdropDomEl, backdropScope, cssTop;
+ var openedWindows = $$stackedMap.createNew();
+ var $modalStack = {};
+
+ function backdropIndex() {
+ var topBackdropIndex = -1;
+ var opened = openedWindows.keys();
+ for (var i = 0; i < opened.length; i++) {
+ if (openedWindows.get(opened[i]).value.backdrop) {
+ topBackdropIndex = i;
+ }
+ }
+ return topBackdropIndex;
+ }
+
+ $rootScope.$watch(backdropIndex, function(newBackdropIndex){
+ if (backdropScope) {
+ backdropScope.index = newBackdropIndex;
+ }
+ });
+
+ function removeModalWindow(modalInstance) {
+ var parent = $document.find(modalInstance.options.parent).eq(0);
+ var modalWindow = openedWindows.get(modalInstance).value;
+
+ //clean up the stack
+ openedWindows.remove(modalInstance);
+
+ //remove window DOM element
+ removeAfterAnimate(modalWindow.modalDomEl, modalWindow.modalScope, 300, function() {
+ modalWindow.modalScope.$destroy();
+ parent.toggleClass(OPENED_MODAL_CLASS, openedWindows.length() > 0);
+ checkRemoveBackdrop();
+ });
+ }
+
+ function checkRemoveBackdrop() {
+ //remove backdrop if no longer needed
+ if (backdropDomEl && backdropIndex() == -1) {
+ var backdropScopeRef = backdropScope;
+ removeAfterAnimate(backdropDomEl, backdropScope, 150, function () {
+ backdropScopeRef.$destroy();
+ backdropScopeRef = null;
+ });
+ backdropDomEl = undefined;
+ backdropScope = undefined;
+ }
+ }
+
+ function removeAfterAnimate(domEl, scope, emulateTime, done) {
+ // Closing animation
+ scope.animate = false;
+
+ var transitionEndEventName = $transition.transitionEndEventName;
+ if (transitionEndEventName) {
+ // transition out
+ var timeout = $timeout(afterAnimating, emulateTime);
+
+ domEl.bind(transitionEndEventName, function () {
+ $timeout.cancel(timeout);
+ afterAnimating();
+ scope.$apply();
+ });
+ } else {
+ // Ensure this call is async
+ $timeout(afterAnimating, 0);
+ }
+
+ function afterAnimating() {
+ if (afterAnimating.done) {
+ return;
+ }
+ afterAnimating.done = true;
+
+ domEl.remove();
+ if (done) {
+ done();
+ }
+ }
+ }
+
+ function calculateModalTop(modalElement, offset) {
+ if (angular.isUndefined(offset)) {
+ offset = 0;
+ }
+ var scrollY = $window.pageYOffset || 0;
+ return offset + scrollY;
+ }
+
+ $document.bind('keydown', function (evt) {
+ var modal;
+
+ if (evt.which === 27) {
+ modal = openedWindows.top();
+ if (modal && modal.value.keyboard) {
+ $rootScope.$apply(function () {
+ $modalStack.dismiss(modal.key);
+ });
+ }
+ }
+ });
+
+ $modalStack.open = function (modalInstance, modal) {
+ modalInstance.options = {
+ deferred: modal.deferred,
+ modalScope: modal.scope,
+ backdrop: modal.backdrop,
+ keyboard: modal.keyboard,
+ parent: modal.parent
+ };
+ openedWindows.add(modalInstance, modalInstance.options);
+
+ var parent = $document.find(modal.parent).eq(0),
+ currBackdropIndex = backdropIndex();
+
+ if (currBackdropIndex >= 0 && !backdropDomEl) {
+ backdropScope = $rootScope.$new(true);
+ backdropScope.index = currBackdropIndex;
+ backdropDomEl = $compile('
')(backdropScope);
+ parent.append(backdropDomEl);
+ }
+
+ // Create a faux modal div just to measure its
+ // distance to top
+ var faux = angular.element('
');
+ parent.append(faux[0]);
+ cssTop = parseInt($window.getComputedStyle(faux[0]).top) || 0;
+ var openAt = calculateModalTop(faux, cssTop);
+ faux.remove();
+
+ var angularDomEl = angular.element('
')
+ .attr({
+ 'window-class': modal.windowClass,
+ 'index': openedWindows.length() - 1,
+ 'animate': 'animate'
+ });
+ angularDomEl.html(modal.content);
+
+ var modalDomEl = $compile(angularDomEl)(modal.scope);
+ openedWindows.top().value.modalDomEl = modalDomEl;
+ parent.append(modalDomEl);
+ parent.addClass(OPENED_MODAL_CLASS);
+ };
+
+ $modalStack.reposition = function (modalInstance) {
+ var modalWindow = openedWindows.get(modalInstance).value;
+ if (modalWindow) {
+ var modalDomEl = modalWindow.modalDomEl;
+ var top = calculateModalTop(modalDomEl, cssTop);
+ modalDomEl.css('top', top + "px");
+ }
+ };
+
+ $modalStack.close = function (modalInstance, result) {
+ var modalWindow = openedWindows.get(modalInstance);
+ if (modalWindow) {
+ modalWindow.value.deferred.resolve(result);
+ removeModalWindow(modalInstance);
+ }
+ };
+
+ $modalStack.dismiss = function (modalInstance, reason) {
+ var modalWindow = openedWindows.get(modalInstance);
+ if (modalWindow) {
+ modalWindow.value.deferred.reject(reason);
+ removeModalWindow(modalInstance);
+ }
+ };
+
+ $modalStack.dismissAll = function (reason) {
+ var topModal = this.getTop();
+ while (topModal) {
+ this.dismiss(topModal.key, reason);
+ topModal = this.getTop();
+ }
+ };
+
+ $modalStack.getTop = function () {
+ return openedWindows.top();
+ };
+
+ return $modalStack;
+ }])
+
+ .provider('$modal', function () {
+
+ var $modalProvider = {
+ options: {
+ backdrop: true, //can be also false or 'static'
+ keyboard: true
+ },
+ $get: ['$injector', '$rootScope', '$q', '$http', '$templateCache', '$controller', '$modalStack',
+ function ($injector, $rootScope, $q, $http, $templateCache, $controller, $modalStack) {
+
+ var $modal = {};
+
+ function getTemplatePromise(options) {
+ return options.template ? $q.when(options.template) :
+ $http.get(options.templateUrl, {cache: $templateCache}).then(function (result) {
+ return result.data;
+ });
+ }
+
+ function getResolvePromises(resolves) {
+ var promisesArr = [];
+ angular.forEach(resolves, function (value, key) {
+ if (angular.isFunction(value) || angular.isArray(value)) {
+ promisesArr.push($q.when($injector.invoke(value)));
+ }
+ });
+ return promisesArr;
+ }
+
+ $modal.open = function (modalOptions) {
+
+ var modalResultDeferred = $q.defer();
+ var modalOpenedDeferred = $q.defer();
+
+ //prepare an instance of a modal to be injected into controllers and returned to a caller
+ var modalInstance = {
+ result: modalResultDeferred.promise,
+ opened: modalOpenedDeferred.promise,
+ close: function (result) {
+ $modalStack.close(modalInstance, result);
+ },
+ dismiss: function (reason) {
+ $modalStack.dismiss(modalInstance, reason);
+ },
+ reposition: function () {
+ $modalStack.reposition(modalInstance);
+ }
+ };
+
+ //merge and clean up options
+ modalOptions = angular.extend({}, $modalProvider.options, modalOptions);
+ modalOptions.resolve = modalOptions.resolve || {};
+
+ //verify options
+ if (!modalOptions.template && !modalOptions.templateUrl) {
+ throw new Error('One of template or templateUrl options is required.');
+ }
+
+ var templateAndResolvePromise =
+ $q.all([getTemplatePromise(modalOptions)].concat(getResolvePromises(modalOptions.resolve)));
+
+
+ templateAndResolvePromise.then(function resolveSuccess(tplAndVars) {
+
+ var modalScope = (modalOptions.scope || $rootScope).$new();
+ modalScope.$close = modalInstance.close;
+ modalScope.$dismiss = modalInstance.dismiss;
+
+ var ctrlInstance, ctrlLocals = {};
+ var resolveIter = 1;
+
+ //controllers
+ if (modalOptions.controller) {
+ ctrlLocals.$scope = modalScope;
+ ctrlLocals.$modalInstance = modalInstance;
+ angular.forEach(modalOptions.resolve, function (value, key) {
+ ctrlLocals[key] = tplAndVars[resolveIter++];
+ });
+
+ ctrlInstance = $controller(modalOptions.controller, ctrlLocals);
+ if (modalOptions.controllerAs) {
+ modalScope[modalOptions.controllerAs] = ctrlInstance;
+ }
+ }
+
+ $modalStack.open(modalInstance, {
+ scope: modalScope,
+ deferred: modalResultDeferred,
+ content: tplAndVars[0],
+ backdrop: modalOptions.backdrop,
+ keyboard: modalOptions.keyboard,
+ windowClass: modalOptions.windowClass,
+ parent: modalOptions.parent || 'body'
+ });
+
+ }, function resolveError(reason) {
+ modalResultDeferred.reject(reason);
+ });
+
+ templateAndResolvePromise.then(function () {
+ modalOpenedDeferred.resolve(true);
+ }, function () {
+ modalOpenedDeferred.reject(false);
+ });
+
+ return modalInstance;
+ };
+
+ return $modal;
+ }]
+ };
+
+ return $modalProvider;
+ });
+
+angular.module("mm.foundation.offcanvas", [])
+ .directive('offCanvasWrap', ['$window', function ($window) {
+ return {
+ scope: {},
+ restrict: 'C',
+ link: function ($scope, element, attrs) {
+ var win = angular.element($window);
+ var sidebar = $scope.sidebar = element;
+
+ $scope.hide = function () {
+ sidebar.removeClass('move-left');
+ sidebar.removeClass('move-right');
+ };
+
+ win.bind("resize.body", $scope.hide);
+
+ $scope.$on('$destroy', function() {
+ win.unbind("resize.body", $scope.hide);
+ });
+
+ },
+ controller: ['$scope', function($scope) {
+
+ this.leftToggle = function() {
+ $scope.sidebar.toggleClass("move-right");
+ };
+
+ this.rightToggle = function() {
+ $scope.sidebar.toggleClass("move-left");
+ };
+
+ this.hide = function() {
+ $scope.hide();
+ };
+ }]
+ };
+ }])
+ .directive('leftOffCanvasToggle', [function () {
+ return {
+ require: '^offCanvasWrap',
+ restrict: 'C',
+ link: function ($scope, element, attrs, offCanvasWrap) {
+ element.on('click', function () {
+ offCanvasWrap.leftToggle();
+ });
+ }
+ };
+ }])
+ .directive('rightOffCanvasToggle', [function () {
+ return {
+ require: '^offCanvasWrap',
+ restrict: 'C',
+ link: function ($scope, element, attrs, offCanvasWrap) {
+ element.on('click', function () {
+ offCanvasWrap.rightToggle();
+ });
+ }
+ };
+ }])
+ .directive('exitOffCanvas', [function () {
+ return {
+ require: '^offCanvasWrap',
+ restrict: 'C',
+ link: function ($scope, element, attrs, offCanvasWrap) {
+ element.on('click', function () {
+ offCanvasWrap.hide();
+ });
+ }
+ };
+ }])
+ .directive('offCanvasList', [function () {
+ return {
+ require: '^offCanvasWrap',
+ restrict: 'C',
+ link: function ($scope, element, attrs, offCanvasWrap) {
+ element.on('click', function () {
+ offCanvasWrap.hide();
+ });
+ }
+ };
+ }]);
+
+angular.module('mm.foundation.pagination', [])
+
+.controller('PaginationController', ['$scope', '$attrs', '$parse', '$interpolate', function ($scope, $attrs, $parse, $interpolate) {
+ var self = this,
+ setNumPages = $attrs.numPages ? $parse($attrs.numPages).assign : angular.noop;
+
+ this.init = function(defaultItemsPerPage) {
+ if ($attrs.itemsPerPage) {
+ $scope.$parent.$watch($parse($attrs.itemsPerPage), function(value) {
+ self.itemsPerPage = parseInt(value, 10);
+ $scope.totalPages = self.calculateTotalPages();
+ });
+ } else {
+ this.itemsPerPage = defaultItemsPerPage;
+ }
+ };
+
+ this.noPrevious = function() {
+ return this.page === 1;
+ };
+ this.noNext = function() {
+ return this.page === $scope.totalPages;
+ };
+
+ this.isActive = function(page) {
+ return this.page === page;
+ };
+
+ this.calculateTotalPages = function() {
+ var totalPages = this.itemsPerPage < 1 ? 1 : Math.ceil($scope.totalItems / this.itemsPerPage);
+ return Math.max(totalPages || 0, 1);
+ };
+
+ this.getAttributeValue = function(attribute, defaultValue, interpolate) {
+ return angular.isDefined(attribute) ? (interpolate ? $interpolate(attribute)($scope.$parent) : $scope.$parent.$eval(attribute)) : defaultValue;
+ };
+
+ this.render = function() {
+ this.page = parseInt($scope.page, 10) || 1;
+ if (this.page > 0 && this.page <= $scope.totalPages) {
+ $scope.pages = this.getPages(this.page, $scope.totalPages);
+ }
+ };
+
+ $scope.selectPage = function(page) {
+ if ( ! self.isActive(page) && page > 0 && page <= $scope.totalPages) {
+ $scope.page = page;
+ $scope.onSelectPage({ page: page });
+ }
+ };
+
+ $scope.$watch('page', function() {
+ self.render();
+ });
+
+ $scope.$watch('totalItems', function() {
+ $scope.totalPages = self.calculateTotalPages();
+ });
+
+ $scope.$watch('totalPages', function(value) {
+ setNumPages($scope.$parent, value); // Readonly variable
+
+ if ( self.page > value ) {
+ $scope.selectPage(value);
+ } else {
+ self.render();
+ }
+ });
+}])
+
+.constant('paginationConfig', {
+ itemsPerPage: 10,
+ boundaryLinks: false,
+ directionLinks: true,
+ firstText: 'First',
+ previousText: 'Previous',
+ nextText: 'Next',
+ lastText: 'Last',
+ rotate: true
+})
+
+.directive('pagination', ['$parse', 'paginationConfig', function($parse, config) {
+ return {
+ restrict: 'EA',
+ scope: {
+ page: '=',
+ totalItems: '=',
+ onSelectPage:' &'
+ },
+ controller: 'PaginationController',
+ templateUrl: 'template/pagination/pagination.html',
+ replace: true,
+ link: function(scope, element, attrs, paginationCtrl) {
+
+ // Setup configuration parameters
+ var maxSize,
+ boundaryLinks = paginationCtrl.getAttributeValue(attrs.boundaryLinks, config.boundaryLinks ),
+ directionLinks = paginationCtrl.getAttributeValue(attrs.directionLinks, config.directionLinks ),
+ firstText = paginationCtrl.getAttributeValue(attrs.firstText, config.firstText, true),
+ previousText = paginationCtrl.getAttributeValue(attrs.previousText, config.previousText, true),
+ nextText = paginationCtrl.getAttributeValue(attrs.nextText, config.nextText, true),
+ lastText = paginationCtrl.getAttributeValue(attrs.lastText, config.lastText, true),
+ rotate = paginationCtrl.getAttributeValue(attrs.rotate, config.rotate);
+
+ paginationCtrl.init(config.itemsPerPage);
+
+ if (attrs.maxSize) {
+ scope.$parent.$watch($parse(attrs.maxSize), function(value) {
+ maxSize = parseInt(value, 10);
+ paginationCtrl.render();
+ });
+ }
+
+ // Create page object used in template
+ function makePage(number, text, isActive, isDisabled) {
+ return {
+ number: number,
+ text: text,
+ active: isActive,
+ disabled: isDisabled
+ };
+ }
+
+ paginationCtrl.getPages = function(currentPage, totalPages) {
+ var pages = [];
+
+ // Default page limits
+ var startPage = 1, endPage = totalPages;
+ var isMaxSized = ( angular.isDefined(maxSize) && maxSize < totalPages );
+
+ // recompute if maxSize
+ if ( isMaxSized ) {
+ if ( rotate ) {
+ // Current page is displayed in the middle of the visible ones
+ startPage = Math.max(currentPage - Math.floor(maxSize/2), 1);
+ endPage = startPage + maxSize - 1;
+
+ // Adjust if limit is exceeded
+ if (endPage > totalPages) {
+ endPage = totalPages;
+ startPage = endPage - maxSize + 1;
+ }
+ } else {
+ // Visible pages are paginated with maxSize
+ startPage = ((Math.ceil(currentPage / maxSize) - 1) * maxSize) + 1;
+
+ // Adjust last page if limit is exceeded
+ endPage = Math.min(startPage + maxSize - 1, totalPages);
+ }
+ }
+
+ // Add page number links
+ for (var number = startPage; number <= endPage; number++) {
+ var page = makePage(number, number, paginationCtrl.isActive(number), false);
+ pages.push(page);
+ }
+
+ // Add links to move between page sets
+ if ( isMaxSized && ! rotate ) {
+ if ( startPage > 1 ) {
+ var previousPageSet = makePage(startPage - 1, '...', false, false);
+ pages.unshift(previousPageSet);
+ }
+
+ if ( endPage < totalPages ) {
+ var nextPageSet = makePage(endPage + 1, '...', false, false);
+ pages.push(nextPageSet);
+ }
+ }
+
+ // Add previous & next links
+ if (directionLinks) {
+ var previousPage = makePage(currentPage - 1, previousText, false, paginationCtrl.noPrevious());
+ pages.unshift(previousPage);
+
+ var nextPage = makePage(currentPage + 1, nextText, false, paginationCtrl.noNext());
+ pages.push(nextPage);
+ }
+
+ // Add first & last links
+ if (boundaryLinks) {
+ var firstPage = makePage(1, firstText, false, paginationCtrl.noPrevious());
+ pages.unshift(firstPage);
+
+ var lastPage = makePage(totalPages, lastText, false, paginationCtrl.noNext());
+ pages.push(lastPage);
+ }
+
+ return pages;
+ };
+ }
+ };
+}])
+
+.constant('pagerConfig', {
+ itemsPerPage: 10,
+ previousText: '« Previous',
+ nextText: 'Next »',
+ align: true
+})
+
+.directive('pager', ['pagerConfig', function(config) {
+ return {
+ restrict: 'EA',
+ scope: {
+ page: '=',
+ totalItems: '=',
+ onSelectPage:' &'
+ },
+ controller: 'PaginationController',
+ templateUrl: 'template/pagination/pager.html',
+ replace: true,
+ link: function(scope, element, attrs, paginationCtrl) {
+
+ // Setup configuration parameters
+ var previousText = paginationCtrl.getAttributeValue(attrs.previousText, config.previousText, true),
+ nextText = paginationCtrl.getAttributeValue(attrs.nextText, config.nextText, true),
+ align = paginationCtrl.getAttributeValue(attrs.align, config.align);
+
+ paginationCtrl.init(config.itemsPerPage);
+
+ // Create page object used in template
+ function makePage(number, text, isDisabled, isPrevious, isNext) {
+ return {
+ number: number,
+ text: text,
+ disabled: isDisabled,
+ previous: ( align && isPrevious ),
+ next: ( align && isNext )
+ };
+ }
+
+ paginationCtrl.getPages = function(currentPage) {
+ return [
+ makePage(currentPage - 1, previousText, paginationCtrl.noPrevious(), true, false),
+ makePage(currentPage + 1, nextText, paginationCtrl.noNext(), false, true)
+ ];
+ };
+ }
+ };
+}]);
+
+/**
+ * The following features are still outstanding: animation as a
+ * function, placement as a function, inside, support for more triggers than
+ * just mouse enter/leave, html tooltips, and selector delegation.
+ */
+angular.module( 'mm.foundation.tooltip', [ 'mm.foundation.position', 'mm.foundation.bindHtml' ] )
+
+/**
+ * The $tooltip service creates tooltip- and popover-like directives as well as
+ * houses global options for them.
+ */
+.provider( '$tooltip', function () {
+ // The default options tooltip and popover.
+ var defaultOptions = {
+ placement: 'top',
+ animation: true,
+ popupDelay: 0
+ };
+
+ // Default hide triggers for each show trigger
+ var triggerMap = {
+ 'mouseenter': 'mouseleave',
+ 'click': 'click',
+ 'focus': 'blur'
+ };
+
+ // The options specified to the provider globally.
+ var globalOptions = {};
+
+ /**
+ * `options({})` allows global configuration of all tooltips in the
+ * application.
+ *
+ * var app = angular.module( 'App', ['mm.foundation.tooltip'], function( $tooltipProvider ) {
+ * // place tooltips left instead of top by default
+ * $tooltipProvider.options( { placement: 'left' } );
+ * });
+ */
+ this.options = function( value ) {
+ angular.extend( globalOptions, value );
+ };
+
+ /**
+ * This allows you to extend the set of trigger mappings available. E.g.:
+ *
+ * $tooltipProvider.setTriggers( 'openTrigger': 'closeTrigger' );
+ */
+ this.setTriggers = function setTriggers ( triggers ) {
+ angular.extend( triggerMap, triggers );
+ };
+
+ /**
+ * This is a helper function for translating camel-case to snake-case.
+ */
+ function snake_case(name){
+ var regexp = /[A-Z]/g;
+ var separator = '-';
+ return name.replace(regexp, function(letter, pos) {
+ return (pos ? separator : '') + letter.toLowerCase();
+ });
+ }
+
+ /**
+ * Returns the actual instance of the $tooltip service.
+ * TODO support multiple triggers
+ */
+ this.$get = [ '$window', '$compile', '$timeout', '$parse', '$document', '$position', '$interpolate', function ( $window, $compile, $timeout, $parse, $document, $position, $interpolate ) {
+ return function $tooltip ( type, prefix, defaultTriggerShow ) {
+ var options = angular.extend( {}, defaultOptions, globalOptions );
+
+ /**
+ * Returns an object of show and hide triggers.
+ *
+ * If a trigger is supplied,
+ * it is used to show the tooltip; otherwise, it will use the `trigger`
+ * option passed to the `$tooltipProvider.options` method; else it will
+ * default to the trigger supplied to this directive factory.
+ *
+ * The hide trigger is based on the show trigger. If the `trigger` option
+ * was passed to the `$tooltipProvider.options` method, it will use the
+ * mapped trigger from `triggerMap` or the passed trigger if the map is
+ * undefined; otherwise, it uses the `triggerMap` value of the show
+ * trigger; else it will just use the show trigger.
+ */
+ function getTriggers ( trigger ) {
+ var show = trigger || options.trigger || defaultTriggerShow;
+ var hide = triggerMap[show] || show;
+ return {
+ show: show,
+ hide: hide
+ };
+ }
+
+ var directiveName = snake_case( type );
+
+ var startSym = $interpolate.startSymbol();
+ var endSym = $interpolate.endSymbol();
+ var template =
+ ''+
+ '
';
+
+ return {
+ restrict: 'EA',
+ scope: true,
+ compile: function (tElem, tAttrs) {
+ var tooltipLinker = $compile( template );
+
+ return function link ( scope, element, attrs ) {
+ var tooltip;
+ var transitionTimeout;
+ var popupTimeout;
+ var appendToBody = angular.isDefined( options.appendToBody ) ? options.appendToBody : false;
+ var triggers = getTriggers( undefined );
+ var hasRegisteredTriggers = false;
+ var hasEnableExp = angular.isDefined(attrs[prefix+'Enable']);
+
+ var positionTooltip = function (){
+ var position,
+ ttWidth,
+ ttHeight,
+ ttPosition;
+ // Get the position of the directive element.
+ position = appendToBody ? $position.offset( element ) : $position.position( element );
+
+ // Get the height and width of the tooltip so we can center it.
+ ttWidth = tooltip.prop( 'offsetWidth' );
+ ttHeight = tooltip.prop( 'offsetHeight' );
+
+ // Calculate the tooltip's top and left coordinates to center it with
+ // this directive.
+ switch ( scope.tt_placement ) {
+ case 'right':
+ ttPosition = {
+ top: position.top + position.height / 2 - ttHeight / 2,
+ left: position.left + position.width + 10
+ };
+ break;
+ case 'bottom':
+ ttPosition = {
+ top: position.top + position.height + 10,
+ left: position.left
+ };
+ break;
+ case 'left':
+ ttPosition = {
+ top: position.top + position.height / 2 - ttHeight / 2,
+ left: position.left - ttWidth - 10
+ };
+ break;
+ default:
+ ttPosition = {
+ top: position.top - ttHeight - 10,
+ left: position.left
+ };
+ break;
+ }
+
+ ttPosition.top += 'px';
+ ttPosition.left += 'px';
+
+ // Now set the calculated positioning.
+ tooltip.css( ttPosition );
+
+ };
+
+ // By default, the tooltip is not open.
+ // TODO add ability to start tooltip opened
+ scope.tt_isOpen = false;
+
+ function toggleTooltipBind () {
+ if ( ! scope.tt_isOpen ) {
+ showTooltipBind();
+ } else {
+ hideTooltipBind();
+ }
+ }
+
+ // Show the tooltip with delay if specified, otherwise show it immediately
+ function showTooltipBind() {
+ if(hasEnableExp && !scope.$eval(attrs[prefix+'Enable'])) {
+ return;
+ }
+ if ( scope.tt_popupDelay ) {
+ popupTimeout = $timeout( show, scope.tt_popupDelay, false );
+ popupTimeout.then(function(reposition){reposition();});
+ } else {
+ show()();
+ }
+ }
+
+ function hideTooltipBind () {
+ scope.$apply(function () {
+ hide();
+ });
+ }
+
+ // Show the tooltip popup element.
+ function show() {
+
+
+ // Don't show empty tooltips.
+ if ( ! scope.tt_content ) {
+ return angular.noop;
+ }
+
+ createTooltip();
+
+ // If there is a pending remove transition, we must cancel it, lest the
+ // tooltip be mysteriously removed.
+ if ( transitionTimeout ) {
+ $timeout.cancel( transitionTimeout );
+ }
+
+ // Set the initial positioning.
+ tooltip.css({ top: 0, left: 0, display: 'block' });
+
+ // Now we add it to the DOM because need some info about it. But it's not
+ // visible yet anyway.
+ if ( appendToBody ) {
+ $document.find( 'body' ).append( tooltip );
+ } else {
+ element.after( tooltip );
+ }
+
+ positionTooltip();
+
+ // And show the tooltip.
+ scope.tt_isOpen = true;
+ scope.$digest(); // digest required as $apply is not called
+
+ // Return positioning function as promise callback for correct
+ // positioning after draw.
+ return positionTooltip;
+ }
+
+ // Hide the tooltip popup element.
+ function hide() {
+ // First things first: we don't show it anymore.
+ scope.tt_isOpen = false;
+
+ //if tooltip is going to be shown after delay, we must cancel this
+ $timeout.cancel( popupTimeout );
+
+ // And now we remove it from the DOM. However, if we have animation, we
+ // need to wait for it to expire beforehand.
+ // FIXME: this is a placeholder for a port of the transitions library.
+ if ( scope.tt_animation ) {
+ transitionTimeout = $timeout(removeTooltip, 500);
+ } else {
+ removeTooltip();
+ }
+ }
+
+ function createTooltip() {
+ // There can only be one tooltip element per directive shown at once.
+ if (tooltip) {
+ removeTooltip();
+ }
+ tooltip = tooltipLinker(scope, function () {});
+
+ // Get contents rendered into the tooltip
+ scope.$digest();
+ }
+
+ function removeTooltip() {
+ if (tooltip) {
+ tooltip.remove();
+ tooltip = null;
+ }
+ }
+
+ /**
+ * Observe the relevant attributes.
+ */
+ attrs.$observe( type, function ( val ) {
+ scope.tt_content = val;
+
+ if (!val && scope.tt_isOpen ) {
+ hide();
+ }
+ });
+
+ attrs.$observe( prefix+'Title', function ( val ) {
+ scope.tt_title = val;
+ });
+
+ attrs[prefix+'Placement'] = attrs[prefix+'Placement'] || null;
+
+ attrs.$observe( prefix+'Placement', function ( val ) {
+ scope.tt_placement = angular.isDefined( val ) && val ? val : options.placement;
+ });
+
+ attrs[prefix+'PopupDelay'] = attrs[prefix+'PopupDelay'] || null;
+
+ attrs.$observe( prefix+'PopupDelay', function ( val ) {
+ var delay = parseInt( val, 10 );
+ scope.tt_popupDelay = ! isNaN(delay) ? delay : options.popupDelay;
+ });
+
+ var unregisterTriggers = function() {
+ if ( hasRegisteredTriggers ) {
+ if ( angular.isFunction( triggers.show ) ) {
+ unregisterTriggerFunction();
+ } else {
+ element.unbind( triggers.show, showTooltipBind );
+ element.unbind( triggers.hide, hideTooltipBind );
+ }
+ }
+ };
+
+ var unregisterTriggerFunction = function () {};
+
+ attrs[prefix+'Trigger'] = attrs[prefix+'Trigger'] || null;
+
+ attrs.$observe( prefix+'Trigger', function ( val ) {
+ unregisterTriggers();
+ unregisterTriggerFunction();
+
+ triggers = getTriggers( val );
+
+ if ( angular.isFunction( triggers.show ) ) {
+ unregisterTriggerFunction = scope.$watch( function () {
+ return triggers.show( scope, element, attrs );
+ }, function ( val ) {
+ return val ? $timeout( show ) : $timeout( hide );
+ });
+ } else {
+ if ( triggers.show === triggers.hide ) {
+ element.bind( triggers.show, toggleTooltipBind );
+ } else {
+ element.bind( triggers.show, showTooltipBind );
+ element.bind( triggers.hide, hideTooltipBind );
+ }
+ }
+
+ hasRegisteredTriggers = true;
+ });
+
+ var animation = scope.$eval(attrs[prefix + 'Animation']);
+ scope.tt_animation = angular.isDefined(animation) ? !!animation : options.animation;
+
+ attrs.$observe( prefix+'AppendToBody', function ( val ) {
+ appendToBody = angular.isDefined( val ) ? $parse( val )( scope ) : appendToBody;
+ });
+
+ // if a tooltip is attached to we need to remove it on
+ // location change as its parent scope will probably not be destroyed
+ // by the change.
+ if ( appendToBody ) {
+ scope.$on('$locationChangeSuccess', function closeTooltipOnLocationChangeSuccess () {
+ if ( scope.tt_isOpen ) {
+ hide();
+ }
+ });
+ }
+
+ // Make sure tooltip is destroyed and removed.
+ scope.$on('$destroy', function onDestroyTooltip() {
+ $timeout.cancel( transitionTimeout );
+ $timeout.cancel( popupTimeout );
+ unregisterTriggers();
+ unregisterTriggerFunction();
+ removeTooltip();
+ });
+ };
+ }
+ };
+ };
+ }];
+})
+
+.directive( 'tooltipPopup', function () {
+ return {
+ restrict: 'EA',
+ replace: true,
+ scope: { content: '@', placement: '@', animation: '&', isOpen: '&' },
+ templateUrl: 'template/tooltip/tooltip-popup.html'
+ };
+})
+
+.directive( 'tooltip', [ '$tooltip', function ( $tooltip ) {
+ return $tooltip( 'tooltip', 'tooltip', 'mouseenter' );
+}])
+
+.directive( 'tooltipHtmlUnsafePopup', function () {
+ return {
+ restrict: 'EA',
+ replace: true,
+ scope: { content: '@', placement: '@', animation: '&', isOpen: '&' },
+ templateUrl: 'template/tooltip/tooltip-html-unsafe-popup.html'
+ };
+})
+
+.directive( 'tooltipHtmlUnsafe', [ '$tooltip', function ( $tooltip ) {
+ return $tooltip( 'tooltipHtmlUnsafe', 'tooltip', 'mouseenter' );
+}]);
+
+/**
+ * The following features are still outstanding: popup delay, animation as a
+ * function, placement as a function, inside, support for more triggers than
+ * just mouse enter/leave, html popovers, and selector delegatation.
+ */
+angular.module( 'mm.foundation.popover', [ 'mm.foundation.tooltip' ] )
+
+.directive( 'popoverPopup', function () {
+ return {
+ restrict: 'EA',
+ replace: true,
+ scope: { title: '@', content: '@', placement: '@', animation: '&', isOpen: '&' },
+ templateUrl: 'template/popover/popover.html'
+ };
+})
+
+.directive( 'popover', [ '$tooltip', function ( $tooltip ) {
+ return $tooltip( 'popover', 'popover', 'click' );
+}]);
+
+angular.module('mm.foundation.progressbar', ['mm.foundation.transition'])
+
+.constant('progressConfig', {
+ animate: true,
+ max: 100
+})
+
+.controller('ProgressController', ['$scope', '$attrs', 'progressConfig', '$transition', function($scope, $attrs, progressConfig, $transition) {
+ var self = this,
+ bars = [],
+ max = angular.isDefined($attrs.max) ? $scope.$parent.$eval($attrs.max) : progressConfig.max,
+ animate = angular.isDefined($attrs.animate) ? $scope.$parent.$eval($attrs.animate) : progressConfig.animate;
+
+ this.addBar = function(bar, element) {
+ var oldValue = 0, index = bar.$parent.$index;
+ if ( angular.isDefined(index) && bars[index] ) {
+ oldValue = bars[index].value;
+ }
+ bars.push(bar);
+
+ this.update(element, bar.value, oldValue);
+
+ bar.$watch('value', function(value, oldValue) {
+ if (value !== oldValue) {
+ self.update(element, value, oldValue);
+ }
+ });
+
+ bar.$on('$destroy', function() {
+ self.removeBar(bar);
+ });
+ };
+
+ // Update bar element width
+ this.update = function(element, newValue, oldValue) {
+ var percent = this.getPercentage(newValue);
+
+ if (animate) {
+ element.css('width', this.getPercentage(oldValue) + '%');
+ $transition(element, {width: percent + '%'});
+ } else {
+ element.css({'transition': 'none', 'width': percent + '%'});
+ }
+ };
+
+ this.removeBar = function(bar) {
+ bars.splice(bars.indexOf(bar), 1);
+ };
+
+ this.getPercentage = function(value) {
+ return Math.round(100 * value / max);
+ };
+}])
+
+.directive('progress', function() {
+ return {
+ restrict: 'EA',
+ replace: true,
+ transclude: true,
+ controller: 'ProgressController',
+ require: 'progress',
+ scope: {},
+ template: '
'
+ //templateUrl: 'template/progressbar/progress.html' // Works in AngularJS 1.2
+ };
+})
+
+.directive('bar', function() {
+ return {
+ restrict: 'EA',
+ replace: true,
+ transclude: true,
+ require: '^progress',
+ scope: {
+ value: '=',
+ type: '@'
+ },
+ templateUrl: 'template/progressbar/bar.html',
+ link: function(scope, element, attrs, progressCtrl) {
+ progressCtrl.addBar(scope, element);
+ }
+ };
+})
+
+.directive('progressbar', function() {
+ return {
+ restrict: 'EA',
+ replace: true,
+ transclude: true,
+ controller: 'ProgressController',
+ scope: {
+ value: '=',
+ type: '@'
+ },
+ templateUrl: 'template/progressbar/progressbar.html',
+ link: function(scope, element, attrs, progressCtrl) {
+ progressCtrl.addBar(scope, angular.element(element.children()[0]));
+ }
+ };
+});
+
+angular.module('mm.foundation.rating', [])
+
+.constant('ratingConfig', {
+ max: 5,
+ stateOn: null,
+ stateOff: null
+})
+
+.controller('RatingController', ['$scope', '$attrs', '$parse', 'ratingConfig', function($scope, $attrs, $parse, ratingConfig) {
+
+ this.maxRange = angular.isDefined($attrs.max) ? $scope.$parent.$eval($attrs.max) : ratingConfig.max;
+ this.stateOn = angular.isDefined($attrs.stateOn) ? $scope.$parent.$eval($attrs.stateOn) : ratingConfig.stateOn;
+ this.stateOff = angular.isDefined($attrs.stateOff) ? $scope.$parent.$eval($attrs.stateOff) : ratingConfig.stateOff;
+
+ this.createRateObjects = function(states) {
+ var defaultOptions = {
+ stateOn: this.stateOn,
+ stateOff: this.stateOff
+ };
+
+ for (var i = 0, n = states.length; i < n; i++) {
+ states[i] = angular.extend({ index: i }, defaultOptions, states[i]);
+ }
+ return states;
+ };
+
+ // Get objects used in template
+ $scope.range = angular.isDefined($attrs.ratingStates) ? this.createRateObjects(angular.copy($scope.$parent.$eval($attrs.ratingStates))): this.createRateObjects(new Array(this.maxRange));
+
+ $scope.rate = function(value) {
+ if ( $scope.value !== value && !$scope.readonly ) {
+ $scope.value = value;
+ }
+ };
+
+ $scope.enter = function(value) {
+ if ( ! $scope.readonly ) {
+ $scope.val = value;
+ }
+ $scope.onHover({value: value});
+ };
+
+ $scope.reset = function() {
+ $scope.val = angular.copy($scope.value);
+ $scope.onLeave();
+ };
+
+ $scope.$watch('value', function(value) {
+ $scope.val = value;
+ });
+
+ $scope.readonly = false;
+ if ($attrs.readonly) {
+ $scope.$parent.$watch($parse($attrs.readonly), function(value) {
+ $scope.readonly = !!value;
+ });
+ }
+}])
+
+.directive('rating', function() {
+ return {
+ restrict: 'EA',
+ scope: {
+ value: '=',
+ onHover: '&',
+ onLeave: '&'
+ },
+ controller: 'RatingController',
+ templateUrl: 'template/rating/rating.html',
+ replace: true
+ };
+});
+
+
+/**
+ * @ngdoc overview
+ * @name mm.foundation.tabs
+ *
+ * @description
+ * AngularJS version of the tabs directive.
+ */
+
+angular.module('mm.foundation.tabs', [])
+
+.controller('TabsetController', ['$scope', function TabsetCtrl($scope) {
+ var ctrl = this,
+ tabs = ctrl.tabs = $scope.tabs = [];
+
+ if (angular.isUndefined($scope.openOnLoad)) { $scope.openOnLoad = true; }
+
+ ctrl.select = function(tab) {
+ angular.forEach(tabs, function(tab) {
+ tab.active = false;
+ });
+ tab.active = true;
+ };
+
+ ctrl.addTab = function addTab(tab) {
+ tabs.push(tab);
+ if ($scope.openOnLoad && (tabs.length === 1 || tab.active)) {
+ ctrl.select(tab);
+ }
+ };
+
+ ctrl.removeTab = function removeTab(tab) {
+ var index = tabs.indexOf(tab);
+ //Select a new tab if the tab to be removed is selected
+ if (tab.active && tabs.length > 1) {
+ //If this is the last tab, select the previous tab. else, the next tab.
+ var newActiveIndex = index == tabs.length - 1 ? index - 1 : index + 1;
+ ctrl.select(tabs[newActiveIndex]);
+ }
+ tabs.splice(index, 1);
+ };
+}])
+
+/**
+ * @ngdoc directive
+ * @name mm.foundation.tabs.directive:tabset
+ * @restrict EA
+ *
+ * @description
+ * Tabset is the outer container for the tabs directive
+ *
+ * @param {boolean=} vertical Whether or not to use vertical styling for the tabs.
+ * @param {boolean=} justified Whether or not to use justified styling for the tabs.
+ *
+ * @example
+
+
+
+ First Content!
+ Second Content!
+
+
+
+ First Vertical Content!
+ Second Vertical Content!
+
+
+ First Justified Content!
+ Second Justified Content!
+
+
+
+ */
+.directive('tabset', function() {
+ return {
+ restrict: 'EA',
+ transclude: true,
+ replace: true,
+ scope: {
+ openOnLoad: '=?'
+ },
+ controller: 'TabsetController',
+ templateUrl: 'template/tabs/tabset.html',
+ link: function(scope, element, attrs) {
+ scope.vertical = angular.isDefined(attrs.vertical) ? scope.$parent.$eval(attrs.vertical) : false;
+ scope.justified = angular.isDefined(attrs.justified) ? scope.$parent.$eval(attrs.justified) : false;
+ scope.type = angular.isDefined(attrs.type) ? scope.$parent.$eval(attrs.type) : 'tabs';
+ }
+ };
+})
+
+/**
+ * @ngdoc directive
+ * @name mm.foundation.tabs.directive:tab
+ * @restrict EA
+ *
+ * @param {string=} heading The visible heading, or title, of the tab. Set HTML headings with {@link mm.foundation.tabs.directive:tabHeading tabHeading}.
+ * @param {string=} select An expression to evaluate when the tab is selected.
+ * @param {boolean=} active A binding, telling whether or not this tab is selected.
+ * @param {boolean=} disabled A binding, telling whether or not this tab is disabled.
+ *
+ * @description
+ * Creates a tab with a heading and content. Must be placed within a {@link mm.foundation.tabs.directive:tabset tabset}.
+ *
+ * @example
+
+
+
+
+ Select item 1, using active binding
+
+
+ Enable/disable item 2, using disabled binding
+
+
+
+ First Tab
+
+ Alert me!
+ Second Tab, with alert callback and html heading!
+
+
+ {{item.content}}
+
+
+
+
+
+ function TabsDemoCtrl($scope) {
+ $scope.items = [
+ { title:"Dynamic Title 1", content:"Dynamic Item 0" },
+ { title:"Dynamic Title 2", content:"Dynamic Item 1", disabled: true }
+ ];
+
+ $scope.alertMe = function() {
+ setTimeout(function() {
+ alert("You've selected the alert tab!");
+ });
+ };
+ };
+
+
+ */
+
+/**
+ * @ngdoc directive
+ * @name mm.foundation.tabs.directive:tabHeading
+ * @restrict EA
+ *
+ * @description
+ * Creates an HTML heading for a {@link mm.foundation.tabs.directive:tab tab}. Must be placed as a child of a tab element.
+ *
+ * @example
+
+
+
+
+ HTML in my titles?!
+ And some content, too!
+
+
+ Icon heading?!?
+ That's right.
+
+
+
+
+ */
+.directive('tab', ['$parse', function($parse) {
+ return {
+ require: '^tabset',
+ restrict: 'EA',
+ replace: true,
+ templateUrl: 'template/tabs/tab.html',
+ transclude: true,
+ scope: {
+ heading: '@',
+ onSelect: '&select', //This callback is called in contentHeadingTransclude
+ //once it inserts the tab's content into the dom
+ onDeselect: '&deselect'
+ },
+ controller: function() {
+ //Empty controller so other directives can require being 'under' a tab
+ },
+ compile: function(elm, attrs, transclude) {
+ return function postLink(scope, elm, attrs, tabsetCtrl) {
+ var getActive, setActive;
+ if (attrs.active) {
+ getActive = $parse(attrs.active);
+ setActive = getActive.assign;
+ scope.$parent.$watch(getActive, function updateActive(value, oldVal) {
+ // Avoid re-initializing scope.active as it is already initialized
+ // below. (watcher is called async during init with value ===
+ // oldVal)
+ if (value !== oldVal) {
+ scope.active = !!value;
+ }
+ });
+ scope.active = getActive(scope.$parent);
+ } else {
+ setActive = getActive = angular.noop;
+ }
+
+ scope.$watch('active', function(active) {
+ if( !angular.isFunction(setActive) ){
+ return;
+ }
+ // Note this watcher also initializes and assigns scope.active to the
+ // attrs.active expression.
+ setActive(scope.$parent, active);
+ if (active) {
+ tabsetCtrl.select(scope);
+ scope.onSelect();
+ } else {
+ scope.onDeselect();
+ }
+ });
+
+ scope.disabled = false;
+ if ( attrs.disabled ) {
+ scope.$parent.$watch($parse(attrs.disabled), function(value) {
+ scope.disabled = !! value;
+ });
+ }
+
+ scope.select = function() {
+ if ( ! scope.disabled ) {
+ scope.active = true;
+ }
+ };
+
+ tabsetCtrl.addTab(scope);
+ scope.$on('$destroy', function() {
+ tabsetCtrl.removeTab(scope);
+ });
+
+
+ //We need to transclude later, once the content container is ready.
+ //when this link happens, we're inside a tab heading.
+ scope.$transcludeFn = transclude;
+ };
+ }
+ };
+}])
+
+.directive('tabHeadingTransclude', [function() {
+ return {
+ restrict: 'A',
+ require: '^tab',
+ link: function(scope, elm, attrs, tabCtrl) {
+ scope.$watch('headingElement', function updateHeadingElement(heading) {
+ if (heading) {
+ elm.html('');
+ elm.append(heading);
+ }
+ });
+ }
+ };
+}])
+
+.directive('tabContentTransclude', function() {
+ return {
+ restrict: 'A',
+ require: '^tabset',
+ link: function(scope, elm, attrs) {
+ var tab = scope.$eval(attrs.tabContentTransclude);
+
+ //Now our tab is ready to be transcluded: both the tab heading area
+ //and the tab content area are loaded. Transclude 'em both.
+ tab.$transcludeFn(tab.$parent, function(contents) {
+ angular.forEach(contents, function(node) {
+ if (isTabHeading(node)) {
+ //Let tabHeadingTransclude know.
+ tab.headingElement = node;
+ } else {
+ elm.append(node);
+ }
+ });
+ });
+ }
+ };
+ function isTabHeading(node) {
+ return node.tagName && (
+ node.hasAttribute('tab-heading') ||
+ node.hasAttribute('data-tab-heading') ||
+ node.tagName.toLowerCase() === 'tab-heading' ||
+ node.tagName.toLowerCase() === 'data-tab-heading'
+ );
+ }
+})
+
+;
+
+angular.module("mm.foundation.topbar", ['mm.foundation.mediaQueries'])
+.factory('closest', [function() {
+ return function(el, selector) {
+ var matchesSelector = function (node, selector) {
+ var nodes = (node.parentNode || node.document).querySelectorAll(selector);
+ var i = -1;
+ while (nodes[++i] && nodes[i] != node){}
+ return !!nodes[i];
+ };
+
+ var element = el[0];
+ while (element) {
+ if (matchesSelector(element, selector)) {
+ return angular.element(element);
+ } else {
+ element = element.parentElement;
+ }
+ }
+ return false;
+ };
+}])
+.directive('topBar', ['$timeout','$compile', '$window', '$document', 'mediaQueries',
+ function($timeout, $compile, $window, $document, mediaQueries) {
+ return {
+ scope: {
+ stickyClass : '@',
+ backText: '@',
+ stickyOn : '=',
+ customBackText: '=',
+ isHover: '=',
+ mobileShowParentLink: '=',
+ scrolltop : '=',
+ },
+ restrict: 'EA',
+ replace: true,
+ templateUrl: 'template/topbar/top-bar.html',
+ transclude: true,
+ controller: ['$window', '$scope', 'closest', function($window, $scope, closest) {
+ $scope.settings = {};
+ $scope.settings.stickyClass = $scope.stickyClass || 'sticky';
+ $scope.settings.backText = $scope.backText || 'Back';
+ $scope.settings.stickyOn = $scope.stickyOn || 'all';
+
+ $scope.settings.customBackText = $scope.customBackText === undefined ? true : $scope.customBackText;
+ $scope.settings.isHover = $scope.isHover === undefined ? true : $scope.isHover;
+ $scope.settings.mobileShowParentLink = $scope.mobileShowParentLink === undefined ? true : $scope.mobileShowParentLink;
+ $scope.settings.scrolltop = $scope.scrolltop === undefined ? true : $scope.scrolltop; // jump to top when sticky nav menu toggle is clicked
+
+ this.settings = $scope.settings;
+
+ $scope.index = 0;
+
+ var outerHeight = function(el) {
+ var height = el.offsetHeight;
+ var style = el.currentStyle || getComputedStyle(el);
+
+ height += parseInt(style.marginTop, 10) + parseInt(style.marginBottom, 10);
+ return height;
+ };
+
+
+ var sections = [];
+
+ this.addSection = function(section) {
+ sections.push(section);
+ };
+
+ this.removeSection = function(section) {
+ var index = sections.indexOf(section);
+ if (index > -1) {
+ sections.splice(index, 1);
+ }
+ };
+
+ var dir = /rtl/i.test($document.find('html').attr('dir')) ? 'right' : 'left';
+
+ $scope.$watch('index', function(index) {
+ for(var i = 0; i < sections.length; i++){
+ sections[i].move(dir, index);
+ }
+ });
+
+ this.toggle = function(on) {
+ $scope.toggle(on);
+ for(var i = 0; i < sections.length; i++){
+ sections[i].reset();
+ }
+ $scope.index = 0;
+ $scope.height = '';
+ $scope.$apply();
+ };
+
+ this.back = function(event) {
+ if($scope.index < 1 || !mediaQueries.topbarBreakpoint()){
+ return;
+ }
+
+ var $link = angular.element(event.currentTarget);
+ var $movedLi = closest($link, 'li.moved');
+ var $previousLevelUl = $movedLi.parent();
+ $scope.index = $scope.index -1;
+
+ if($scope.index === 0){
+ $scope.height = '';
+ } else {
+ $scope.height = $scope.originalHeight + outerHeight($previousLevelUl[0]);
+ }
+
+ $timeout(function() {
+ $movedLi.removeClass('moved');
+ }, 300);
+ };
+
+ this.forward = function(event) {
+ if(!mediaQueries.topbarBreakpoint()){
+ return false;
+ }
+
+ var $link = angular.element(event.currentTarget);
+ var $selectedLi = closest($link, 'li');
+ $selectedLi.addClass('moved');
+ $scope.height = $scope.originalHeight + outerHeight($link.parent()[0].querySelector('ul'));
+ $scope.index = $scope.index + 1;
+ $scope.$apply();
+ };
+
+ }],
+ link: function(scope, element, attrs) {
+ var topbar = scope.topbar = element;
+ var topbarContainer = topbar.parent();
+ var body = angular.element($document[0].querySelector('body'));
+ var lastBreakpoint = mediaQueries.topbarBreakpoint();
+
+ var isSticky = scope.isSticky = function() {
+ var sticky = topbarContainer.hasClass(scope.settings.stickyClass);
+ if (sticky && scope.settings.stickyOn === 'all') {
+ return true;
+ } else if (sticky && mediaQueries.small() && scope.settings.stickyOn === 'small') {
+ return true;
+ } else if (sticky && mediaQueries.medium() && scope.settings.stickyOn === 'medium') {
+ return true;
+ } else if (sticky && mediaQueries.large() && scope.settings.stickyOn === 'large') {
+ return true;
+ }
+ return false;
+ };
+
+ var updateStickyPositioning = function() {
+ if (!scope.stickyTopbar || !scope.isSticky()) {
+ return;
+ }
+
+ var distance = stickyoffset;
+
+ if ($window.pageYOffset > distance && !topbarContainer.hasClass('fixed')) {
+ topbarContainer.addClass('fixed');
+ body.css('padding-top', scope.originalHeight + 'px');
+ } else if ($window.pageYOffset <= distance && topbarContainer.hasClass('fixed')) {
+ topbarContainer.removeClass('fixed');
+ body.css('padding-top', '');
+ }
+ };
+
+ var onResize = function() {
+ var currentBreakpoint = mediaQueries.topbarBreakpoint();
+ if(lastBreakpoint === currentBreakpoint){
+ return;
+ }
+ lastBreakpoint = mediaQueries.topbarBreakpoint();
+
+ topbar.removeClass('expanded');
+ topbar.parent().removeClass('expanded');
+ scope.height = '';
+
+ var sections = angular.element(topbar[0].querySelectorAll('section'));
+ angular.forEach(sections, function(section) {
+ angular.element(section.querySelectorAll('li.moved')).removeClass('moved');
+ });
+
+ scope.$apply();
+ };
+
+ var onScroll = function() {
+ updateStickyPositioning();
+ scope.$apply();
+ };
+
+ scope.toggle = function(on) {
+ if(!mediaQueries.topbarBreakpoint()){
+ return false;
+ }
+
+ var expand = (on === undefined) ? !topbar.hasClass('expanded') : on;
+
+ if (expand) {
+ topbar.addClass('expanded');
+ }
+ else {
+ topbar.removeClass('expanded');
+ }
+
+ if (scope.settings.scrolltop) {
+ if (!expand && topbar.hasClass('fixed')) {
+ topbar.parent().addClass('fixed');
+ topbar.removeClass('fixed');
+ body.css('padding-top', scope.originalHeight + 'px');
+ } else if (expand && topbar.parent().hasClass('fixed')) {
+ topbar.parent().removeClass('fixed');
+ topbar.addClass('fixed');
+ body.css('padding-top', '');
+ $window.scrollTo(0,0);
+ }
+ } else {
+ if(isSticky()) {
+ topbar.parent().addClass('fixed');
+ }
+
+ if(topbar.parent().hasClass('fixed')) {
+ if (!expand) {
+ topbar.removeClass('fixed');
+ topbar.parent().removeClass('expanded');
+ updateStickyPositioning();
+ } else {
+ topbar.addClass('fixed');
+ topbar.parent().addClass('expanded');
+ body.css('padding-top', scope.originalHeight + 'px');
+ }
+ }
+ }
+ };
+
+ if(topbarContainer.hasClass('fixed') || isSticky() ) {
+ scope.stickyTopbar = true;
+ scope.height = topbarContainer[0].offsetHeight;
+ var stickyoffset = topbarContainer[0].getBoundingClientRect().top + $window.pageYOffset;
+ } else {
+ scope.height = topbar[0].offsetHeight;
+ }
+
+ scope.originalHeight = scope.height;
+
+ scope.$watch('height', function(h) {
+ if(h){
+ topbar.css('height', h + 'px');
+ } else {
+ topbar.css('height', '');
+ }
+ });
+
+ angular.element($window).bind('resize', onResize);
+ angular.element($window).bind('scroll', onScroll);
+
+ scope.$on('$destroy', function() {
+ angular.element($window).unbind('resize', onResize);
+ angular.element($window).unbind('scroll', onScroll);
+ });
+
+ if (topbarContainer.hasClass('fixed')) {
+ body.css('padding-top', scope.originalHeight + 'px');
+ }
+ }
+ };
+ }]
+)
+.directive('toggleTopBar', ['closest', function (closest) {
+ return {
+ scope: {},
+ require: '^topBar',
+ restrict: 'A',
+ replace: true,
+ templateUrl: 'template/topbar/toggle-top-bar.html',
+ transclude: true,
+ link: function(scope, element, attrs, topBar) {
+ element.bind('click', function(event) {
+ var li = closest(angular.element(event.currentTarget), 'li');
+ if(!li.hasClass('back') && !li.hasClass('has-dropdown')) {
+ topBar.toggle();
+ }
+ });
+
+ scope.$on('$destroy', function() {
+ element.unbind('click');
+ });
+ }
+ };
+}])
+.directive('topBarSection', ['$compile', 'closest', function($compile, closest) {
+ return {
+ scope: {},
+ require: '^topBar',
+ restrict: 'EA',
+ replace: true,
+ templateUrl: 'template/topbar/top-bar-section.html',
+ transclude: true,
+ link: function(scope, element, attrs, topBar) {
+ var section = element;
+
+ scope.reset = function() {
+ angular.element(section[0].querySelectorAll('li.moved')).removeClass('moved');
+ };
+
+ scope.move = function(dir, index) {
+ if(dir === 'left'){
+ section.css({"left": index * -100 + '%'});
+ }
+ else {
+ section.css({"right": index * -100 + '%'});
+ }
+ };
+
+ topBar.addSection(scope);
+
+ scope.$on("$destroy", function() {
+ topBar.removeSection(scope);
+ });
+
+ // Top level links close menu on click
+ var links = section[0].querySelectorAll('li>a');
+ angular.forEach(links, function(link) {
+ var $link = angular.element(link);
+ var li = closest($link, 'li');
+ if (li.hasClass('has-dropdown') || li.hasClass('back') || li.hasClass('title')) {
+ return;
+ }
+
+ $link.bind('click', function() {
+ topBar.toggle(false);
+ });
+
+ scope.$on('$destroy', function() {
+ $link.bind('click');
+ });
+ });
+ }
+ };
+}])
+.directive('hasDropdown', ['mediaQueries', function (mediaQueries) {
+ return {
+ scope: {},
+ require: '^topBar',
+ restrict: 'A',
+ templateUrl: 'template/topbar/has-dropdown.html',
+ replace: true,
+ transclude: true,
+ link: function(scope, element, attrs, topBar) {
+ scope.triggerLink = element.children('a')[0];
+
+ var $link = angular.element(scope.triggerLink);
+
+ $link.bind('click', function(event) {
+ topBar.forward(event);
+ });
+ scope.$on('$destroy', function() {
+ $link.unbind('click');
+ });
+
+ element.bind('mouseenter', function() {
+ if(topBar.settings.isHover && !mediaQueries.topbarBreakpoint()){
+ element.addClass('not-click');
+ }
+ });
+ element.bind('click', function(event) {
+ if(!topBar.settings.isHover && !mediaQueries.topbarBreakpoint()){
+ element.toggleClass('not-click');
+ }
+ });
+
+ element.bind('mouseleave', function() {
+ element.removeClass('not-click');
+ });
+
+ scope.$on('$destroy', function() {
+ element.unbind('click');
+ element.unbind('mouseenter');
+ element.unbind('mouseleave');
+ });
+ },
+ controller: ['$window', '$scope', function($window, $scope) {
+ this.triggerLink = $scope.triggerLink;
+ }]
+ };
+}])
+.directive('topBarDropdown', ['$compile', function($compile) {
+ return {
+ scope: {},
+ require: ['^topBar', '^hasDropdown'],
+ restrict: 'A',
+ replace: true,
+ templateUrl: 'template/topbar/top-bar-dropdown.html',
+ transclude: true,
+ link: function(scope, element, attrs, ctrls) {
+ var topBar = ctrls[0];
+ var hasDropdown = ctrls[1];
+ var $link = angular.element(hasDropdown.triggerLink);
+ var url = $link.attr('href');
+ var $titleLi;
+
+ scope.linkText = $link.text();
+
+ scope.back = function(event) {
+ topBar.back(event);
+ };
+
+ // Add back link
+ if (topBar.settings.customBackText) {
+ scope.backText = topBar.settings.backText;
+ } else {
+ scope.backText = '« ' + $link.html();
+ }
+
+ if (topBar.settings.mobileShowParentLink && url && url.length > 1) {
+ $titleLi = angular.element('' +
+ ' ' +
+ '{{linkText}} ');
+ } else {
+ $titleLi = angular.element('' +
+ ' ');
+ }
+
+ $compile($titleLi)(scope);
+ element.prepend($titleLi);
+ }
+ };
+}]);
+
+angular.module( 'mm.foundation.tour', [ 'mm.foundation.position', 'mm.foundation.tooltip' ] )
+
+.service( '$tour', [ '$window', function ( $window ) {
+ var currentIndex = getStoredCurrentStep();
+ var ended = false;
+ var steps = {};
+
+ function getStoredCurrentStep() {
+ try {
+ return parseInt( $window.localStorage.getItem( 'mm.tour.step' ), 10 );
+ } catch(e) {
+ if (e.name !== "SecurityError") {
+ throw e;
+ }
+ }
+ }
+
+ function storeCurrentStep() {
+ try {
+ $window.localStorage.setItem( 'mm.tour.step', currentIndex );
+ } catch(e) {
+ if (e.name !== "SecurityError") {
+ throw e;
+ }
+ }
+ }
+
+ function setCurrentStep(step) {
+ currentIndex = step;
+ storeCurrentStep();
+ }
+
+ this.add = function ( index, attrs ) {
+ steps[ index ] = attrs;
+ };
+
+ this.has = function ( index ) {
+ return !!steps[ index ];
+ };
+
+ this.isActive = function () {
+ return currentIndex > 0;
+ };
+
+ this.current = function ( index ) {
+ if ( index ) {
+ setCurrentStep( currentIndex );
+ } else {
+ return currentIndex;
+ }
+ };
+
+ this.start = function () {
+ setCurrentStep( 1 );
+ };
+
+ this.next = function () {
+ setCurrentStep( currentIndex + 1 );
+ };
+
+ this.end = function () {
+ setCurrentStep( 0 );
+ };
+}])
+
+.directive( 'stepTextPopup', ['$tour', function ( $tour ) {
+ return {
+ restrict: 'EA',
+ replace: true,
+ scope: { title: '@', content: '@', placement: '@', animation: '&', isOpen: '&' },
+ templateUrl: 'template/tour/tour.html',
+ link: function (scope, element) {
+ scope.isLastStep = function () {
+ return !$tour.has( $tour.current() + 1 );
+ };
+
+ scope.endTour = function () {
+ element.remove();
+ $tour.end();
+ };
+
+ scope.nextStep = function () {
+ element.remove();
+ $tour.next();
+ };
+
+ scope.$on('$locationChangeSuccess', scope.endTour);
+ }
+ };
+}])
+
+.directive( 'stepText', [ '$position', '$tooltip', '$tour', '$window', function ( $position, $tooltip, $tour, $window ) {
+ function isElementInViewport( element ) {
+ var rect = element[0].getBoundingClientRect();
+
+ return (
+ rect.top >= 0 &&
+ rect.left >= 0 &&
+ rect.bottom <= ($window.innerHeight - 80) &&
+ rect.right <= $window.innerWidth
+ );
+ }
+
+ function show( scope, element, attrs ) {
+ var index = parseInt( attrs.stepIndex, 10);
+
+ if ( $tour.isActive() && index ) {
+ $tour.add( index, attrs );
+
+ if ( index === $tour.current() ) {
+ if ( !isElementInViewport( element ) ) {
+ var offset = $position.offset( element );
+ $window.scrollTo( 0, offset.top - $window.innerHeight / 2 );
+ }
+
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ return $tooltip( 'stepText', 'step', show );
+}]);
+
+angular.module('mm.foundation.typeahead', ['mm.foundation.position', 'mm.foundation.bindHtml'])
+
+/**
+ * A helper service that can parse typeahead's syntax (string provided by users)
+ * Extracted to a separate service for ease of unit testing
+ */
+ .factory('typeaheadParser', ['$parse', function ($parse) {
+
+ // 00000111000000000000022200000000000000003333333333333330000000000044000
+ var TYPEAHEAD_REGEXP = /^\s*(.*?)(?:\s+as\s+(.*?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+(.*)$/;
+
+ return {
+ parse:function (input) {
+
+ var match = input.match(TYPEAHEAD_REGEXP);
+ if (!match) {
+ throw new Error(
+ "Expected typeahead specification in form of '_modelValue_ (as _label_)? for _item_ in _collection_'" +
+ " but got '" + input + "'.");
+ }
+
+ return {
+ itemName:match[3],
+ source:$parse(match[4]),
+ viewMapper:$parse(match[2] || match[1]),
+ modelMapper:$parse(match[1])
+ };
+ }
+ };
+}])
+
+ .directive('typeahead', ['$compile', '$parse', '$q', '$timeout', '$document', '$position', 'typeaheadParser',
+ function ($compile, $parse, $q, $timeout, $document, $position, typeaheadParser) {
+
+ var HOT_KEYS = [9, 13, 27, 38, 40];
+
+ return {
+ require:'ngModel',
+ link:function (originalScope, element, attrs, modelCtrl) {
+
+ //SUPPORTED ATTRIBUTES (OPTIONS)
+
+ //minimal no of characters that needs to be entered before typeahead kicks-in
+ var minSearch = originalScope.$eval(attrs.typeaheadMinLength) || 1;
+
+ //minimal wait time after last character typed before typehead kicks-in
+ var waitTime = originalScope.$eval(attrs.typeaheadWaitMs) || 0;
+
+ //should it restrict model values to the ones selected from the popup only?
+ var isEditable = originalScope.$eval(attrs.typeaheadEditable) !== false;
+
+ //binding to a variable that indicates if matches are being retrieved asynchronously
+ var isLoadingSetter = $parse(attrs.typeaheadLoading).assign || angular.noop;
+
+ //a callback executed when a match is selected
+ var onSelectCallback = $parse(attrs.typeaheadOnSelect);
+
+ var inputFormatter = attrs.typeaheadInputFormatter ? $parse(attrs.typeaheadInputFormatter) : undefined;
+
+ var appendToBody = attrs.typeaheadAppendToBody ? $parse(attrs.typeaheadAppendToBody) : false;
+
+ //INTERNAL VARIABLES
+
+ //model setter executed upon match selection
+ var $setModelValue = $parse(attrs.ngModel).assign;
+
+ //expressions used by typeahead
+ var parserResult = typeaheadParser.parse(attrs.typeahead);
+
+ var hasFocus;
+
+ //pop-up element used to display matches
+ var popUpEl = angular.element('
');
+ popUpEl.attr({
+ matches: 'matches',
+ active: 'activeIdx',
+ select: 'select(activeIdx)',
+ query: 'query',
+ position: 'position'
+ });
+ //custom item template
+ if (angular.isDefined(attrs.typeaheadTemplateUrl)) {
+ popUpEl.attr('template-url', attrs.typeaheadTemplateUrl);
+ }
+
+ //create a child scope for the typeahead directive so we are not polluting original scope
+ //with typeahead-specific data (matches, query etc.)
+ var scope = originalScope.$new();
+ originalScope.$on('$destroy', function(){
+ scope.$destroy();
+ });
+
+ var resetMatches = function() {
+ scope.matches = [];
+ scope.activeIdx = -1;
+ };
+
+ var getMatchesAsync = function(inputValue) {
+
+ var locals = {$viewValue: inputValue};
+ isLoadingSetter(originalScope, true);
+ $q.when(parserResult.source(originalScope, locals)).then(function(matches) {
+
+ //it might happen that several async queries were in progress if a user were typing fast
+ //but we are interested only in responses that correspond to the current view value
+ if (inputValue === modelCtrl.$viewValue && hasFocus) {
+ if (matches.length > 0) {
+
+ scope.activeIdx = 0;
+ scope.matches.length = 0;
+
+ //transform labels
+ for(var i=0; i= minSearch) {
+ if (waitTime > 0) {
+ if (timeoutPromise) {
+ $timeout.cancel(timeoutPromise);//cancel previous timeout
+ }
+ timeoutPromise = $timeout(function () {
+ getMatchesAsync(inputValue);
+ }, waitTime);
+ } else {
+ getMatchesAsync(inputValue);
+ }
+ } else {
+ isLoadingSetter(originalScope, false);
+ resetMatches();
+ }
+
+ if (isEditable) {
+ return inputValue;
+ } else {
+ if (!inputValue) {
+ // Reset in case user had typed something previously.
+ modelCtrl.$setValidity('editable', true);
+ return inputValue;
+ } else {
+ modelCtrl.$setValidity('editable', false);
+ return undefined;
+ }
+ }
+ });
+
+ modelCtrl.$formatters.push(function (modelValue) {
+
+ var candidateViewValue, emptyViewValue;
+ var locals = {};
+
+ if (inputFormatter) {
+
+ locals['$model'] = modelValue;
+ return inputFormatter(originalScope, locals);
+
+ } else {
+
+ //it might happen that we don't have enough info to properly render input value
+ //we need to check for this situation and simply return model value if we can't apply custom formatting
+ locals[parserResult.itemName] = modelValue;
+ candidateViewValue = parserResult.viewMapper(originalScope, locals);
+ locals[parserResult.itemName] = undefined;
+ emptyViewValue = parserResult.viewMapper(originalScope, locals);
+
+ return candidateViewValue!== emptyViewValue ? candidateViewValue : modelValue;
+ }
+ });
+
+ scope.select = function (activeIdx) {
+ //called from within the $digest() cycle
+ var locals = {};
+ var model, item;
+
+ locals[parserResult.itemName] = item = scope.matches[activeIdx].model;
+ model = parserResult.modelMapper(originalScope, locals);
+ $setModelValue(originalScope, model);
+ modelCtrl.$setValidity('editable', true);
+
+ onSelectCallback(originalScope, {
+ $item: item,
+ $model: model,
+ $label: parserResult.viewMapper(originalScope, locals)
+ });
+
+ resetMatches();
+
+ //return focus to the input element if a mach was selected via a mouse click event
+ element[0].focus();
+ };
+
+ //bind keyboard events: arrows up(38) / down(40), enter(13) and tab(9), esc(27)
+ element.bind('keydown', function (evt) {
+
+ //typeahead is open and an "interesting" key was pressed
+ if (scope.matches.length === 0 || HOT_KEYS.indexOf(evt.which) === -1) {
+ return;
+ }
+
+ evt.preventDefault();
+
+ if (evt.which === 40) {
+ scope.activeIdx = (scope.activeIdx + 1) % scope.matches.length;
+ scope.$digest();
+
+ } else if (evt.which === 38) {
+ scope.activeIdx = (scope.activeIdx ? scope.activeIdx : scope.matches.length) - 1;
+ scope.$digest();
+
+ } else if (evt.which === 13 || evt.which === 9) {
+ scope.$apply(function () {
+ scope.select(scope.activeIdx);
+ });
+
+ } else if (evt.which === 27) {
+ evt.stopPropagation();
+
+ resetMatches();
+ scope.$digest();
+ }
+ });
+
+ element.bind('blur', function (evt) {
+ hasFocus = false;
+ });
+
+ element.bind('focus', function (evt) {
+ hasFocus = true;
+ });
+
+ // Keep reference to click handler to unbind it.
+ var dismissClickHandler = function (evt) {
+ if (element[0] !== evt.target) {
+ resetMatches();
+ scope.$digest();
+ }
+ };
+
+ $document.bind('click', dismissClickHandler);
+
+ originalScope.$on('$destroy', function(){
+ $document.unbind('click', dismissClickHandler);
+ });
+
+ var $popup = $compile(popUpEl)(scope);
+ if ( appendToBody ) {
+ $document.find('body').append($popup);
+ } else {
+ element.after($popup);
+ }
+ }
+ };
+
+}])
+
+ .directive('typeaheadPopup', function () {
+ return {
+ restrict:'EA',
+ scope:{
+ matches:'=',
+ query:'=',
+ active:'=',
+ position:'=',
+ select:'&'
+ },
+ replace:true,
+ templateUrl:'template/typeahead/typeahead-popup.html',
+ link:function (scope, element, attrs) {
+
+ scope.templateUrl = attrs.templateUrl;
+
+ scope.isOpen = function () {
+ return scope.matches.length > 0;
+ };
+
+ scope.isActive = function (matchIdx) {
+ return scope.active == matchIdx;
+ };
+
+ scope.selectActive = function (matchIdx) {
+ scope.active = matchIdx;
+ };
+
+ scope.selectMatch = function (activeIdx) {
+ scope.select({activeIdx:activeIdx});
+ };
+ }
+ };
+ })
+
+ .directive('typeaheadMatch', ['$http', '$templateCache', '$compile', '$parse', function ($http, $templateCache, $compile, $parse) {
+ return {
+ restrict:'EA',
+ scope:{
+ index:'=',
+ match:'=',
+ query:'='
+ },
+ link:function (scope, element, attrs) {
+ var tplUrl = $parse(attrs.templateUrl)(scope.$parent) || 'template/typeahead/typeahead-match.html';
+ $http.get(tplUrl, {cache: $templateCache}).success(function(tplContent){
+ element.replaceWith($compile(tplContent.trim())(scope));
+ });
+ }
+ };
+ }])
+
+ .filter('typeaheadHighlight', function() {
+
+ function escapeRegexp(queryToEscape) {
+ return queryToEscape.replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1");
+ }
+
+ return function(matchItem, query) {
+ return query ? matchItem.replace(new RegExp(escapeRegexp(query), 'gi'), '$& ') : matchItem;
+ };
+ });
+
+angular.module("template/accordion/accordion-group.html", []).run(["$templateCache", function($templateCache) {
+ $templateCache.put("template/accordion/accordion-group.html",
+ "\n" +
+ " {{heading}} \n" +
+ "
\n" +
+ " \n" +
+ "");
+}]);
+
+angular.module("template/accordion/accordion.html", []).run(["$templateCache", function($templateCache) {
+ $templateCache.put("template/accordion/accordion.html",
+ " \n" +
+ "");
+}]);
+
+angular.module("template/alert/alert.html", []).run(["$templateCache", function($templateCache) {
+ $templateCache.put("template/alert/alert.html",
+ "\n" +
+ "
\n" +
+ "
× \n" +
+ "
\n" +
+ "");
+}]);
+
+angular.module("template/modal/backdrop.html", []).run(["$templateCache", function($templateCache) {
+ $templateCache.put("template/modal/backdrop.html",
+ "
\n" +
+ "");
+}]);
+
+angular.module("template/modal/window.html", []).run(["$templateCache", function($templateCache) {
+ $templateCache.put("template/modal/window.html",
+ "\n" +
+ "");
+}]);
+
+angular.module("template/pagination/pager.html", []).run(["$templateCache", function($templateCache) {
+ $templateCache.put("template/pagination/pager.html",
+ "\n" +
+ "");
+}]);
+
+angular.module("template/pagination/pagination.html", []).run(["$templateCache", function($templateCache) {
+ $templateCache.put("template/pagination/pagination.html",
+ "\n" +
+ "");
+}]);
+
+angular.module("template/tooltip/tooltip-html-unsafe-popup.html", []).run(["$templateCache", function($templateCache) {
+ $templateCache.put("template/tooltip/tooltip-html-unsafe-popup.html",
+ "\n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ "");
+}]);
+
+angular.module("template/tooltip/tooltip-popup.html", []).run(["$templateCache", function($templateCache) {
+ $templateCache.put("template/tooltip/tooltip-popup.html",
+ "\n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ "");
+}]);
+
+angular.module("template/popover/popover.html", []).run(["$templateCache", function($templateCache) {
+ $templateCache.put("template/popover/popover.html",
+ "\n" +
+ "
\n" +
+ "
\n" +
+ "
\n" +
+ "
\n" +
+ "
\n" +
+ "
\n" +
+ "");
+}]);
+
+angular.module("template/progressbar/bar.html", []).run(["$templateCache", function($templateCache) {
+ $templateCache.put("template/progressbar/bar.html",
+ " \n" +
+ "");
+}]);
+
+angular.module("template/progressbar/progress.html", []).run(["$templateCache", function($templateCache) {
+ $templateCache.put("template/progressbar/progress.html",
+ "
\n" +
+ "");
+}]);
+
+angular.module("template/progressbar/progressbar.html", []).run(["$templateCache", function($templateCache) {
+ $templateCache.put("template/progressbar/progressbar.html",
+ "\n" +
+ " \n" +
+ "
\n" +
+ "");
+}]);
+
+angular.module("template/rating/rating.html", []).run(["$templateCache", function($templateCache) {
+ $templateCache.put("template/rating/rating.html",
+ "\n" +
+ " \n" +
+ " \n" +
+ "");
+}]);
+
+angular.module("template/tabs/tab.html", []).run(["$templateCache", function($templateCache) {
+ $templateCache.put("template/tabs/tab.html",
+ "\n" +
+ " {{heading}} \n" +
+ " \n" +
+ "");
+}]);
+
+angular.module("template/tabs/tabset.html", []).run(["$templateCache", function($templateCache) {
+ $templateCache.put("template/tabs/tabset.html",
+ "\n" +
+ "
\n" +
+ "
\n" +
+ "
\n" +
+ "");
+}]);
+
+angular.module("template/tabs/tabset.html.BACKUP.5992.html", []).run(["$templateCache", function($templateCache) {
+ $templateCache.put("template/tabs/tabset.html.BACKUP.5992.html",
+ "\n" +
+ "
\n" +
+ "
\n" +
+ "
\n" +
+ "");
+}]);
+
+angular.module("template/tabs/tabset.html.BACKUP.7584.html", []).run(["$templateCache", function($templateCache) {
+ $templateCache.put("template/tabs/tabset.html.BACKUP.7584.html",
+ "\n" +
+ "
\n" +
+ "
\n" +
+ "
\n" +
+ "");
+}]);
+
+angular.module("template/tabs/tabset.html.BACKUP.9724.html", []).run(["$templateCache", function($templateCache) {
+ $templateCache.put("template/tabs/tabset.html.BACKUP.9724.html",
+ "\n" +
+ "
\n" +
+ "
\n" +
+ "
\n" +
+ "");
+}]);
+
+angular.module("template/tabs/tabset.html.BACKUP.9820.html", []).run(["$templateCache", function($templateCache) {
+ $templateCache.put("template/tabs/tabset.html.BACKUP.9820.html",
+ "\n" +
+ "
\n" +
+ "
\n" +
+ "
\n" +
+ "");
+}]);
+
+angular.module("template/tabs/tabset.html.BASE.5992.html", []).run(["$templateCache", function($templateCache) {
+ $templateCache.put("template/tabs/tabset.html.BASE.5992.html",
+ "\n" +
+ "
\n" +
+ "
\n" +
+ "
\n" +
+ "");
+}]);
+
+angular.module("template/tabs/tabset.html.BASE.7584.html", []).run(["$templateCache", function($templateCache) {
+ $templateCache.put("template/tabs/tabset.html.BASE.7584.html",
+ "\n" +
+ "
\n" +
+ "
\n" +
+ "
\n" +
+ "");
+}]);
+
+angular.module("template/tabs/tabset.html.BASE.9724.html", []).run(["$templateCache", function($templateCache) {
+ $templateCache.put("template/tabs/tabset.html.BASE.9724.html",
+ "\n" +
+ "
\n" +
+ "
\n" +
+ "
\n" +
+ "");
+}]);
+
+angular.module("template/tabs/tabset.html.BASE.9820.html", []).run(["$templateCache", function($templateCache) {
+ $templateCache.put("template/tabs/tabset.html.BASE.9820.html",
+ "\n" +
+ "
\n" +
+ "
\n" +
+ "
\n" +
+ "");
+}]);
+
+angular.module("template/tabs/tabset.html.LOCAL.5992.html", []).run(["$templateCache", function($templateCache) {
+ $templateCache.put("template/tabs/tabset.html.LOCAL.5992.html",
+ "\n" +
+ "
\n" +
+ "
\n" +
+ "
\n" +
+ "");
+}]);
+
+angular.module("template/tabs/tabset.html.LOCAL.7584.html", []).run(["$templateCache", function($templateCache) {
+ $templateCache.put("template/tabs/tabset.html.LOCAL.7584.html",
+ "\n" +
+ "
\n" +
+ "
\n" +
+ "
\n" +
+ "");
+}]);
+
+angular.module("template/tabs/tabset.html.LOCAL.9724.html", []).run(["$templateCache", function($templateCache) {
+ $templateCache.put("template/tabs/tabset.html.LOCAL.9724.html",
+ "\n" +
+ "
\n" +
+ "
\n" +
+ "
\n" +
+ "");
+}]);
+
+angular.module("template/tabs/tabset.html.LOCAL.9820.html", []).run(["$templateCache", function($templateCache) {
+ $templateCache.put("template/tabs/tabset.html.LOCAL.9820.html",
+ "\n" +
+ "
\n" +
+ "
\n" +
+ "
\n" +
+ "");
+}]);
+
+angular.module("template/tabs/tabset.html.REMOTE.5992.html", []).run(["$templateCache", function($templateCache) {
+ $templateCache.put("template/tabs/tabset.html.REMOTE.5992.html",
+ "");
+}]);
+
+angular.module("template/tabs/tabset.html.REMOTE.7584.html", []).run(["$templateCache", function($templateCache) {
+ $templateCache.put("template/tabs/tabset.html.REMOTE.7584.html",
+ "");
+}]);
+
+angular.module("template/tabs/tabset.html.REMOTE.9724.html", []).run(["$templateCache", function($templateCache) {
+ $templateCache.put("template/tabs/tabset.html.REMOTE.9724.html",
+ "");
+}]);
+
+angular.module("template/tabs/tabset.html.REMOTE.9820.html", []).run(["$templateCache", function($templateCache) {
+ $templateCache.put("template/tabs/tabset.html.REMOTE.9820.html",
+ "");
+}]);
+
+angular.module("template/topbar/has-dropdown.html", []).run(["$templateCache", function($templateCache) {
+ $templateCache.put("template/topbar/has-dropdown.html",
+ " ");
+}]);
+
+angular.module("template/topbar/toggle-top-bar.html", []).run(["$templateCache", function($templateCache) {
+ $templateCache.put("template/topbar/toggle-top-bar.html",
+ " ");
+}]);
+
+angular.module("template/topbar/top-bar-dropdown.html", []).run(["$templateCache", function($templateCache) {
+ $templateCache.put("template/topbar/top-bar-dropdown.html",
+ "");
+}]);
+
+angular.module("template/topbar/top-bar-section.html", []).run(["$templateCache", function($templateCache) {
+ $templateCache.put("template/topbar/top-bar-section.html",
+ "");
+}]);
+
+angular.module("template/topbar/top-bar.html", []).run(["$templateCache", function($templateCache) {
+ $templateCache.put("template/topbar/top-bar.html",
+ " ");
+}]);
+
+angular.module("template/tour/tour.html", []).run(["$templateCache", function($templateCache) {
+ $templateCache.put("template/tour/tour.html",
+ "\n" +
+ "
\n" +
+ "
\n" +
+ "
\n" +
+ "
\n" +
+ "
Next \n" +
+ "
End \n" +
+ "
× \n" +
+ "
\n" +
+ "
\n" +
+ "");
+}]);
+
+angular.module("template/typeahead/typeahead-match.html", []).run(["$templateCache", function($templateCache) {
+ $templateCache.put("template/typeahead/typeahead-match.html",
+ " ");
+}]);
+
+angular.module("template/typeahead/typeahead-popup.html", []).run(["$templateCache", function($templateCache) {
+ $templateCache.put("template/typeahead/typeahead-popup.html",
+ "\n" +
+ " \n" +
+ "
\n" +
+ " \n" +
+ " \n" +
+ "");
+}]);
diff --git a/mm-foundation-tpls-0.9.2.min.js b/mm-foundation-tpls-0.9.2.min.js
new file mode 100644
index 0000000..38fde81
--- /dev/null
+++ b/mm-foundation-tpls-0.9.2.min.js
@@ -0,0 +1,10 @@
+/*
+ * angular-mm-foundation
+ * http://pineconellc.github.io/angular-foundation/
+
+ * Version: 0.9.2 - 2016-05-16
+ * License: MIT
+ * (c) Pinecone, LLC
+ */
+angular.module("mm.foundation",["mm.foundation.tpls","mm.foundation.accordion","mm.foundation.alert","mm.foundation.bindHtml","mm.foundation.buttons","mm.foundation.position","mm.foundation.mediaQueries","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"]),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/tabs/tabset.html.BACKUP.5992.html","template/tabs/tabset.html.BACKUP.7584.html","template/tabs/tabset.html.BACKUP.9724.html","template/tabs/tabset.html.BACKUP.9820.html","template/tabs/tabset.html.BASE.5992.html","template/tabs/tabset.html.BASE.7584.html","template/tabs/tabset.html.BASE.9724.html","template/tabs/tabset.html.BASE.9820.html","template/tabs/tabset.html.LOCAL.5992.html","template/tabs/tabset.html.LOCAL.7584.html","template/tabs/tabset.html.LOCAL.9724.html","template/tabs/tabset.html.LOCAL.9820.html","template/tabs/tabset.html.REMOTE.5992.html","template/tabs/tabset.html.REMOTE.7584.html","template/tabs/tabset.html.REMOTE.9724.html","template/tabs/tabset.html.REMOTE.9820.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"]),angular.module("mm.foundation.accordion",[]).constant("accordionConfig",{closeOthers:!0}).controller("AccordionController",["$scope","$attrs","accordionConfig",function(a,b,c){this.groups=[],this.closeOthers=function(d){var e=angular.isDefined(b.closeOthers)?a.$eval(b.closeOthers):c.closeOthers;e&&angular.forEach(this.groups,function(a){a!==d&&(a.isOpen=!1)})},this.addGroup=function(a){var b=this;this.groups.push(a),a.$on("$destroy",function(c){b.removeGroup(a)})},this.removeGroup=function(a){var b=this.groups.indexOf(a);-1!==b&&this.groups.splice(b,1)}}]).directive("accordion",function(){return{restrict:"EA",controller:"AccordionController",transclude:!0,replace:!1,templateUrl:"template/accordion/accordion.html"}}).directive("accordionGroup",["$parse",function(a){return{require:"^accordion",restrict:"EA",transclude:!0,replace:!0,templateUrl:"template/accordion/accordion-group.html",scope:{heading:"@",cbOpen:"&toggleOpen"},controller:function(){this.setHeading=function(a){this.heading=a}},link:function(b,c,d,e){var f,g;e.addGroup(b),b.isOpen=!1,d.isOpen&&(f=a(d.isOpen),g=f.assign,b.$parent.$watch(f,function(a){b.isOpen=!!a})),b.$watch("isOpen",function(a){a&&(e.closeOthers(b),b.cbOpen()),g&&g(b.$parent,a)})}}}]).directive("accordionHeading",function(){return{restrict:"EA",transclude:!0,template:"",replace:!0,require:"^accordionGroup",compile:function(a,b,c){return function(a,b,d,e){e.setHeading(c(a,function(){}))}}}}).directive("accordionTransclude",function(){return{require:"^accordionGroup",link:function(a,b,c,d){a.$watch(function(){return d[c.accordionTransclude]},function(a){a&&(b.html(""),b.append(a))})}}}),angular.module("mm.foundation.alert",[]).controller("AlertController",["$scope","$attrs",function(a,b){a.closeable="close"in b&&"undefined"!=typeof b.close}]).directive("alert",function(){return{restrict:"EA",controller:"AlertController",templateUrl:"template/alert/alert.html",transclude:!0,replace:!0,scope:{type:"=",close:"&"}}}),angular.module("mm.foundation.bindHtml",[]).directive("bindHtmlUnsafe",function(){return function(a,b,c){b.addClass("ng-binding").data("$binding",c.bindHtmlUnsafe),a.$watch(c.bindHtmlUnsafe,function(a){b.html(a||"")})}}),angular.module("mm.foundation.buttons",[]).constant("buttonConfig",{activeClass:"active",toggleEvent:"click"}).controller("ButtonsController",["buttonConfig",function(a){this.activeClass=a.activeClass,this.toggleEvent=a.toggleEvent}]).directive("btnRadio",function(){return{require:["btnRadio","ngModel"],controller:"ButtonsController",link:function(a,b,c,d){var e=d[0],f=d[1];f.$render=function(){b.toggleClass(e.activeClass,angular.equals(f.$modelValue,a.$eval(c.btnRadio)))},b.bind(e.toggleEvent,function(){b.hasClass(e.activeClass)||a.$apply(function(){f.$setViewValue(a.$eval(c.btnRadio)),f.$render()})})}}}).directive("btnCheckbox",function(){return{require:["btnCheckbox","ngModel"],controller:"ButtonsController",link:function(a,b,c,d){function e(){return g(c.btnCheckboxTrue,!0)}function f(){return g(c.btnCheckboxFalse,!1)}function g(b,c){var d=a.$eval(b);return angular.isDefined(d)?d:c}var h=d[0],i=d[1];i.$render=function(){b.toggleClass(h.activeClass,angular.equals(i.$modelValue,e()))},b.bind(h.toggleEvent,function(){a.$apply(function(){i.$setViewValue(b.hasClass(h.activeClass)?f():e()),i.$render()})})}}}),angular.module("mm.foundation.position",[]).factory("$position",["$document","$window",function(a,b){function c(a,c){return a.currentStyle?a.currentStyle[c]:b.getComputedStyle?b.getComputedStyle(a)[c]:a.style[c]}function d(a){return"static"===(c(a,"position")||"static")}var e=function(b){for(var c=a[0],e=b.offsetParent||c;e&&e!==c&&d(e);)e=e.offsetParent;return e||c};return{position:function(b){var c=this.offset(b),d={top:0,left:0},f=e(b[0]);f!=a[0]&&(d=this.offset(angular.element(f)),d.top+=f.clientTop-f.scrollTop,d.left+=f.clientLeft-f.scrollLeft);var g=b[0].getBoundingClientRect();return{width:g.width||b.prop("offsetWidth"),height:g.height||b.prop("offsetHeight"),top:c.top-d.top,left:c.left-d.left}},offset:function(c){var d=c[0].getBoundingClientRect();return{width:d.width||c.prop("offsetWidth"),height:d.height||c.prop("offsetHeight"),top:d.top+(b.pageYOffset||a[0].body.scrollTop||a[0].documentElement.scrollTop),left:d.left+(b.pageXOffset||a[0].body.scrollLeft||a[0].documentElement.scrollLeft)}}}}]),angular.module("mm.foundation.mediaQueries",[]).factory("matchMedia",["$document","$window",function(a,b){return b.matchMedia||function(a,b){var c,d=a.documentElement,e=d.firstElementChild||d.firstChild,f=a.createElement("body"),g=a.createElement("div");return g.id="mq-test-1",g.style.cssText="position:absolute;top:-100em",f.style.background="none",f.appendChild(g),function(a){return g.innerHTML='',d.insertBefore(f,e),c=42===g.offsetWidth,d.removeChild(f),{matches:c,media:a}}}(a[0])}]).factory("mediaQueries",["$document","matchMedia",function(a,b){var c=angular.element(a[0].querySelector("head"));c.append(' '),c.append(' '),c.append(' '),c.append(' ');var d=/^[\/\\'"]+|(;\s?})+|[\/\\'"]+$/g,e={topbar:getComputedStyle(c[0].querySelector("meta.foundation-mq-topbar")).fontFamily.replace(d,""),small:getComputedStyle(c[0].querySelector("meta.foundation-mq-small")).fontFamily.replace(d,""),medium:getComputedStyle(c[0].querySelector("meta.foundation-mq-medium")).fontFamily.replace(d,""),large:getComputedStyle(c[0].querySelector("meta.foundation-mq-large")).fontFamily.replace(d,"")};return{topbarBreakpoint:function(){return!b(e.topbar).matches},small:function(){return b(e.small).matches},medium:function(){return b(e.medium).matches},large:function(){return b(e.large).matches}}}]),angular.module("mm.foundation.dropdownToggle",["mm.foundation.position","mm.foundation.mediaQueries"]).controller("DropdownToggleController",["$scope","$attrs","mediaQueries",function(a,b,c){this.small=function(){return c.small()&&!c.medium()}}]).directive("dropdownToggle",["$document","$window","$location","$position",function(a,b,c,d){var e=null,f=angular.noop;return{restrict:"CA",controller:"DropdownToggleController",link:function(c,g,h,i){var j=g.parent(),k=angular.element(a[0].querySelector(h.dropdownToggle)),l=function(){return j.hasClass("has-dropdown")},m=function(c){k=angular.element(a[0].querySelector(h.dropdownToggle));var m=g===e;if(c.preventDefault(),c.stopPropagation(),e&&f(),!m&&!g.hasClass("disabled")&&!g.prop("disabled")){k.css("display","block"),k.addClass("f-open-dropdown");var n=d.offset(g),o=d.offset(angular.element(k[0].offsetParent)),p=k.prop("offsetWidth"),q={top:n.top-o.top+n.height+"px"};if(i.small())q.left=Math.max((o.width-p)/2,8)+"px",q.position="absolute",q.width="95%",q["max-width"]="none";else{var r=Math.round(n.left-o.left),s=b.innerWidth-p-8;r>s&&(r=s,k.removeClass("left").addClass("right")),q.left=r+"px",q.position=null,q["max-width"]=null}k.css(q),g.addClass("expanded"),l()&&j.addClass("hover"),e=g,f=function(b){a.off("click",f),k.css("display","none"),k.removeClass("f-open-dropdown"),g.removeClass("expanded"),f=angular.noop,e=null,j.hasClass("hover")&&j.removeClass("hover")},a.on("click",f)}};k&&k.css("display","none"),c.$watch("$location.path",function(){f()}),g.on("click",m),g.on("$destroy",function(){g.off("click",m)})}}}]),angular.module("mm.foundation.interchange",["mm.foundation.mediaQueries"]).factory("interchangeQueries",["$document",function(a){for(var b,c,d={"default":"only screen",landscape:"only screen and (orientation: landscape)",portrait:"only screen and (orientation: portrait)",retina:"only screen and (-webkit-min-device-pixel-ratio: 2),only screen and (min--moz-device-pixel-ratio: 2),only screen and (-o-min-device-pixel-ratio: 2/1),only screen and (min-device-pixel-ratio: 2),only screen and (min-resolution: 192dpi),only screen and (min-resolution: 2dppx)"},e="foundation-mq-",f=["small","medium","large","xlarge","xxlarge"],g=angular.element(a[0].querySelector("head")),h=0;h '),b=getComputedStyle(g[0].querySelector("meta."+e+f[h])),c=b.fontFamily.replace(/^[\/\\'"]+|(;\s?})+|[\/\\'"]+$/g,""),d[f[h]]=c;return d}]).factory("interchangeQueriesManager",["interchangeQueries",function(a){return{add:function(b,c){return b&&c&&angular.isString(b)&&angular.isString(c)&&!a[b]?(a[b]=c,!0):!1}}}]).factory("interchangeTools",["$window","matchMedia","interchangeQueries",function(a,b,c){var d=function(a){for(var b,c=a.split(/\[(.*?)\]/),d=c.length,e=/^(.+)\,\ \((.+)\)$/,f={};d--;)c[d].replace(/[\W\d]+/,"").length>4&&(b=e.exec(c[d]),b&&3===b.length&&(f[b[2]]=b[1]));return f},e=function(a){var d,e,f;for(d in a)if(e=c[d]||d,f=b(e),f.matches)return a[d]};return{parseAttribute:d,findCurrentMediaFile:e}}]).directive("interchange",["$window","$rootScope","interchangeTools",function(a,b,c){var d=/[A-Za-z0-9-_]+\.(jpg|jpeg|png|gif|bmp|tiff)\ *,/i;return{restrict:"A",scope:!0,priority:450,compile:function(e,f){return"DIV"!==e[0].nodeName||d.test(f.interchange)||e.html(' '),{pre:function(a,b,c){},post:function(d,e,f){var g,h;switch(h=e&&e[0]&&e[0].nodeName,d.fileMap=c.parseAttribute(f.interchange),h){case"DIV":g=c.findCurrentMediaFile(d.fileMap),/[A-Za-z0-9-_]+\.(jpg|jpeg|png|gif|bmp|tiff)$/i.test(g)?d.type="background":d.type="include";break;case"IMG":d.type="image";break;default:return}var i=function(a){var f=c.findCurrentMediaFile(d.fileMap);if(!d.currentFile||d.currentFile!==f){switch(d.currentFile=f,d.type){case"image":e.attr("src",d.currentFile);break;case"background":e.css("background-image","url("+d.currentFile+")")}b.$emit("replace",e,d),a&&d.$apply()}};i(),a.addEventListener("resize",i),d.$on("$destroy",function(){a.removeEventListener("resize",i)})}}}}}]),angular.module("mm.foundation.transition",[]).factory("$transition",["$q","$timeout","$rootScope",function(a,b,c){function d(a){for(var b in a)if(void 0!==f.style[b])return a[b]}var e=function(d,f,g){g=g||{};var h=a.defer(),i=e[g.animation?"animationEndEventName":"transitionEndEventName"],j=function(a){c.$apply(function(){d.unbind(i,j),h.resolve(d)})};return i&&d.bind(i,j),b(function(){angular.isString(f)?d.addClass(f):angular.isFunction(f)?f(d):angular.isObject(f)&&d.css(f),i||h.resolve(d)}),h.promise.cancel=function(){i&&d.unbind(i,j),h.reject("Transition cancelled")},h.promise},f=document.createElement("trans"),g={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd",transition:"transitionend"},h={WebkitTransition:"webkitAnimationEnd",MozTransition:"animationend",OTransition:"oAnimationEnd",transition:"animationend"};return e.transitionEndEventName=d(g),e.animationEndEventName=d(h),e}]),angular.module("mm.foundation.modal",["mm.foundation.transition"]).factory("$$stackedMap",function(){return{createNew:function(){var a=[];return{add:function(b,c){a.push({key:b,value:c})},get:function(b){for(var c=0;c0?c[0].querySelectorAll("[autofocus]")[0].focus():c[0].focus()})}}}]).factory("$modalStack",["$window","$transition","$timeout","$document","$compile","$rootScope","$$stackedMap",function(a,b,c,d,e,f,g){function h(){for(var a=-1,b=q.keys(),c=0;c0),j()})}function j(){if(m&&-1==h()){var a=n;k(m,n,150,function(){a.$destroy(),a=null}),m=void 0,n=void 0}}function k(a,d,e,f){function g(){g.done||(g.done=!0,a.remove(),f&&f())}d.animate=!1;var h=b.transitionEndEventName;if(h){var i=c(g,e);a.bind(h,function(){c.cancel(i),g(),d.$apply()})}else c(g,0)}function l(b,c){angular.isUndefined(c)&&(c=0);var d=a.pageYOffset||0;return c+d}var m,n,o,p="modal-open",q=g.createNew(),r={};return f.$watch(h,function(a){n&&(n.index=a)}),d.bind("keydown",function(a){var b;27===a.which&&(b=q.top(),b&&b.value.keyboard&&f.$apply(function(){r.dismiss(b.key)}))}),r.open=function(b,c){b.options={deferred:c.deferred,modalScope:c.scope,backdrop:c.backdrop,keyboard:c.keyboard,parent:c.parent},q.add(b,b.options);var g=d.find(c.parent).eq(0),i=h();i>=0&&!m&&(n=f.$new(!0),n.index=i,m=e("
")(n),g.append(m));var j=angular.element('
');g.append(j[0]),o=parseInt(a.getComputedStyle(j[0]).top)||0;var k=l(j,o);j.remove();var r=angular.element('
').attr({"window-class":c.windowClass,index:q.length()-1,animate:"animate"});r.html(c.content);var s=e(r)(c.scope);q.top().value.modalDomEl=s,g.append(s),g.addClass(p)},r.reposition=function(a){var b=q.get(a).value;if(b){var c=b.modalDomEl,d=l(c,o);c.css("top",d+"px")}},r.close=function(a,b){var c=q.get(a);c&&(c.value.deferred.resolve(b),i(a))},r.dismiss=function(a,b){var c=q.get(a);c&&(c.value.deferred.reject(b),i(a))},r.dismissAll=function(a){for(var b=this.getTop();b;)this.dismiss(b.key,a),b=this.getTop()},r.getTop=function(){return q.top()},r}]).provider("$modal",function(){var a={options:{backdrop:!0,keyboard:!0},$get:["$injector","$rootScope","$q","$http","$templateCache","$controller","$modalStack",function(b,c,d,e,f,g,h){function i(a){return a.template?d.when(a.template):e.get(a.templateUrl,{cache:f}).then(function(a){return a.data})}function j(a){var c=[];return angular.forEach(a,function(a,e){(angular.isFunction(a)||angular.isArray(a))&&c.push(d.when(b.invoke(a)))}),c}var k={};return k.open=function(b){var e=d.defer(),f=d.defer(),k={result:e.promise,opened:f.promise,close:function(a){h.close(k,a)},dismiss:function(a){h.dismiss(k,a)},reposition:function(){h.reposition(k)}};if(b=angular.extend({},a.options,b),b.resolve=b.resolve||{},!b.template&&!b.templateUrl)throw new Error("One of template or templateUrl options is required.");var l=d.all([i(b)].concat(j(b.resolve)));return l.then(function(a){var d=(b.scope||c).$new();d.$close=k.close,d.$dismiss=k.dismiss;var f,i={},j=1;b.controller&&(i.$scope=d,i.$modalInstance=k,angular.forEach(b.resolve,function(b,c){i[c]=a[j++]}),f=g(b.controller,i),b.controllerAs&&(d[b.controllerAs]=f)),h.open(k,{scope:d,deferred:e,content:a[0],backdrop:b.backdrop,keyboard:b.keyboard,windowClass:b.windowClass,parent:b.parent||"body"})},function(a){e.reject(a)}),l.then(function(){f.resolve(!0)},function(){f.reject(!1)}),k},k}]};return a}),angular.module("mm.foundation.offcanvas",[]).directive("offCanvasWrap",["$window",function(a){return{scope:{},restrict:"C",link:function(b,c,d){var e=angular.element(a),f=b.sidebar=c;b.hide=function(){f.removeClass("move-left"),f.removeClass("move-right")},e.bind("resize.body",b.hide),b.$on("$destroy",function(){e.unbind("resize.body",b.hide)})},controller:["$scope",function(a){this.leftToggle=function(){a.sidebar.toggleClass("move-right")},this.rightToggle=function(){a.sidebar.toggleClass("move-left")},this.hide=function(){a.hide()}}]}}]).directive("leftOffCanvasToggle",[function(){return{require:"^offCanvasWrap",restrict:"C",link:function(a,b,c,d){b.on("click",function(){d.leftToggle()})}}}]).directive("rightOffCanvasToggle",[function(){return{require:"^offCanvasWrap",restrict:"C",link:function(a,b,c,d){b.on("click",function(){d.rightToggle()})}}}]).directive("exitOffCanvas",[function(){return{require:"^offCanvasWrap",restrict:"C",link:function(a,b,c,d){b.on("click",function(){d.hide()})}}}]).directive("offCanvasList",[function(){return{require:"^offCanvasWrap",restrict:"C",link:function(a,b,c,d){b.on("click",function(){d.hide()})}}}]),angular.module("mm.foundation.pagination",[]).controller("PaginationController",["$scope","$attrs","$parse","$interpolate",function(a,b,c,d){var e=this,f=b.numPages?c(b.numPages).assign:angular.noop;this.init=function(d){b.itemsPerPage?a.$parent.$watch(c(b.itemsPerPage),function(b){e.itemsPerPage=parseInt(b,10),a.totalPages=e.calculateTotalPages()}):this.itemsPerPage=d},this.noPrevious=function(){return 1===this.page},this.noNext=function(){return this.page===a.totalPages},this.isActive=function(a){return this.page===a},this.calculateTotalPages=function(){var b=this.itemsPerPage<1?1:Math.ceil(a.totalItems/this.itemsPerPage);return Math.max(b||0,1)},this.getAttributeValue=function(b,c,e){return angular.isDefined(b)?e?d(b)(a.$parent):a.$parent.$eval(b):c},this.render=function(){this.page=parseInt(a.page,10)||1,this.page>0&&this.page<=a.totalPages&&(a.pages=this.getPages(this.page,a.totalPages))},a.selectPage=function(b){!e.isActive(b)&&b>0&&b<=a.totalPages&&(a.page=b,a.onSelectPage({page:b}))},a.$watch("page",function(){e.render()}),a.$watch("totalItems",function(){a.totalPages=e.calculateTotalPages()}),a.$watch("totalPages",function(b){f(a.$parent,b),e.page>b?a.selectPage(b):e.render()})}]).constant("paginationConfig",{itemsPerPage:10,boundaryLinks:!1,directionLinks:!0,firstText:"First",previousText:"Previous",nextText:"Next",lastText:"Last",rotate:!0}).directive("pagination",["$parse","paginationConfig",function(a,b){return{restrict:"EA",scope:{page:"=",totalItems:"=",onSelectPage:" &"},controller:"PaginationController",templateUrl:"template/pagination/pagination.html",replace:!0,link:function(c,d,e,f){function g(a,b,c,d){return{number:a,text:b,active:c,disabled:d}}var h,i=f.getAttributeValue(e.boundaryLinks,b.boundaryLinks),j=f.getAttributeValue(e.directionLinks,b.directionLinks),k=f.getAttributeValue(e.firstText,b.firstText,!0),l=f.getAttributeValue(e.previousText,b.previousText,!0),m=f.getAttributeValue(e.nextText,b.nextText,!0),n=f.getAttributeValue(e.lastText,b.lastText,!0),o=f.getAttributeValue(e.rotate,b.rotate);f.init(b.itemsPerPage),e.maxSize&&c.$parent.$watch(a(e.maxSize),function(a){h=parseInt(a,10),f.render()}),f.getPages=function(a,b){var c=[],d=1,e=b,p=angular.isDefined(h)&&b>h;p&&(o?(d=Math.max(a-Math.floor(h/2),1),e=d+h-1,e>b&&(e=b,d=e-h+1)):(d=(Math.ceil(a/h)-1)*h+1,e=Math.min(d+h-1,b)));for(var q=d;e>=q;q++){var r=g(q,q,f.isActive(q),!1);c.push(r)}if(p&&!o){if(d>1){var s=g(d-1,"...",!1,!1);c.unshift(s)}if(b>e){var t=g(e+1,"...",!1,!1);c.push(t)}}if(j){var u=g(a-1,l,!1,f.noPrevious());c.unshift(u);var v=g(a+1,m,!1,f.noNext());c.push(v)}if(i){var w=g(1,k,!1,f.noPrevious());c.unshift(w);var x=g(b,n,!1,f.noNext());c.push(x)}return c}}}}]).constant("pagerConfig",{itemsPerPage:10,previousText:"« Previous",nextText:"Next »",align:!0}).directive("pager",["pagerConfig",function(a){return{restrict:"EA",scope:{page:"=",totalItems:"=",onSelectPage:" &"},controller:"PaginationController",templateUrl:"template/pagination/pager.html",replace:!0,link:function(b,c,d,e){function f(a,b,c,d,e){return{number:a,text:b,disabled:c,previous:i&&d,next:i&&e}}var g=e.getAttributeValue(d.previousText,a.previousText,!0),h=e.getAttributeValue(d.nextText,a.nextText,!0),i=e.getAttributeValue(d.align,a.align);e.init(a.itemsPerPage),e.getPages=function(a){return[f(a-1,g,e.noPrevious(),!0,!1),f(a+1,h,e.noNext(),!1,!0)]}}}}]),angular.module("mm.foundation.tooltip",["mm.foundation.position","mm.foundation.bindHtml"]).provider("$tooltip",function(){function a(a){var b=/[A-Z]/g,c="-";return a.replace(b,function(a,b){return(b?c:"")+a.toLowerCase()})}var b={placement:"top",animation:!0,popupDelay:0},c={mouseenter:"mouseleave",click:"click",focus:"blur"},d={};this.options=function(a){angular.extend(d,a)},this.setTriggers=function(a){angular.extend(c,a)},this.$get=["$window","$compile","$timeout","$parse","$document","$position","$interpolate",function(e,f,g,h,i,j,k){return function(e,l,m){function n(a){var b=a||o.trigger||m,d=c[b]||b;return{show:b,hide:d}}var o=angular.extend({},b,d),p=a(e),q=k.startSymbol(),r=k.endSymbol(),s="
';return{restrict:"EA",scope:!0,compile:function(a,b){var c=f(s);return function(a,b,d){function f(){a.tt_isOpen?m():k()}function k(){(!z||a.$eval(d[l+"Enable"]))&&(a.tt_popupDelay?(v=g(p,a.tt_popupDelay,!1),v.then(function(a){a()})):p()())}function m(){a.$apply(function(){q()})}function p(){return a.tt_content?(r(),u&&g.cancel(u),t.css({top:0,left:0,display:"block"}),w?i.find("body").append(t):b.after(t),A(),a.tt_isOpen=!0,a.$digest(),A):angular.noop}function q(){a.tt_isOpen=!1,g.cancel(v),a.tt_animation?u=g(s,500):s()}function r(){t&&s(),t=c(a,function(){}),a.$digest()}function s(){t&&(t.remove(),t=null)}var t,u,v,w=angular.isDefined(o.appendToBody)?o.appendToBody:!1,x=n(void 0),y=!1,z=angular.isDefined(d[l+"Enable"]),A=function(){var c,d,e,f;switch(c=w?j.offset(b):j.position(b),d=t.prop("offsetWidth"),e=t.prop("offsetHeight"),a.tt_placement){case"right":f={top:c.top+c.height/2-e/2,left:c.left+c.width+10};break;case"bottom":f={top:c.top+c.height+10,left:c.left};break;case"left":f={top:c.top+c.height/2-e/2,left:c.left-d-10};break;default:f={top:c.top-e-10,left:c.left}}f.top+="px",f.left+="px",t.css(f)};a.tt_isOpen=!1,d.$observe(e,function(b){a.tt_content=b,!b&&a.tt_isOpen&&q()}),d.$observe(l+"Title",function(b){a.tt_title=b}),d[l+"Placement"]=d[l+"Placement"]||null,d.$observe(l+"Placement",function(b){a.tt_placement=angular.isDefined(b)&&b?b:o.placement}),d[l+"PopupDelay"]=d[l+"PopupDelay"]||null,d.$observe(l+"PopupDelay",function(b){var c=parseInt(b,10);a.tt_popupDelay=isNaN(c)?o.popupDelay:c});var B=function(){y&&(angular.isFunction(x.show)?C():(b.unbind(x.show,k),b.unbind(x.hide,m)))},C=function(){};d[l+"Trigger"]=d[l+"Trigger"]||null,d.$observe(l+"Trigger",function(c){B(),C(),x=n(c),angular.isFunction(x.show)?C=a.$watch(function(){return x.show(a,b,d)},function(a){return g(a?p:q)}):x.show===x.hide?b.bind(x.show,f):(b.bind(x.show,k),b.bind(x.hide,m)),y=!0});var D=a.$eval(d[l+"Animation"]);a.tt_animation=angular.isDefined(D)?!!D:o.animation,d.$observe(l+"AppendToBody",function(b){w=angular.isDefined(b)?h(b)(a):w}),w&&a.$on("$locationChangeSuccess",function(){a.tt_isOpen&&q()}),a.$on("$destroy",function(){g.cancel(u),g.cancel(v),B(),C(),s()})}}}}}]}).directive("tooltipPopup",function(){return{restrict:"EA",replace:!0,scope:{content:"@",placement:"@",animation:"&",isOpen:"&"},templateUrl:"template/tooltip/tooltip-popup.html"}}).directive("tooltip",["$tooltip",function(a){return a("tooltip","tooltip","mouseenter")}]).directive("tooltipHtmlUnsafePopup",function(){return{restrict:"EA",replace:!0,scope:{content:"@",placement:"@",animation:"&",isOpen:"&"},templateUrl:"template/tooltip/tooltip-html-unsafe-popup.html"}}).directive("tooltipHtmlUnsafe",["$tooltip",function(a){return a("tooltipHtmlUnsafe","tooltip","mouseenter")}]),angular.module("mm.foundation.popover",["mm.foundation.tooltip"]).directive("popoverPopup",function(){return{restrict:"EA",replace:!0,scope:{title:"@",content:"@",placement:"@",animation:"&",isOpen:"&"},templateUrl:"template/popover/popover.html"}}).directive("popover",["$tooltip",function(a){return a("popover","popover","click")}]),angular.module("mm.foundation.progressbar",["mm.foundation.transition"]).constant("progressConfig",{animate:!0,max:100}).controller("ProgressController",["$scope","$attrs","progressConfig","$transition",function(a,b,c,d){var e=this,f=[],g=angular.isDefined(b.max)?a.$parent.$eval(b.max):c.max,h=angular.isDefined(b.animate)?a.$parent.$eval(b.animate):c.animate;this.addBar=function(a,b){var c=0,d=a.$parent.$index;angular.isDefined(d)&&f[d]&&(c=f[d].value),f.push(a),this.update(b,a.value,c),a.$watch("value",function(a,c){a!==c&&e.update(b,a,c)}),a.$on("$destroy",function(){e.removeBar(a)})},this.update=function(a,b,c){var e=this.getPercentage(b);h?(a.css("width",this.getPercentage(c)+"%"),d(a,{width:e+"%"})):a.css({transition:"none",width:e+"%"})},this.removeBar=function(a){f.splice(f.indexOf(a),1)},this.getPercentage=function(a){return Math.round(100*a/g)}}]).directive("progress",function(){return{restrict:"EA",replace:!0,transclude:!0,controller:"ProgressController",require:"progress",scope:{},template:'
'}}).directive("bar",function(){return{restrict:"EA",replace:!0,transclude:!0,require:"^progress",scope:{value:"=",type:"@"},templateUrl:"template/progressbar/bar.html",link:function(a,b,c,d){d.addBar(a,b)}}}).directive("progressbar",function(){return{restrict:"EA",replace:!0,transclude:!0,controller:"ProgressController",scope:{value:"=",type:"@"},templateUrl:"template/progressbar/progressbar.html",link:function(a,b,c,d){d.addBar(a,angular.element(b.children()[0]))}}}),angular.module("mm.foundation.rating",[]).constant("ratingConfig",{max:5,stateOn:null,stateOff:null}).controller("RatingController",["$scope","$attrs","$parse","ratingConfig",function(a,b,c,d){this.maxRange=angular.isDefined(b.max)?a.$parent.$eval(b.max):d.max,this.stateOn=angular.isDefined(b.stateOn)?a.$parent.$eval(b.stateOn):d.stateOn,this.stateOff=angular.isDefined(b.stateOff)?a.$parent.$eval(b.stateOff):d.stateOff,this.createRateObjects=function(a){for(var b={stateOn:this.stateOn,stateOff:this.stateOff},c=0,d=a.length;d>c;c++)a[c]=angular.extend({index:c},b,a[c]);return a},a.range=angular.isDefined(b.ratingStates)?this.createRateObjects(angular.copy(a.$parent.$eval(b.ratingStates))):this.createRateObjects(new Array(this.maxRange)),a.rate=function(b){a.value===b||a.readonly||(a.value=b)},a.enter=function(b){a.readonly||(a.val=b),a.onHover({value:b})},a.reset=function(){a.val=angular.copy(a.value),a.onLeave()},a.$watch("value",function(b){a.val=b}),a.readonly=!1,b.readonly&&a.$parent.$watch(c(b.readonly),function(b){a.readonly=!!b})}]).directive("rating",function(){return{restrict:"EA",scope:{value:"=",onHover:"&",onLeave:"&"},controller:"RatingController",templateUrl:"template/rating/rating.html",replace:!0}}),angular.module("mm.foundation.tabs",[]).controller("TabsetController",["$scope",function(a){var b=this,c=b.tabs=a.tabs=[];angular.isUndefined(a.openOnLoad)&&(a.openOnLoad=!0),b.select=function(a){angular.forEach(c,function(a){a.active=!1}),a.active=!0},b.addTab=function(d){c.push(d),a.openOnLoad&&(1===c.length||d.active)&&b.select(d)},b.removeTab=function(a){var d=c.indexOf(a);if(a.active&&c.length>1){var e=d==c.length-1?d-1:d+1;b.select(c[e])}c.splice(d,1)}}]).directive("tabset",function(){return{restrict:"EA",transclude:!0,replace:!0,scope:{openOnLoad:"=?"},controller:"TabsetController",templateUrl:"template/tabs/tabset.html",link:function(a,b,c){a.vertical=angular.isDefined(c.vertical)?a.$parent.$eval(c.vertical):!1,a.justified=angular.isDefined(c.justified)?a.$parent.$eval(c.justified):!1,a.type=angular.isDefined(c.type)?a.$parent.$eval(c.type):"tabs"}}}).directive("tab",["$parse",function(a){return{require:"^tabset",restrict:"EA",replace:!0,templateUrl:"template/tabs/tab.html",transclude:!0,scope:{heading:"@",onSelect:"&select",onDeselect:"&deselect"},controller:function(){},compile:function(b,c,d){return function(b,c,e,f){var g,h;e.active?(g=a(e.active),h=g.assign,b.$parent.$watch(g,function(a,c){a!==c&&(b.active=!!a)}),b.active=g(b.$parent)):h=g=angular.noop,b.$watch("active",function(a){angular.isFunction(h)&&(h(b.$parent,a),a?(f.select(b),b.onSelect()):b.onDeselect())}),b.disabled=!1,e.disabled&&b.$parent.$watch(a(e.disabled),function(a){b.disabled=!!a}),b.select=function(){b.disabled||(b.active=!0)},f.addTab(b),b.$on("$destroy",function(){f.removeTab(b)}),b.$transcludeFn=d}}}}]).directive("tabHeadingTransclude",[function(){return{restrict:"A",require:"^tab",link:function(a,b,c,d){a.$watch("headingElement",function(a){a&&(b.html(""),b.append(a))})}}}]).directive("tabContentTransclude",function(){function a(a){return a.tagName&&(a.hasAttribute("tab-heading")||a.hasAttribute("data-tab-heading")||"tab-heading"===a.tagName.toLowerCase()||"data-tab-heading"===a.tagName.toLowerCase())}return{restrict:"A",require:"^tabset",link:function(b,c,d){var e=b.$eval(d.tabContentTransclude);e.$transcludeFn(e.$parent,function(b){angular.forEach(b,function(b){a(b)?e.headingElement=b:c.append(b)})})}}}),angular.module("mm.foundation.topbar",["mm.foundation.mediaQueries"]).factory("closest",[function(){return function(a,b){for(var c=function(a,b){for(var c=(a.parentNode||a.document).querySelectorAll(b),d=-1;c[++d]&&c[d]!=a;);return!!c[d]},d=a[0];d;){if(c(d,b))return angular.element(d);d=d.parentElement}return!1}}]).directive("topBar",["$timeout","$compile","$window","$document","mediaQueries",function(a,b,c,d,e){return{scope:{stickyClass:"@",backText:"@",stickyOn:"=",customBackText:"=",isHover:"=",mobileShowParentLink:"=",scrolltop:"="},restrict:"EA",replace:!0,templateUrl:"template/topbar/top-bar.html",transclude:!0,controller:["$window","$scope","closest",function(b,c,f){c.settings={},c.settings.stickyClass=c.stickyClass||"sticky",c.settings.backText=c.backText||"Back",
+c.settings.stickyOn=c.stickyOn||"all",c.settings.customBackText=void 0===c.customBackText?!0:c.customBackText,c.settings.isHover=void 0===c.isHover?!0:c.isHover,c.settings.mobileShowParentLink=void 0===c.mobileShowParentLink?!0:c.mobileShowParentLink,c.settings.scrolltop=void 0===c.scrolltop?!0:c.scrolltop,this.settings=c.settings,c.index=0;var g=function(a){var b=a.offsetHeight,c=a.currentStyle||getComputedStyle(a);return b+=parseInt(c.marginTop,10)+parseInt(c.marginBottom,10)},h=[];this.addSection=function(a){h.push(a)},this.removeSection=function(a){var b=h.indexOf(a);b>-1&&h.splice(b,1)};var i=/rtl/i.test(d.find("html").attr("dir"))?"right":"left";c.$watch("index",function(a){for(var b=0;bb&&!h.hasClass("fixed")?(h.addClass("fixed"),i.css("padding-top",a.originalHeight+"px")):c.pageYOffset<=b&&h.hasClass("fixed")&&(h.removeClass("fixed"),i.css("padding-top",""))}},m=function(){var b=e.topbarBreakpoint();if(j!==b){j=e.topbarBreakpoint(),g.removeClass("expanded"),g.parent().removeClass("expanded"),a.height="";var c=angular.element(g[0].querySelectorAll("section"));angular.forEach(c,function(a){angular.element(a.querySelectorAll("li.moved")).removeClass("moved")}),a.$apply()}},n=function(){l(),a.$apply()};if(a.toggle=function(b){if(!e.topbarBreakpoint())return!1;var d=void 0===b?!g.hasClass("expanded"):b;d?g.addClass("expanded"):g.removeClass("expanded"),a.settings.scrolltop?!d&&g.hasClass("fixed")?(g.parent().addClass("fixed"),g.removeClass("fixed"),i.css("padding-top",a.originalHeight+"px")):d&&g.parent().hasClass("fixed")&&(g.parent().removeClass("fixed"),g.addClass("fixed"),i.css("padding-top",""),c.scrollTo(0,0)):(k()&&g.parent().addClass("fixed"),g.parent().hasClass("fixed")&&(d?(g.addClass("fixed"),g.parent().addClass("expanded"),i.css("padding-top",a.originalHeight+"px")):(g.removeClass("fixed"),g.parent().removeClass("expanded"),l())))},h.hasClass("fixed")||k()){a.stickyTopbar=!0,a.height=h[0].offsetHeight;var o=h[0].getBoundingClientRect().top+c.pageYOffset}else a.height=g[0].offsetHeight;a.originalHeight=a.height,a.$watch("height",function(a){a?g.css("height",a+"px"):g.css("height","")}),angular.element(c).bind("resize",m),angular.element(c).bind("scroll",n),a.$on("$destroy",function(){angular.element(c).unbind("resize",m),angular.element(c).unbind("scroll",n)}),h.hasClass("fixed")&&i.css("padding-top",a.originalHeight+"px")}}}]).directive("toggleTopBar",["closest",function(a){return{scope:{},require:"^topBar",restrict:"A",replace:!0,templateUrl:"template/topbar/toggle-top-bar.html",transclude:!0,link:function(b,c,d,e){c.bind("click",function(b){var c=a(angular.element(b.currentTarget),"li");c.hasClass("back")||c.hasClass("has-dropdown")||e.toggle()}),b.$on("$destroy",function(){c.unbind("click")})}}}]).directive("topBarSection",["$compile","closest",function(a,b){return{scope:{},require:"^topBar",restrict:"EA",replace:!0,templateUrl:"template/topbar/top-bar-section.html",transclude:!0,link:function(a,c,d,e){var f=c;a.reset=function(){angular.element(f[0].querySelectorAll("li.moved")).removeClass("moved")},a.move=function(a,b){"left"===a?f.css({left:-100*b+"%"}):f.css({right:-100*b+"%"})},e.addSection(a),a.$on("$destroy",function(){e.removeSection(a)});var g=f[0].querySelectorAll("li>a");angular.forEach(g,function(c){var d=angular.element(c),f=b(d,"li");f.hasClass("has-dropdown")||f.hasClass("back")||f.hasClass("title")||(d.bind("click",function(){e.toggle(!1)}),a.$on("$destroy",function(){d.bind("click")}))})}}}]).directive("hasDropdown",["mediaQueries",function(a){return{scope:{},require:"^topBar",restrict:"A",templateUrl:"template/topbar/has-dropdown.html",replace:!0,transclude:!0,link:function(b,c,d,e){b.triggerLink=c.children("a")[0];var f=angular.element(b.triggerLink);f.bind("click",function(a){e.forward(a)}),b.$on("$destroy",function(){f.unbind("click")}),c.bind("mouseenter",function(){e.settings.isHover&&!a.topbarBreakpoint()&&c.addClass("not-click")}),c.bind("click",function(b){e.settings.isHover||a.topbarBreakpoint()||c.toggleClass("not-click")}),c.bind("mouseleave",function(){c.removeClass("not-click")}),b.$on("$destroy",function(){c.unbind("click"),c.unbind("mouseenter"),c.unbind("mouseleave")})},controller:["$window","$scope",function(a,b){this.triggerLink=b.triggerLink}]}}]).directive("topBarDropdown",["$compile",function(a){return{scope:{},require:["^topBar","^hasDropdown"],restrict:"A",replace:!0,templateUrl:"template/topbar/top-bar-dropdown.html",transclude:!0,link:function(b,c,d,e){var f,g=e[0],h=e[1],i=angular.element(h.triggerLink),j=i.attr("href");b.linkText=i.text(),b.back=function(a){g.back(a)},g.settings.customBackText?b.backText=g.settings.backText:b.backText="« "+i.html(),f=g.settings.mobileShowParentLink&&j&&j.length>1?angular.element('{{linkText}} '):angular.element(' '),a(f)(b),c.prepend(f)}}}]),angular.module("mm.foundation.tour",["mm.foundation.position","mm.foundation.tooltip"]).service("$tour",["$window",function(a){function b(){try{return parseInt(a.localStorage.getItem("mm.tour.step"),10)}catch(b){if("SecurityError"!==b.name)throw b}}function c(){try{a.localStorage.setItem("mm.tour.step",e)}catch(b){if("SecurityError"!==b.name)throw b}}function d(a){e=a,c()}var e=b(),f={};this.add=function(a,b){f[a]=b},this.has=function(a){return!!f[a]},this.isActive=function(){return e>0},this.current=function(a){return a?void d(e):e},this.start=function(){d(1)},this.next=function(){d(e+1)},this.end=function(){d(0)}}]).directive("stepTextPopup",["$tour",function(a){return{restrict:"EA",replace:!0,scope:{title:"@",content:"@",placement:"@",animation:"&",isOpen:"&"},templateUrl:"template/tour/tour.html",link:function(b,c){b.isLastStep=function(){return!a.has(a.current()+1)},b.endTour=function(){c.remove(),a.end()},b.nextStep=function(){c.remove(),a.next()},b.$on("$locationChangeSuccess",b.endTour)}}}]).directive("stepText",["$position","$tooltip","$tour","$window",function(a,b,c,d){function e(a){var b=a[0].getBoundingClientRect();return b.top>=0&&b.left>=0&&b.bottom<=d.innerHeight-80&&b.right<=d.innerWidth}function f(b,f,g){var h=parseInt(g.stepIndex,10);if(c.isActive()&&h&&(c.add(h,g),h===c.current())){if(!e(f)){var i=a.offset(f);d.scrollTo(0,i.top-d.innerHeight/2)}return!0}return!1}return b("stepText","step",f)}]),angular.module("mm.foundation.typeahead",["mm.foundation.position","mm.foundation.bindHtml"]).factory("typeaheadParser",["$parse",function(a){var b=/^\s*(.*?)(?:\s+as\s+(.*?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+(.*)$/;return{parse:function(c){var d=c.match(b);if(!d)throw new Error("Expected typeahead specification in form of '_modelValue_ (as _label_)? for _item_ in _collection_' but got '"+c+"'.");return{itemName:d[3],source:a(d[4]),viewMapper:a(d[2]||d[1]),modelMapper:a(d[1])}}}}]).directive("typeahead",["$compile","$parse","$q","$timeout","$document","$position","typeaheadParser",function(a,b,c,d,e,f,g){var h=[9,13,27,38,40];return{require:"ngModel",link:function(i,j,k,l){var m,n=i.$eval(k.typeaheadMinLength)||1,o=i.$eval(k.typeaheadWaitMs)||0,p=i.$eval(k.typeaheadEditable)!==!1,q=b(k.typeaheadLoading).assign||angular.noop,r=b(k.typeaheadOnSelect),s=k.typeaheadInputFormatter?b(k.typeaheadInputFormatter):void 0,t=k.typeaheadAppendToBody?b(k.typeaheadAppendToBody):!1,u=b(k.ngModel).assign,v=g.parse(k.typeahead),w=angular.element("
");w.attr({matches:"matches",active:"activeIdx",select:"select(activeIdx)",query:"query",position:"position"}),angular.isDefined(k.typeaheadTemplateUrl)&&w.attr("template-url",k.typeaheadTemplateUrl);var x=i.$new();i.$on("$destroy",function(){x.$destroy()});var y=function(){x.matches=[],x.activeIdx=-1},z=function(a){var b={$viewValue:a};q(i,!0),c.when(v.source(i,b)).then(function(c){if(a===l.$viewValue&&m)if(c.length>0){x.activeIdx=0,x.matches.length=0;for(var d=0;d=n?o>0?(A&&d.cancel(A),A=d(function(){z(a)},o)):z(a):(q(i,!1),y()),p?a:a?void l.$setValidity("editable",!1):(l.$setValidity("editable",!0),a)}),l.$formatters.push(function(a){var b,c,d={};return s?(d.$model=a,s(i,d)):(d[v.itemName]=a,b=v.viewMapper(i,d),d[v.itemName]=void 0,c=v.viewMapper(i,d),b!==c?b:a)}),x.select=function(a){var b,c,d={};d[v.itemName]=c=x.matches[a].model,b=v.modelMapper(i,d),u(i,b),l.$setValidity("editable",!0),r(i,{$item:c,$model:b,$label:v.viewMapper(i,d)}),y(),j[0].focus()},j.bind("keydown",function(a){0!==x.matches.length&&-1!==h.indexOf(a.which)&&(a.preventDefault(),40===a.which?(x.activeIdx=(x.activeIdx+1)%x.matches.length,x.$digest()):38===a.which?(x.activeIdx=(x.activeIdx?x.activeIdx:x.matches.length)-1,x.$digest()):13===a.which||9===a.which?x.$apply(function(){x.select(x.activeIdx)}):27===a.which&&(a.stopPropagation(),y(),x.$digest()))}),j.bind("blur",function(a){m=!1}),j.bind("focus",function(a){m=!0});var B=function(a){j[0]!==a.target&&(y(),x.$digest())};e.bind("click",B),i.$on("$destroy",function(){e.unbind("click",B)});var C=a(w)(x);t?e.find("body").append(C):j.after(C)}}}]).directive("typeaheadPopup",function(){return{restrict:"EA",scope:{matches:"=",query:"=",active:"=",position:"=",select:"&"},replace:!0,templateUrl:"template/typeahead/typeahead-popup.html",link:function(a,b,c){a.templateUrl=c.templateUrl,a.isOpen=function(){return a.matches.length>0},a.isActive=function(b){return a.active==b},a.selectActive=function(b){a.active=b},a.selectMatch=function(b){a.select({activeIdx:b})}}}}).directive("typeaheadMatch",["$http","$templateCache","$compile","$parse",function(a,b,c,d){return{restrict:"EA",scope:{index:"=",match:"=",query:"="},link:function(e,f,g){var h=d(g.templateUrl)(e.$parent)||"template/typeahead/typeahead-match.html";a.get(h,{cache:b}).success(function(a){f.replaceWith(c(a.trim())(e))})}}}]).filter("typeaheadHighlight",function(){function a(a){return a.replace(/([.?*+^$[\]\\(){}|-])/g,"\\$1")}return function(b,c){return c?b.replace(new RegExp(a(c),"gi"),"$& "):b}}),angular.module("template/accordion/accordion-group.html",[]).run(["$templateCache",function(a){a.put("template/accordion/accordion-group.html",'\n {{heading}} \n
\n \n')}]),angular.module("template/accordion/accordion.html",[]).run(["$templateCache",function(a){a.put("template/accordion/accordion.html",' \n')}]),angular.module("template/alert/alert.html",[]).run(["$templateCache",function(a){a.put("template/alert/alert.html","\n")}]),angular.module("template/modal/backdrop.html",[]).run(["$templateCache",function(a){a.put("template/modal/backdrop.html",'
\n')}]),angular.module("template/modal/window.html",[]).run(["$templateCache",function(a){a.put("template/modal/window.html",'\n')}]),angular.module("template/pagination/pager.html",[]).run(["$templateCache",function(a){a.put("template/pagination/pager.html",'\n')}]),angular.module("template/pagination/pagination.html",[]).run(["$templateCache",function(a){a.put("template/pagination/pagination.html",'\n')}]),angular.module("template/tooltip/tooltip-html-unsafe-popup.html",[]).run(["$templateCache",function(a){a.put("template/tooltip/tooltip-html-unsafe-popup.html",'\n \n \n \n')}]),angular.module("template/tooltip/tooltip-popup.html",[]).run(["$templateCache",function(a){a.put("template/tooltip/tooltip-popup.html",'\n \n \n \n')}]),angular.module("template/popover/popover.html",[]).run(["$templateCache",function(a){a.put("template/popover/popover.html",'\n')}]),angular.module("template/progressbar/bar.html",[]).run(["$templateCache",function(a){a.put("template/progressbar/bar.html",' \n')}]),angular.module("template/progressbar/progress.html",[]).run(["$templateCache",function(a){a.put("template/progressbar/progress.html",'
\n')}]),angular.module("template/progressbar/progressbar.html",[]).run(["$templateCache",function(a){a.put("template/progressbar/progressbar.html",'\n \n
\n')}]),angular.module("template/rating/rating.html",[]).run(["$templateCache",function(a){a.put("template/rating/rating.html",'\n \n \n')}]),angular.module("template/tabs/tab.html",[]).run(["$templateCache",function(a){a.put("template/tabs/tab.html",'\n {{heading}} \n \n')}]),angular.module("template/tabs/tabset.html",[]).run(["$templateCache",function(a){a.put("template/tabs/tabset.html",'\n')}]),angular.module("template/tabs/tabset.html.BACKUP.5992.html",[]).run(["$templateCache",function(a){a.put("template/tabs/tabset.html.BACKUP.5992.html",'\n')}]),angular.module("template/tabs/tabset.html.BACKUP.7584.html",[]).run(["$templateCache",function(a){a.put("template/tabs/tabset.html.BACKUP.7584.html",'\n')}]),angular.module("template/tabs/tabset.html.BACKUP.9724.html",[]).run(["$templateCache",function(a){a.put("template/tabs/tabset.html.BACKUP.9724.html",'\n')}]),angular.module("template/tabs/tabset.html.BACKUP.9820.html",[]).run(["$templateCache",function(a){a.put("template/tabs/tabset.html.BACKUP.9820.html",'\n')}]),angular.module("template/tabs/tabset.html.BASE.5992.html",[]).run(["$templateCache",function(a){a.put("template/tabs/tabset.html.BASE.5992.html",'\n')}]),angular.module("template/tabs/tabset.html.BASE.7584.html",[]).run(["$templateCache",function(a){a.put("template/tabs/tabset.html.BASE.7584.html",'\n')}]),angular.module("template/tabs/tabset.html.BASE.9724.html",[]).run(["$templateCache",function(a){a.put("template/tabs/tabset.html.BASE.9724.html",'\n')}]),angular.module("template/tabs/tabset.html.BASE.9820.html",[]).run(["$templateCache",function(a){a.put("template/tabs/tabset.html.BASE.9820.html",'\n')}]),angular.module("template/tabs/tabset.html.LOCAL.5992.html",[]).run(["$templateCache",function(a){a.put("template/tabs/tabset.html.LOCAL.5992.html",'\n')}]),angular.module("template/tabs/tabset.html.LOCAL.7584.html",[]).run(["$templateCache",function(a){a.put("template/tabs/tabset.html.LOCAL.7584.html",'\n')}]),angular.module("template/tabs/tabset.html.LOCAL.9724.html",[]).run(["$templateCache",function(a){a.put("template/tabs/tabset.html.LOCAL.9724.html",'\n')}]),angular.module("template/tabs/tabset.html.LOCAL.9820.html",[]).run(["$templateCache",function(a){a.put("template/tabs/tabset.html.LOCAL.9820.html",'\n')}]),angular.module("template/tabs/tabset.html.REMOTE.5992.html",[]).run(["$templateCache",function(a){a.put("template/tabs/tabset.html.REMOTE.5992.html","")}]),angular.module("template/tabs/tabset.html.REMOTE.7584.html",[]).run(["$templateCache",function(a){a.put("template/tabs/tabset.html.REMOTE.7584.html","")}]),angular.module("template/tabs/tabset.html.REMOTE.9724.html",[]).run(["$templateCache",function(a){a.put("template/tabs/tabset.html.REMOTE.9724.html","")}]),angular.module("template/tabs/tabset.html.REMOTE.9820.html",[]).run(["$templateCache",function(a){a.put("template/tabs/tabset.html.REMOTE.9820.html","")}]),angular.module("template/topbar/has-dropdown.html",[]).run(["$templateCache",function(a){a.put("template/topbar/has-dropdown.html",' ')}]),angular.module("template/topbar/toggle-top-bar.html",[]).run(["$templateCache",function(a){a.put("template/topbar/toggle-top-bar.html",'')}]),angular.module("template/topbar/top-bar-dropdown.html",[]).run(["$templateCache",function(a){a.put("template/topbar/top-bar-dropdown.html",'')}]),angular.module("template/topbar/top-bar-section.html",[]).run(["$templateCache",function(a){a.put("template/topbar/top-bar-section.html",'')}]),angular.module("template/topbar/top-bar.html",[]).run(["$templateCache",function(a){a.put("template/topbar/top-bar.html",' ')}]),angular.module("template/tour/tour.html",[]).run(["$templateCache",function(a){a.put("template/tour/tour.html",'\n')}]),angular.module("template/typeahead/typeahead-match.html",[]).run(["$templateCache",function(a){a.put("template/typeahead/typeahead-match.html",' ')}]),angular.module("template/typeahead/typeahead-popup.html",[]).run(["$templateCache",function(a){a.put("template/typeahead/typeahead-popup.html","\n')}]);
\ No newline at end of file
diff --git a/package.json b/package.json
index 94e9f40..11d4e10 100644
--- a/package.json
+++ b/package.json
@@ -1,11 +1,11 @@
{
"author": "Pinecone, LLC",
"name": "angular-mm-foundation",
- "version": "0.9.0-SNAPSHOT",
+ "version": "0.9.2",
"homepage": "http://pineconellc.github.io/angular-foundation/",
"repository": {
"type": "git",
- "url": "git://github.com/pineconellc/angular-foundation.git"
+ "url": "git://github.com/legendarydrew/angular-foundation.git"
},
"devDependencies": {
"karma": "~0.12.0",
diff --git a/src/accordion/accordion.js b/src/accordion/accordion.js
index 3c7990a..c237a32 100644
--- a/src/accordion/accordion.js
+++ b/src/accordion/accordion.js
@@ -61,7 +61,10 @@ angular.module('mm.foundation.accordion', [])
transclude:true, // It transcludes the contents of the directive into the template
replace: true, // The element containing the directive will be replaced with the template
templateUrl:'template/accordion/accordion-group.html',
- scope:{ heading:'@' }, // Create an isolated scope and interpolate the heading attribute onto this scope
+ scope: { // Create an isolated scope and interpolate the heading attribute onto this scope
+ heading: '@',
+ cbOpen: '&toggleOpen'
+ },
controller: function() {
this.setHeading = function(element) {
this.heading = element;
@@ -73,7 +76,7 @@ angular.module('mm.foundation.accordion', [])
accordionCtrl.addGroup(scope);
scope.isOpen = false;
-
+
if ( attrs.isOpen ) {
getIsOpen = $parse(attrs.isOpen);
setIsOpen = getIsOpen.assign;
@@ -86,6 +89,7 @@ angular.module('mm.foundation.accordion', [])
scope.$watch('isOpen', function(value) {
if ( value ) {
accordionCtrl.closeOthers(scope);
+ scope.cbOpen();
}
if ( setIsOpen ) {
setIsOpen(scope.$parent, value);