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 + + +
+ + +
+ + 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('
  • ' + + '
    {{backText}}
  • ' + + '
  • {{linkText}}
  • '); + } else { + $titleLi = angular.element('
  • ' + + '
    {{backText}}
  • '); + } + + $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('
  • {{backText}}
  • {{linkText}}
  • '):angular.element('
  • {{backText}}
  • '),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 + + +
    + + +
    + + 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('
  • ' + + '
    {{backText}}
  • ' + + '
  • {{linkText}}
  • '); + } else { + $titleLi = angular.element('
  • ' + + '
    {{backText}}
  • '); + } + + $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" + + "
    \n" + + "
    \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" + + "
    \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" + + "
    \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" + + "
    \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" + + "
    \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" + + "
    \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" + + "
    \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" + + "
    \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" + + "
    \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" + + "
    \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" + + "
    \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" + + "
    \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" + + "
    \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" + + "
    \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('
    • {{backText}}
    • {{linkText}}
    • '):angular.element('
    • {{backText}}
    • '),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 \n ×\n
      \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
      \n
      \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 \n
      \n

      \n

      \n
      \n
      \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
      \n
      \n
      \n
      \n
      \n
      \n
      \n')}]),angular.module("template/tabs/tabset.html.BACKUP.5992.html",[]).run(["$templateCache",function(a){a.put("template/tabs/tabset.html.BACKUP.5992.html",'
      \n
      \n
      \n
      \n
      \n
      \n
      \n
      \n')}]),angular.module("template/tabs/tabset.html.BACKUP.7584.html",[]).run(["$templateCache",function(a){a.put("template/tabs/tabset.html.BACKUP.7584.html",'
      \n
      \n
      \n
      \n
      \n
      \n
      \n
      \n')}]),angular.module("template/tabs/tabset.html.BACKUP.9724.html",[]).run(["$templateCache",function(a){a.put("template/tabs/tabset.html.BACKUP.9724.html",'
      \n
      \n
      \n
      \n
      \n
      \n
      \n
      \n')}]),angular.module("template/tabs/tabset.html.BACKUP.9820.html",[]).run(["$templateCache",function(a){a.put("template/tabs/tabset.html.BACKUP.9820.html",'
      \n
      \n
      \n
      \n
      \n
      \n
      \n
      \n')}]),angular.module("template/tabs/tabset.html.BASE.5992.html",[]).run(["$templateCache",function(a){a.put("template/tabs/tabset.html.BASE.5992.html",'
      \n
      \n
      \n
      \n
      \n
      \n
      \n
      \n')}]),angular.module("template/tabs/tabset.html.BASE.7584.html",[]).run(["$templateCache",function(a){a.put("template/tabs/tabset.html.BASE.7584.html",'
      \n
      \n
      \n
      \n
      \n
      \n
      \n
      \n')}]),angular.module("template/tabs/tabset.html.BASE.9724.html",[]).run(["$templateCache",function(a){a.put("template/tabs/tabset.html.BASE.9724.html",'
      \n
      \n
      \n
      \n
      \n
      \n
      \n
      \n')}]),angular.module("template/tabs/tabset.html.BASE.9820.html",[]).run(["$templateCache",function(a){a.put("template/tabs/tabset.html.BASE.9820.html",'
      \n
      \n
      \n
      \n
      \n
      \n
      \n
      \n')}]),angular.module("template/tabs/tabset.html.LOCAL.5992.html",[]).run(["$templateCache",function(a){a.put("template/tabs/tabset.html.LOCAL.5992.html",'
      \n
      \n
      \n
      \n
      \n
      \n
      \n
      \n')}]),angular.module("template/tabs/tabset.html.LOCAL.7584.html",[]).run(["$templateCache",function(a){a.put("template/tabs/tabset.html.LOCAL.7584.html",'
      \n
      \n
      \n
      \n
      \n
      \n
      \n
      \n')}]),angular.module("template/tabs/tabset.html.LOCAL.9724.html",[]).run(["$templateCache",function(a){a.put("template/tabs/tabset.html.LOCAL.9724.html",'
      \n
      \n
      \n
      \n
      \n
      \n
      \n
      \n')}]),angular.module("template/tabs/tabset.html.LOCAL.9820.html",[]).run(["$templateCache",function(a){a.put("template/tabs/tabset.html.LOCAL.9820.html",'
      \n
      \n
      \n
      \n
      \n
      \n
      \n
      \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 \n
      \n

      \n

      \n Next\n End\n ×\n
      \n
      \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"+'
      • \n
        \n
      • \n
      \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);