/*! * Angular Material Design * https://github.com/angular/material * @license MIT * v0.0.3 */ (function(){ angular.module('ngMaterial', [ 'ng', 'ngAnimate', 'material.services.attrBind', 'material.services.compiler', 'material.services.registry', 'material.services.throttle', 'material.decorators', 'material.services.aria', "material.components.button","material.components.card","material.components.checkbox","material.components.content","material.components.dialog","material.components.divider","material.components.icon","material.components.linearProgress","material.components.list","material.components.radioButton","material.components.sidenav","material.components.slider","material.components.switch","material.components.tabs","material.components.textField","material.components.toast","material.components.toolbar","material.components.whiteframe"]); /* * 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) { 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 tab * @returns {Array.length|*|number|boolean} */ function hasNext(tab) { return tab ? inRange(indexOf(tab) + 1) : false; } /* * Can the iterator proceed to the previous item in the list; relative to * the specified item. * * @param tab * @returns {Array.length|*|number|boolean} */ function hasPrevious(tab) { return tab ? inRange(indexOf(tab) - 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) { /* * Implement of e6 Array::find() * @param list * @param callback * @returns {*} */ function find(list, callback) { var results = [ ]; angular.forEach(list, function (it, index) { var val = callback.apply(null, [it, index, list]); if (val) { results.push(val); } }); return results.length ? results : null; } // Use iterator callback to matches element key value // NOTE: searches full prototype chain return find(_items, function (el) { return ( el[key] == val ) ? el : null; }); } /* * Add item to list * @param it * @param index * @returns {*} */ function add(it, index) { if ( !it ) return -1; if (!angular.isDefined(index)) { index = _items.length; } _items.splice(index, 0, it); return indexOf(it); } /* * Remove it from list... * @param it */ function remove(it) { if ( contains(it) ){ _items.splice(indexOf(it), 1); } } /* * Get the zero-based index of the target tab * @param it * @returns {*} */ function indexOf(it) { return _items.indexOf(it); } /* * Boolean existence check * @param it * @returns {boolean} */ function contains(it) { return it && (indexOf(it) > -1); } /* * Find the next item * @param tab * @returns {*} */ function next(it, validate) { if (contains(it)) { var index = indexOf(it) + 1, found = inRange(index) ? _items[ index ] : reloop ? first() : null, skip = found && validate && !validate(found); return skip ? next(found, validate) : found; } return null; } /* * Find the previous item * @param tab * @returns {*} */ function previous(it, validate) { if (contains(it)) { var index = indexOf(it) - 1, found = inRange(index) ? _items[ index ] : reloop ? last() : null, skip = found && validate && !validate(found); return skip ? previous(found, validate) : found; } 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; } } var SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g; var Util = { /** * 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 */ isDisabled : function isDisabled(element, limit) { return Util.ancestorHasAttribute( element, 'disabled', limit ); }, /** * 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 8. */ 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 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; }, /** * Spread the arguments as individual parameters to the target function. * @param targetFn * @param scope * @returns {Function} */ spread : function ( targetFn, scope ) { return function() { var params = Array.prototype.slice.call(arguments, 0); targetFn.apply(scope, params); }; }, /** * Publish the iterator facade to easily support iteration and accessors * @see iterator.js */ iterator : iterator, css : { /** * For any positional fields, ensure that a `px` suffix * is provided. * @param target * @returns {*} */ appendSuffix : function (target) { var styles = 'top left right bottom ' + 'x y width height ' + 'border-width border-radius borderWidth borderRadius' + 'margin margin-top margin-bottom margin-left margin-right ' + 'padding padding-left padding-right padding-top padding-bottom'.split(' '); angular.forEach(target, function(val, key) { var isPositional = styles.indexOf(key) > -1; var hasPx = String(val).indexOf('px') > -1; if (isPositional && !hasPx) { target[key] = val + 'px'; } }); return target; } }, // 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() { 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); }; } }; /* * 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; }; var Constant = { ARIA : { ROLE : { BUTTON : 'button', CHECKBOX : 'checkbox', DIALOG : 'dialog', LIST : 'list', LIST_ITEM : 'listitem', RADIO : 'radio', RADIO_GROUP : 'radiogroup', SLIDER : 'slider', TAB_LIST : 'tablist', TAB : 'tab', TAB_PANEL : 'tabpanel' }, PROPERTY : { CHECKED : 'aria-checked', HIDDEN : 'aria-hidden', EXPANDED : 'aria-expanded', LABEL: 'aria-label', SELECTED : 'aria-selected', LABEL_BY : 'aria-labelledby' }, STATE: {} }, KEY_CODE : { ESCAPE : 27, SPACE : 32, LEFT_ARROW : 37, RIGHT_ARROW : 39, ENTER: 13 }, EVENTS : { SCOPE_DESTROY : '$destroy', TABS_CHANGED : '$materialTabsChanged', FOCUS_CHANGED : '$materialFocusChanged', WINDOW_RESIZE : 'resize', KEY_DOWN : 'keydown', CLICK : 'click' } }; /** * Alias shortcuts... */ var EVENT = Constant.EVENTS; var KEY = Constant.KEY_CODE; /** * @ngdoc module * @name material.components.animate * @description * * Ink and Popup Effects */ angular.module('material.animations', [ 'material.services.throttle' ]) .service('$materialEffects', [ '$rootElement', '$$rAF', '$sniffer', '$q', MaterialEffects ]); /** * @ngdoc service * @name $materialEffects * @module material.components.animate * * @description * The `$materialEffects` service provides a simple API for various * Material Design effects. * * @returns A `$materialEffects` 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 MaterialEffects($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('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') /** * noink/nobar/nostretch directive: make any element that has one of * these attributes be given a controller, so that other directives can * `require:` these and see if there is a `no` parent attribute. * * @usage * * * * * * * * * myApp.directive('detectNo', function() { * return { * require: ['^?noink', ^?nobar'], * link: function(scope, element, attr, ctrls) { * var noinkCtrl = ctrls[0]; * var nobarCtrl = ctrls[1]; * if (noInkCtrl) { * alert("the noink flag has been specified on an ancestor!"); * } * if (nobarCtrl) { * alert("the nobar flag has been specified on an ancestor!"); * } * } * }; * }); * */ .directive({ noink: attrNoDirective(), nobar: attrNoDirective(), nostretch: attrNoDirective() }); function attrNoDirective() { return function() { return { controller: angular.noop }; }; } })(); angular.module('material.animations') .directive('inkRipple', [ '$materialInkRipple', InkRippleDirective ]) .factory('$materialInkRipple', [ '$window', '$$rAF', '$materialEffects', '$timeout', InkRippleService ]); function InkRippleDirective($materialInkRipple) { return function(scope, element, attr) { if (attr.inkRipple == 'checkbox') { $materialInkRipple.attachCheckboxBehavior(element); } else { $materialInkRipple.attachButtonBehavior(element); } }; } function InkRippleService($window, $$rAF, $materialEffects, $timeout) { // TODO fix this. doesn't support touch AND click devices (eg chrome pixel) var hasTouch = !!('ontouchend' in document); var POINTERDOWN_EVENT = hasTouch ? 'touchstart' : 'mousedown'; var POINTERUP_EVENT = hasTouch ? 'touchend touchcancel' : 'mouseup mouseleave'; 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) { 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]; if (options.mousedown) { enableMousedown(); } function rippleIsAllowed() { return !element.controller('noink') && !Util.isDisabled(element); } function enableMousedown() { element.on(POINTERDOWN_EVENT, onPointerDown); function onPointerDown(ev) { if (!rippleIsAllowed()) return; var rippleEl = createRippleFromEvent(ev); var ripplePauseTimeout = $timeout(pauseRipple, options.mousedownPauseTime, false); rippleEl.on('$destroy', cancelRipplePause); // Stop listening to pointer down for now, until the user lifts their finger/mouse element.off(POINTERDOWN_EVENT, onPointerDown); element.on(POINTERUP_EVENT, onPointerUp); function onPointerUp() { cancelRipplePause(); rippleEl.css($materialEffects.ANIMATION_PLAY_STATE, 'running'); element.off(POINTERUP_EVENT, onPointerUp); element.on(POINTERDOWN_EVENT, onPointerDown); } function pauseRipple() { rippleEl.css($materialEffects.ANIMATION_PLAY_STATE, 'paused'); } function cancelRipplePause() { $timeout.cancel(ripplePauseTimeout); } } } function createRippleFromEvent(ev) { ev = ev.touches ? ev.touches[0] : ev; return createRipple(ev.pageX, ev.pageY, true); } function createRipple(left, top, positionsAreAbsolute) { var rippleEl = angular.element('
') .css($materialEffects.ANIMATION_DURATION, options.animationDuration + 'ms') .css($materialEffects.ANIMATION_NAME, options.animationName) .css($materialEffects.ANIMATION_TIMING, options.animationTimingFunction) .on($materialEffects.ANIMATIONEND_EVENT, function() { rippleEl.remove(); }); if (!rippleContainer) { rippleContainer = angular.element('
'); element.append(rippleContainer); } rippleContainer.append(rippleEl); var containerWidth = rippleContainer.prop('offsetWidth'); if (options.center) { left = containerWidth / 2; top = rippleContainer.prop('offsetHeight') / 2; } else if (positionsAreAbsolute) { var elementRect = node.getBoundingClientRect(); left -= elementRect.left; top -= elementRect.top; } var css = { 'background-color': $window.getComputedStyle(rippleEl[0]).color || $window.getComputedStyle(node).color, 'border-radius': (containerWidth / 2) + 'px', left: (left - containerWidth / 2) + 'px', width: containerWidth + 'px', top: (top - containerWidth / 2) + 'px', height: containerWidth + 'px' }; css[$materialEffects.ANIMATION_DURATION] = options.fadeoutDuration + 'ms'; rippleEl.css(css); return rippleEl; } } } /** * @ngdoc module * @name material.components.buttons * @description * * Button */ angular.module('material.components.button', [ 'material.animations', 'material.services.aria' ]) .directive('materialButton', [ 'ngHrefDirective', '$materialInkRipple', '$aria', MaterialButtonDirective ]); /** * @ngdoc directive * @name materialButton * @order 0 * * @restrict E * * @description * `` is a button directive with optional ink ripples (default enabled). * * @param {boolean=} noink Flag indicates use of ripple ink effects * @param {boolean=} disabled Flag indicates if the tab is disabled: not selectable with no ink effects * @param {string=} type Optional attribute to specific button types (useful for forms); such as 'submit', etc. * @param {string=} ng-href Optional attribute to support both ARIA and link navigation * @param {string=} href Optional attribute to support both ARIA and link navigation * @param {string=} ariaLabel Publish the button label used by screen-readers for accessibility. Defaults to the radio button's text. * * @usage * * Button *
* * Button (noInk) * *
* * Colored (disabled) * *
*/ function MaterialButtonDirective(ngHrefDirectives, $materialInkRipple, $aria ) { var ngHrefDirective = ngHrefDirectives[0]; return { restrict: 'E', compile: function(element, attr) { var innerElement; var attributesToCopy; // Add an inner anchor if the element has a `href` or `ngHref` attribute, // so this element can be clicked like a normal ``. if (attr.ngHref || attr.href) { innerElement = angular.element(''); attributesToCopy = ['ng-href', 'href', 'rel', 'target']; // Otherwise, just add an inner button element (for form submission etc) } else { innerElement = angular.element('