/*! * Angular Material Design * https://github.com/angular/material * @license MIT * v0.5.0 */ (function() { angular.module('ngMaterial', ["ng","ngAnimate","ngAria","material.core","material.decorators","material.animations","material.components.backdrop","material.components.bottomSheet","material.components.button","material.components.card","material.components.checkbox","material.components.content","material.components.dialog","material.components.divider","material.components.icon","material.components.list","material.components.progressCircular","material.components.progressLinear","material.components.radioButton","material.components.sidenav","material.components.slider","material.components.sticky","material.components.subheader","material.components.swipe","material.components.switch","material.components.tabs","material.components.textField","material.components.toast","material.components.toolbar","material.components.tooltip","material.components.whiteframe","material.services.aria","material.services.attrBind","material.services.compiler","material.services.interimElement","material.services.media","material.services.registry","material.services.theming"]);})(); (function() { /** * Angular Mds initialization function that validates environment * requirements. */ angular.module('material.core', [] ) .run(function validateEnvironment() { if (typeof Hammer === 'undefined') { throw new Error( 'ngMaterial requires HammerJS to be preloaded.' ); } }); })(); (function() { angular.module('material.core') .constant('$mdConstant', { KEY_CODE: { ENTER: 13, ESCAPE: 27, SPACE: 32, LEFT_ARROW : 37, UP_ARROW : 38, RIGHT_ARROW : 39, DOWN_ARROW : 40 } }); })(); (function() { angular.module('material.core') .factory('$mdUtil', ['$cacheFactory', function($cacheFactory) { var SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g; /* for nextUid() function below */ var uid = ['0','0','0']; var Util; return Util = { now: window.performance ? angular.bind(window.performance, window.performance.now) : Date.now, /** * Checks if the specified element has an ancestor (ancestor being parent, grandparent, etc) * with the given attribute defined. * * Also pass in an optional `limit` (levels of ancestry to scan), default 4. */ ancestorHasAttribute: function ancestorHasAttribute(element, attrName, limit) { limit = limit || 4; var current = element; while (limit-- && current.length) { if (current[0].hasAttribute && current[0].hasAttribute(attrName)) { return true; } current = current.parent(); } return false; }, /** * Checks to see if the element or its parents are disabled. * @param element DOM element to start scanning for `disabled` attribute * @param limit Number of parent levels that should be scanned; defaults to 4 * @returns {*} Boolean */ isParentDisabled: function isParentDisabled(element, limit) { return Util.ancestorHasAttribute(element, 'disabled', limit); }, /** * Checks if two elements have the same parent */ elementIsSibling: function elementIsSibling(element, otherElement) { return element.parent().length && (element.parent()[0] === otherElement.parent()[0]); }, /** * Converts snake_case to camelCase. * @param name Name to normalize */ camelCase: function camelCase(name) { return name .replace(SPECIAL_CHARS_REGEXP, function(_, separator, letter, offset) { return offset ? letter.toUpperCase() : letter; }); }, /** * Selects 'n' words from a string * for use in an HTML attribute */ stringFromTextBody: function stringFromTextBody(textBody, numWords) { var string = textBody.trim(); if(string.split(/\s+/).length > numWords){ string = textBody.split(/\s+/).slice(1, (numWords + 1)).join(" ") + '...'; } return string; }, /** * Publish the iterator facade to easily support iteration and accessors * @see iterator below */ iterator: iterator, /** * @see cacheFactory below */ cacheFactory: cacheFactory, // Returns a function, that, as long as it continues to be invoked, will not // be triggered. The function will be called after it stops being called for // N milliseconds. If `immediate` is passed, trigger the function on the // leading edge, instead of the trailing. debounce: function debounce(func, wait, immediate) { var timeout; return function debounced() { var context = this, args = arguments; clearTimeout(timeout); timeout = setTimeout(function() { timeout = null; if (!immediate) func.apply(context, args); }, wait); if (immediate && !timeout) func.apply(context, args); }; }, // Returns a function that can only be triggered every `delay` milliseconds. // In other words, the function will not be called unless it has been more // than `delay` milliseconds since the last call. throttle: function throttle(func, delay) { var recent; return function throttled() { var context = this; var args = arguments; var now = Util.now(); if (!recent || recent - now > delay) { func.apply(context, args); recent = now; } }; }, /** * Wraps an element with a tag * * @param el element to wrap * @param tag tag to wrap it with * @param [className] optional class to apply to the wrapper * @returns new element * */ wrap: function(el, tag, className) { if(el.hasOwnProperty(0)) { el = el[0]; } var wrapper = document.createElement(tag); wrapper.className += className; wrapper.appendChild(el.parentNode.replaceChild(wrapper, el)); return angular.element(wrapper); }, /** * nextUid, from angular.js. * A consistent way of creating unique IDs in angular. The ID is a sequence of alpha numeric * characters such as '012ABC'. The reason why we are not using simply a number counter is that * the number string gets longer over time, and it can also overflow, where as the nextId * will grow much slower, it is a string, and it will never overflow. * * @returns an unique alpha-numeric string */ nextUid: function() { var index = uid.length; var digit; while(index) { index--; digit = uid[index].charCodeAt(0); if (digit == 57 /*'9'*/) { uid[index] = 'A'; return uid.join(''); } if (digit == 90 /*'Z'*/) { uid[index] = '0'; } else { uid[index] = String.fromCharCode(digit + 1); return uid.join(''); } } uid.unshift('0'); return uid.join(''); }, // Stop watchers and events from firing on a scope without destroying it, // by disconnecting it from its parent and its siblings' linked lists. disconnectScope: function disconnectScope(scope) { if (!scope) return; // we can't destroy the root scope or a scope that has been already destroyed if (scope.$root === scope) return; if (scope.$$destroyed ) return; var parent = scope.$parent; scope.$$disconnected = true; // See Scope.$destroy if (parent.$$childHead === scope) parent.$$childHead = scope.$$nextSibling; if (parent.$$childTail === scope) parent.$$childTail = scope.$$prevSibling; if (scope.$$prevSibling) scope.$$prevSibling.$$nextSibling = scope.$$nextSibling; if (scope.$$nextSibling) scope.$$nextSibling.$$prevSibling = scope.$$prevSibling; scope.$$nextSibling = scope.$$prevSibling = null; }, // Undo the effects of disconnectScope above. reconnectScope: function reconnectScope(scope) { if (!scope) return; // we can't disconnect the root node or scope already disconnected if (scope.$root === scope) return; if (!scope.$$disconnected) return; var child = scope; var parent = child.$parent; child.$$disconnected = false; // See Scope.$new for this logic... child.$$prevSibling = parent.$$childTail; if (parent.$$childHead) { parent.$$childTail.$$nextSibling = child; parent.$$childTail = child; } else { parent.$$childHead = parent.$$childTail = child; } } }; /* * iterator is a list facade to easily support iteration and accessors * * @param items Array list which this iterator will enumerate * @param reloop Boolean enables iterator to consider the list as an endless reloop */ function iterator(items, reloop) { var trueFn = function() { return true; }; reloop = !!reloop; var _items = items || [ ]; // Published API return { items: getItems, count: count, inRange: inRange, contains: contains, indexOf: indexOf, itemAt: itemAt, findBy: findBy, add: add, remove: remove, first: first, last: last, next: next, previous: previous, hasPrevious: hasPrevious, hasNext: hasNext }; /* * Publish copy of the enumerable set * @returns {Array|*} */ function getItems() { return [].concat(_items); } /* * Determine length of the list * @returns {Array.length|*|number} */ function count() { return _items.length; } /* * Is the index specified valid * @param index * @returns {Array.length|*|number|boolean} */ function inRange(index) { return _items.length && ( index > -1 ) && (index < _items.length ); } /* * Can the iterator proceed to the next item in the list; relative to * the specified item. * * @param item * @returns {Array.length|*|number|boolean} */ function hasNext(item) { return item ? inRange(indexOf(item) + 1) : false; } /* * Can the iterator proceed to the previous item in the list; relative to * the specified item. * * @param item * @returns {Array.length|*|number|boolean} */ function hasPrevious(item) { return item ? inRange(indexOf(item) - 1) : false; } /* * Get item at specified index/position * @param index * @returns {*} */ function itemAt(index) { return inRange(index) ? _items[index] : null; } /* * Find all elements matching the key/value pair * otherwise return null * * @param val * @param key * * @return array */ function findBy(key, val) { return _items.filter(function(item) { return item[key] === val; }); } /* * Add item to list * @param item * @param index * @returns {*} */ function add(item, index) { if ( !item ) return -1; if (!angular.isNumber(index)) { index = _items.length; } _items.splice(index, 0, item); return indexOf(item); } /* * Remove item from list... * @param item */ function remove(item) { if ( contains(item) ){ _items.splice(indexOf(item), 1); } } /* * Get the zero-based index of the target item * @param item * @returns {*} */ function indexOf(item) { return _items.indexOf(item); } /* * Boolean existence check * @param item * @returns {boolean} */ function contains(item) { return item && (indexOf(item) > -1); } /* * Find the next item. If reloop is true and at the end of the list, it will * go back to the first item. If given ,the `validate` callback will be used * determine whether the next item is valid. If not valid, it will try to find the * next item again. * @param item * @param {optional} validate * @returns {*} */ function next(item, validate) { validate = validate || trueFn; if (contains(item)) { var index = indexOf(item) + 1, found = inRange(index) ? _items[ index ] : (reloop ? first() : null); return validate(found) ? found : next(found, validate); } return null; } /* * Find the previous item. If reloop is true and at the beginning of the list, it will * go back to the last item. If given ,the `validate` callback will be used * determine whether the previous item is valid. If not valid, it will try to find the * previous item again. * @param item * @param {optional} validate * @returns {*} */ function previous(item, validate) { validate = validate || trueFn; if (contains(item)) { var index = indexOf(item) - 1, found = inRange(index) ? _items[ index ] : (reloop ? last() : null); return validate(found) ? found : previous(found, validate); } return null; } /* * Return first item in the list * @returns {*} */ function first() { return _items.length ? _items[0] : null; } /* * Return last item in the list... * @returns {*} */ function last() { return _items.length ? _items[_items.length - 1] : null; } } /* * Angular's $cacheFactory doesn't have a keys() method, * so we add one ourself. */ function cacheFactory(id, options) { var cache = $cacheFactory(id, options); var keys = {}; cache._put = cache.put; cache.put = function(k,v) { keys[k] = true; return cache._put(k, v); }; cache._remove = cache.remove; cache.remove = function(k) { delete keys[k]; return cache._remove(k); }; cache.keys = function() { return Object.keys(keys); }; return cache; } }]); /* * Since removing jQuery from the demos, some code that uses `element.focus()` is broken. * * We need to add `element.focus()`, because it's testable unlike `element[0].focus`. * * TODO(ajoslin): This should be added in a better place later. */ angular.element.prototype.focus = angular.element.prototype.focus || function() { if (this.length) { this[0].focus(); } return this; }; angular.element.prototype.blur = angular.element.prototype.blur || function() { if (this.length) { this[0].blur(); } return this; }; })(); (function() { angular.module('material.decorators', []) .config(['$provide', function($provide) { $provide.decorator('$$rAF', ['$delegate', '$rootScope', rAFDecorator]); function rAFDecorator($$rAF, $rootScope) { /** * Use this to debounce events that come in often. * The debounced function will always use the *last* invocation before the * coming frame. * * For example, window resize events that fire many times a second: * If we set to use an raf-debounced callback on window resize, then * our callback will only be fired once per frame, with the last resize * event that happened before that frame. * * @param {function} callback function to debounce */ $$rAF.debounce = function(cb) { var queueArgs, alreadyQueued, queueCb, context; return function debounced() { queueArgs = arguments; context = this; queueCb = cb; if (!alreadyQueued) { alreadyQueued = true; $$rAF(function() { queueCb.apply(context, queueArgs); alreadyQueued = false; }); } }; }; return $$rAF; } }]); })(); (function() { /* * @ngdoc module * @name material.components.animate * @description * * Ink and Popup Effects */ angular.module('material.animations', ['material.core']) .service('$mdEffects', [ '$rootElement', '$$rAF', '$sniffer', '$q', MdEffects ]); /* * @ngdoc service * @name $mdEffects * @module material.components.animate * * @description * The `$mdEffects` service provides a simple API for various * Material Design effects. * * @returns A `$mdEffects` object with the following properties: * - `{function(element,styles,duration)}` `inkBar` - starts ink bar * animation on specified DOM element * - `{function(element,parentElement,clickElement)}` `popIn` - animated show of element overlayed on parent element * - `{function(element,parentElement)}` `popOut` - animated close of popup overlay * */ function MdEffects($rootElement, $$rAF, $sniffer, $q) { var webkit = /webkit/i.test($sniffer.vendorPrefix); function vendorProperty(name) { return webkit ? ('webkit' + name.charAt(0).toUpperCase() + name.substring(1)) : name; } var self; // Publish API for effects... return self = { popIn: popIn, /* Constants */ TRANSITIONEND_EVENT: 'transitionend' + (webkit ? ' webkitTransitionEnd' : ''), ANIMATIONEND_EVENT: 'animationend' + (webkit ? ' webkitAnimationEnd' : ''), TRANSFORM: vendorProperty('transform'), TRANSITION: vendorProperty('transition'), TRANSITION_DURATION: vendorProperty('transitionDuration'), ANIMATION_PLAY_STATE: vendorProperty('animationPlayState'), ANIMATION_DURATION: vendorProperty('animationDuration'), ANIMATION_NAME: vendorProperty('animationName'), ANIMATION_TIMING: vendorProperty('animationTimingFunction'), ANIMATION_DIRECTION: vendorProperty('animationDirection') }; // ********************************************************** // API Methods // ********************************************************** function popIn(element, parentElement, clickElement) { var deferred = $q.defer(); parentElement.append(element); var startPos; if (clickElement) { var clickRect = clickElement[0].getBoundingClientRect(); startPos = translateString( clickRect.left - element[0].offsetWidth, clickRect.top - element[0].offsetHeight, 0 ) + ' scale(0.2)'; } else { startPos = 'translate3d(0,100%,0) scale(0.5)'; } element .css(self.TRANSFORM, startPos) .css('opacity', 0); $$rAF(function() { $$rAF(function() { element .addClass('md-active') .css(self.TRANSFORM, '') .css('opacity', '') .on(self.TRANSITIONEND_EVENT, finished); }); }); function finished(ev) { //Make sure this transitionend didn't bubble up from a child if (ev.target === element[0]) { element.off(self.TRANSITIONEND_EVENT, finished); deferred.resolve(); } } return deferred.promise; } // ********************************************************** // Utility Methods // ********************************************************** function translateString(x, y, z) { return 'translate3d(' + Math.floor(x) + 'px,' + Math.floor(y) + 'px,' + Math.floor(z) + 'px)'; } } })(); (function() { angular.module('material.animations') .directive('inkRipple', [ '$mdInkRipple', InkRippleDirective ]) .factory('$mdInkRipple', [ '$window', '$$rAF', '$mdEffects', '$timeout', '$mdUtil', InkRippleService ]); function InkRippleDirective($mdInkRipple) { return function(scope, element, attr) { if (attr.inkRipple == 'checkbox') { $mdInkRipple.attachCheckboxBehavior(element); } else { $mdInkRipple.attachButtonBehavior(element); } }; } function InkRippleService($window, $$rAF, $mdEffects, $timeout, $mdUtil) { return { attachButtonBehavior: attachButtonBehavior, attachCheckboxBehavior: attachCheckboxBehavior, attach: attach }; function attachButtonBehavior(element) { return attach(element, { mousedown: true, center: false, animationDuration: 350, mousedownPauseTime: 175, animationName: 'inkRippleButton', animationTimingFunction: 'linear' }); } function attachCheckboxBehavior(element) { return attach(element, { mousedown: true, center: true, animationDuration: 300, mousedownPauseTime: 180, animationName: 'inkRippleCheckbox', animationTimingFunction: 'linear' }); } function attach(element, options) { // Parent element with noink attr? Abort. if (element.controller('noink')) return angular.noop; var contentParent = element.controller('mdContent'); options = angular.extend({ mousedown: true, hover: true, focus: true, center: false, animationDuration: 300, mousedownPauseTime: 150, animationName: '', animationTimingFunction: 'linear' }, options || {}); var rippleContainer; var node = element[0]; var hammertime = new Hammer(node); if (options.mousedown) { hammertime.on('hammer.input', onInput); } // Publish self-detach method if desired... return function detach() { hammertime.destroy(); if (rippleContainer) { rippleContainer.remove(); } }; function rippleIsAllowed() { return !element[0].hasAttribute('disabled'); } function createRipple(left, top, positionsAreAbsolute) { var rippleEl = angular.element('