;(function () { "use strict"; /** * @license * Copyright 2015 Google Inc. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * A component handler interface using the revealing module design pattern. * More details on this design pattern here: * https://github.com/jasonmayes/mdl-component-design-pattern * * @author Jason Mayes. */ /* exported componentHandler */ // Pre-defining the componentHandler interface, for closure documentation and // static verification. var componentHandler = { /** * Searches existing DOM for elements of our component type and upgrades them * if they have not already been upgraded. * * @param {string=} optJsClass the programatic name of the element class we * need to create a new instance of. * @param {string=} optCssClass the name of the CSS class elements of this * type will have. */ upgradeDom: function (optJsClass, optCssClass) { }, /** * Upgrades a specific element rather than all in the DOM. * * @param {!Element} element The element we wish to upgrade. * @param {string=} optJsClass Optional name of the class we want to upgrade * the element to. */ upgradeElement: function (element, optJsClass) { }, /** * Upgrades a specific list of elements rather than all in the DOM. * * @param {!Element|!Array|!NodeList|!HTMLCollection} elements * The elements we wish to upgrade. */ upgradeElements: function (elements) { }, /** * Upgrades all registered components found in the current DOM. This is * automatically called on window load. */ upgradeAllRegistered: function () { }, /** * Allows user to be alerted to any upgrades that are performed for a given * component type * * @param {string} jsClass The class name of the MDL component we wish * to hook into for any upgrades performed. * @param {function(!HTMLElement)} callback The function to call upon an * upgrade. This function should expect 1 parameter - the HTMLElement which * got upgraded. */ registerUpgradedCallback: function (jsClass, callback) { }, /** * Registers a class for future use and attempts to upgrade existing DOM. * * @param {componentHandler.ComponentConfigPublic} config the registration configuration */ register: function (config) { }, /** * Downgrade either a given node, an array of nodes, or a NodeList. * * @param {!Node|!Array|!NodeList} nodes */ downgradeElements: function (nodes) { } }; componentHandler = (function () { 'use strict'; /** @type {!Array} */ var registeredComponents_ = []; /** @type {!Array} */ var createdComponents_ = []; var componentConfigProperty_ = 'mdlComponentConfigInternal_'; /** * Searches registered components for a class we are interested in using. * Optionally replaces a match with passed object if specified. * * @param {string} name The name of a class we want to use. * @param {componentHandler.ComponentConfig=} optReplace Optional object to replace match with. * @return {!Object|boolean} * @private */ function findRegisteredClass_(name, optReplace) { for (var i = 0; i < registeredComponents_.length; i++) { if (registeredComponents_[i].className === name) { if (typeof optReplace !== 'undefined') { registeredComponents_[i] = optReplace; } return registeredComponents_[i]; } } return false; } /** * Returns an array of the classNames of the upgraded classes on the element. * * @param {!Element} element The element to fetch data from. * @return {!Array} * @private */ function getUpgradedListOfElement_(element) { var dataUpgraded = element.getAttribute('data-upgraded'); // Use `['']` as default value to conform the `,name,name...` style. return dataUpgraded === null ? [''] : dataUpgraded.split(','); } /** * Returns true if the given element has already been upgraded for the given * class. * * @param {!Element} element The element we want to check. * @param {string} jsClass The class to check for. * @returns {boolean} * @private */ function isElementUpgraded_(element, jsClass) { var upgradedList = getUpgradedListOfElement_(element); return upgradedList.indexOf(jsClass) !== -1; } /** * Create an event object. * * @param {string} eventType The type name of the event. * @param {boolean} bubbles Whether the event should bubble up the DOM. * @param {boolean} cancelable Whether the event can be canceled. * @returns {!Event} */ function createEvent_(eventType, bubbles, cancelable) { if ('CustomEvent' in window && typeof window.CustomEvent === 'function') { return new CustomEvent(eventType, { bubbles: bubbles, cancelable: cancelable }); } else { var ev = document.createEvent('Events'); ev.initEvent(eventType, bubbles, cancelable); return ev; } } /** * Searches existing DOM for elements of our component type and upgrades them * if they have not already been upgraded. * * @param {string=} optJsClass the programatic name of the element class we * need to create a new instance of. * @param {string=} optCssClass the name of the CSS class elements of this * type will have. */ function upgradeDomInternal(optJsClass, optCssClass) { if (typeof optJsClass === 'undefined' && typeof optCssClass === 'undefined') { for (var i = 0; i < registeredComponents_.length; i++) { upgradeDomInternal(registeredComponents_[i].className, registeredComponents_[i].cssClass); } } else { var jsClass = /** @type {string} */ (optJsClass); if (typeof optCssClass === 'undefined') { var registeredClass = findRegisteredClass_(jsClass); if (registeredClass) { optCssClass = registeredClass.cssClass; } } var elements = document.querySelectorAll('.' + optCssClass); for (var n = 0; n < elements.length; n++) { upgradeElementInternal(elements[n], jsClass); } } } /** * Upgrades a specific element rather than all in the DOM. * * @param {!Element} element The element we wish to upgrade. * @param {string=} optJsClass Optional name of the class we want to upgrade * the element to. */ function upgradeElementInternal(element, optJsClass) { // Verify argument type. if (!(typeof element === 'object' && element instanceof Element)) { throw new Error('Invalid argument provided to upgrade MDL element.'); } // Allow upgrade to be canceled by canceling emitted event. var upgradingEv = createEvent_('mdl-componentupgrading', true, true); element.dispatchEvent(upgradingEv); if (upgradingEv.defaultPrevented) { return; } var upgradedList = getUpgradedListOfElement_(element); var classesToUpgrade = []; // If jsClass is not provided scan the registered components to find the // ones matching the element's CSS classList. if (!optJsClass) { var classList = element.classList; registeredComponents_.forEach(function (component) { // Match CSS & Not to be upgraded & Not upgraded. if (classList.contains(component.cssClass) && classesToUpgrade.indexOf(component) === -1 && !isElementUpgraded_(element, component.className)) { classesToUpgrade.push(component); } }); } else if (!isElementUpgraded_(element, optJsClass)) { classesToUpgrade.push(findRegisteredClass_(optJsClass)); } // Upgrade the element for each classes. for (var i = 0, n = classesToUpgrade.length, registeredClass; i < n; i++) { registeredClass = classesToUpgrade[i]; if (registeredClass) { // Mark element as upgraded. upgradedList.push(registeredClass.className); element.setAttribute('data-upgraded', upgradedList.join(',')); var instance = new registeredClass.classConstructor(element); instance[componentConfigProperty_] = registeredClass; createdComponents_.push(instance); // Call any callbacks the user has registered with this component type. for (var j = 0, m = registeredClass.callbacks.length; j < m; j++) { registeredClass.callbacks[j](element); } if (registeredClass.widget) { // Assign per element instance for control over API element[registeredClass.className] = instance; } } else { throw new Error( 'Unable to find a registered component for the given class.'); } var upgradedEv = createEvent_('mdl-componentupgraded', true, false); element.dispatchEvent(upgradedEv); } } /** * Upgrades a specific list of elements rather than all in the DOM. * * @param {!Element|!Array|!NodeList|!HTMLCollection} elements * The elements we wish to upgrade. */ function upgradeElementsInternal(elements) { if (!Array.isArray(elements)) { if (elements instanceof Element) { elements = [elements]; } else { elements = Array.prototype.slice.call(elements); } } for (var i = 0, n = elements.length, element; i < n; i++) { element = elements[i]; if (element instanceof HTMLElement) { upgradeElementInternal(element); if (element.children.length > 0) { upgradeElementsInternal(element.children); } } } } /** * Registers a class for future use and attempts to upgrade existing DOM. * * @param {componentHandler.ComponentConfigPublic} config */ function registerInternal(config) { // In order to support both Closure-compiled and uncompiled code accessing // this method, we need to allow for both the dot and array syntax for // property access. You'll therefore see the `foo.bar || foo['bar']` // pattern repeated across this method. var widgetMissing = (typeof config.widget === 'undefined' && typeof config['widget'] === 'undefined'); var widget = true; if (!widgetMissing) { widget = config.widget || config['widget']; } var newConfig = /** @type {componentHandler.ComponentConfig} */ ({ classConstructor: config.constructor || config['constructor'], className: config.classAsString || config['classAsString'], cssClass: config.cssClass || config['cssClass'], widget: widget, callbacks: [] }); registeredComponents_.forEach(function (item) { if (item.cssClass === newConfig.cssClass) { throw new Error('The provided cssClass has already been registered: ' + item.cssClass); } if (item.className === newConfig.className) { throw new Error('The provided className has already been registered'); } }); if (config.constructor.prototype .hasOwnProperty(componentConfigProperty_)) { throw new Error( 'MDL component classes must not have ' + componentConfigProperty_ + ' defined as a property.'); } var found = findRegisteredClass_(config.classAsString, newConfig); if (!found) { registeredComponents_.push(newConfig); } } /** * Allows user to be alerted to any upgrades that are performed for a given * component type * * @param {string} jsClass The class name of the MDL component we wish * to hook into for any upgrades performed. * @param {function(!HTMLElement)} callback The function to call upon an * upgrade. This function should expect 1 parameter - the HTMLElement which * got upgraded. */ function registerUpgradedCallbackInternal(jsClass, callback) { var regClass = findRegisteredClass_(jsClass); if (regClass) { regClass.callbacks.push(callback); } } /** * Upgrades all registered components found in the current DOM. This is * automatically called on window load. */ function upgradeAllRegisteredInternal() { for (var n = 0; n < registeredComponents_.length; n++) { upgradeDomInternal(registeredComponents_[n].className); } } /** * Check the component for the downgrade method. * Execute if found. * Remove component from createdComponents list. * * @param {?componentHandler.Component} component */ function deconstructComponentInternal(component) { if (component) { var componentIndex = createdComponents_.indexOf(component); createdComponents_.splice(componentIndex, 1); var upgrades = component.element_.getAttribute('data-upgraded').split(','); var componentPlace = upgrades.indexOf(component[componentConfigProperty_].classAsString); upgrades.splice(componentPlace, 1); component.element_.setAttribute('data-upgraded', upgrades.join(',')); var ev = createEvent_('mdl-componentdowngraded', true, false); component.element_.dispatchEvent(ev); } } /** * Downgrade either a given node, an array of nodes, or a NodeList. * * @param {!Node|!Array|!NodeList} nodes */ function downgradeNodesInternal(nodes) { /** * Auxiliary function to downgrade a single node. * @param {!Node} node the node to be downgraded */ var downgradeNode = function (node) { createdComponents_.filter(function (item) { return item.element_ === node; }).forEach(deconstructComponentInternal); }; if (nodes instanceof Array || nodes instanceof NodeList) { for (var n = 0; n < nodes.length; n++) { downgradeNode(nodes[n]); } } else if (nodes instanceof Node) { downgradeNode(nodes); } else { throw new Error('Invalid argument provided to downgrade MDL nodes.'); } } // Now return the functions that should be made public with their publicly // facing names... return { upgradeDom: upgradeDomInternal, upgradeElement: upgradeElementInternal, upgradeElements: upgradeElementsInternal, upgradeAllRegistered: upgradeAllRegisteredInternal, registerUpgradedCallback: registerUpgradedCallbackInternal, register: registerInternal, downgradeElements: downgradeNodesInternal }; })(); /** * Describes the type of a registered component type managed by * componentHandler. Provided for benefit of the Closure compiler. * * @typedef {{ * constructor: Function, * classAsString: string, * cssClass: string, * widget: (string|boolean|undefined) * }} */ componentHandler.ComponentConfigPublic; // jshint ignore:line /** * Describes the type of a registered component type managed by * componentHandler. Provided for benefit of the Closure compiler. * * @typedef {{ * constructor: !Function, * className: string, * cssClass: string, * widget: (string|boolean), * callbacks: !Array * }} */ componentHandler.ComponentConfig; // jshint ignore:line /** * Created component (i.e., upgraded element) type as managed by * componentHandler. Provided for benefit of the Closure compiler. * * @typedef {{ * element_: !HTMLElement, * className: string, * classAsString: string, * cssClass: string, * widget: string * }} */ componentHandler.Component; // jshint ignore:line // Export all symbols, for the benefit of Closure compiler. // No effect on uncompiled code. componentHandler['upgradeDom'] = componentHandler.upgradeDom; componentHandler['upgradeElement'] = componentHandler.upgradeElement; componentHandler['upgradeElements'] = componentHandler.upgradeElements; componentHandler['upgradeAllRegistered'] = componentHandler.upgradeAllRegistered; componentHandler['registerUpgradedCallback'] = componentHandler.registerUpgradedCallback; componentHandler['register'] = componentHandler.register; componentHandler['downgradeElements'] = componentHandler.downgradeElements; window.componentHandler = componentHandler; window['componentHandler'] = componentHandler; window.addEventListener('load', function () { 'use strict'; /** * Performs a "Cutting the mustard" test. If the browser supports the features * tested, adds a mdl-js class to the element. It then upgrades all MDL * components requiring JavaScript. */ if ('classList' in document.createElement('div') && 'querySelector' in document && 'addEventListener' in window && Array.prototype.forEach) { document.documentElement.classList.add('mdl-js'); componentHandler.upgradeAllRegistered(); } else { /** * Dummy function to avoid JS errors. */ componentHandler.upgradeElement = function () { }; /** * Dummy function to avoid JS errors. */ componentHandler.register = function () { }; } }); // Source: https://github.com/darius/requestAnimationFrame/blob/master/requestAnimationFrame.js // Adapted from https://gist.github.com/paulirish/1579671 which derived from // http://paulirish.com/2011/requestanimationframe-for-smart-animating/ // http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating // requestAnimationFrame polyfill by Erik Möller. // Fixes from Paul Irish, Tino Zijdel, Andrew Mao, Klemen Slavič, Darius Bacon // MIT license if (!Date.now) { /** * Date.now polyfill. * @return {number} the current Date */ Date.now = function () { return new Date().getTime(); }; Date['now'] = Date.now; } var vendors = [ 'webkit', 'moz' ]; for (var i = 0; i < vendors.length && !window.requestAnimationFrame; ++i) { var vp = vendors[i]; window.requestAnimationFrame = window[vp + 'RequestAnimationFrame']; window.cancelAnimationFrame = window[vp + 'CancelAnimationFrame'] || window[vp + 'CancelRequestAnimationFrame']; window['requestAnimationFrame'] = window.requestAnimationFrame; window['cancelAnimationFrame'] = window.cancelAnimationFrame; } if (/iP(ad|hone|od).*OS 6/.test(window.navigator.userAgent) || !window.requestAnimationFrame || !window.cancelAnimationFrame) { var lastTime = 0; /** * requestAnimationFrame polyfill. * @param {!Function} callback the callback function. */ window.requestAnimationFrame = function (callback) { var now = Date.now(); var nextTime = Math.max(lastTime + 16, now); return setTimeout(function () { callback(lastTime = nextTime); }, nextTime - now); }; window.cancelAnimationFrame = clearTimeout; window['requestAnimationFrame'] = window.requestAnimationFrame; window['cancelAnimationFrame'] = window.cancelAnimationFrame; } /** * @license * Copyright 2015 Google Inc. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * Class constructor for Button MDL component. * Implements MDL component design pattern defined at: * https://github.com/jasonmayes/mdl-component-design-pattern * * @param {HTMLElement} element The element that will be upgraded. */ var MaterialButton = function MaterialButton(element) { this.element_ = element; // Initialize instance. this.init(); }; window['MaterialButton'] = MaterialButton; /** * Store constants in one place so they can be updated easily. * * @enum {string | number} * @private */ MaterialButton.prototype.Constant_ = {}; /** * Store strings for class names defined by this component that are used in * JavaScript. This allows us to simply change it in one place should we * decide to modify at a later date. * * @enum {string} * @private */ MaterialButton.prototype.CssClasses_ = { RIPPLE_EFFECT: 'mdl-js-ripple-effect', RIPPLE_CONTAINER: 'mdl-button__ripple-container', RIPPLE: 'mdl-ripple' }; /** * Handle blur of element. * * @param {Event} event The event that fired. * @private */ MaterialButton.prototype.blurHandler_ = function (event) { if (event) { this.element_.blur(); } }; // Public methods. /** * Disable button. * * @public */ MaterialButton.prototype.disable = function () { this.element_.disabled = true; }; MaterialButton.prototype['disable'] = MaterialButton.prototype.disable; /** * Enable button. * * @public */ MaterialButton.prototype.enable = function () { this.element_.disabled = false; }; MaterialButton.prototype['enable'] = MaterialButton.prototype.enable; /** * Initialize element. */ MaterialButton.prototype.init = function () { if (this.element_) { if (this.element_.classList.contains(this.CssClasses_.RIPPLE_EFFECT)) { var rippleContainer = document.createElement('span'); rippleContainer.classList.add(this.CssClasses_.RIPPLE_CONTAINER); this.rippleElement_ = document.createElement('span'); this.rippleElement_.classList.add(this.CssClasses_.RIPPLE); rippleContainer.appendChild(this.rippleElement_); this.boundRippleBlurHandler = this.blurHandler_.bind(this); this.rippleElement_.addEventListener('mouseup', this.boundRippleBlurHandler); this.element_.appendChild(rippleContainer); } this.boundButtonBlurHandler = this.blurHandler_.bind(this); this.element_.addEventListener('mouseup', this.boundButtonBlurHandler); this.element_.addEventListener('mouseleave', this.boundButtonBlurHandler); } }; // The component registers itself. It can assume componentHandler is available // in the global scope. componentHandler.register({ constructor: MaterialButton, classAsString: 'MaterialButton', cssClass: 'mdl-js-button', widget: true }); /** * @license * Copyright 2015 Google Inc. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * Class constructor for Checkbox MDL component. * Implements MDL component design pattern defined at: * https://github.com/jasonmayes/mdl-component-design-pattern * * @constructor * @param {HTMLElement} element The element that will be upgraded. */ var MaterialCheckbox = function MaterialCheckbox(element) { this.element_ = element; // Initialize instance. this.init(); }; window['MaterialCheckbox'] = MaterialCheckbox; /** * Store constants in one place so they can be updated easily. * * @enum {string | number} * @private */ MaterialCheckbox.prototype.Constant_ = {TINY_TIMEOUT: 0.001}; /** * Store strings for class names defined by this component that are used in * JavaScript. This allows us to simply change it in one place should we * decide to modify at a later date. * * @enum {string} * @private */ MaterialCheckbox.prototype.CssClasses_ = { INPUT: 'mdl-checkbox__input', BOX_OUTLINE: 'mdl-checkbox__box-outline', FOCUS_HELPER: 'mdl-checkbox__focus-helper', TICK_OUTLINE: 'mdl-checkbox__tick-outline', RIPPLE_EFFECT: 'mdl-js-ripple-effect', RIPPLE_IGNORE_EVENTS: 'mdl-js-ripple-effect--ignore-events', RIPPLE_CONTAINER: 'mdl-checkbox__ripple-container', RIPPLE_CENTER: 'mdl-ripple--center', RIPPLE: 'mdl-ripple', IS_FOCUSED: 'is-focused', IS_DISABLED: 'is-disabled', IS_CHECKED: 'is-checked', IS_UPGRADED: 'is-upgraded' }; /** * Handle change of state. * * @param {Event} event The event that fired. * @private */ MaterialCheckbox.prototype.onChange_ = function (event) { this.updateClasses_(); }; /** * Handle focus of element. * * @param {Event} event The event that fired. * @private */ MaterialCheckbox.prototype.onFocus_ = function (event) { this.element_.classList.add(this.CssClasses_.IS_FOCUSED); }; /** * Handle lost focus of element. * * @param {Event} event The event that fired. * @private */ MaterialCheckbox.prototype.onBlur_ = function (event) { this.element_.classList.remove(this.CssClasses_.IS_FOCUSED); }; /** * Handle mouseup. * * @param {Event} event The event that fired. * @private */ MaterialCheckbox.prototype.onMouseUp_ = function (event) { this.blur_(); }; /** * Handle class updates. * * @private */ MaterialCheckbox.prototype.updateClasses_ = function () { this.checkDisabled(); this.checkToggleState(); }; /** * Add blur. * * @private */ MaterialCheckbox.prototype.blur_ = function () { // TODO: figure out why there's a focus event being fired after our blur, // so that we can avoid this hack. window.setTimeout(function () { this.inputElement_.blur(); }.bind(this), this.Constant_.TINY_TIMEOUT); }; // Public methods. /** * Check the inputs toggle state and update display. * * @public */ MaterialCheckbox.prototype.checkToggleState = function () { if (this.inputElement_.checked) { this.element_.classList.add(this.CssClasses_.IS_CHECKED); } else { this.element_.classList.remove(this.CssClasses_.IS_CHECKED); } }; MaterialCheckbox.prototype['checkToggleState'] = MaterialCheckbox.prototype.checkToggleState; /** * Check the inputs disabled state and update display. * * @public */ MaterialCheckbox.prototype.checkDisabled = function () { if (this.inputElement_.disabled) { this.element_.classList.add(this.CssClasses_.IS_DISABLED); } else { this.element_.classList.remove(this.CssClasses_.IS_DISABLED); } }; MaterialCheckbox.prototype['checkDisabled'] = MaterialCheckbox.prototype.checkDisabled; /** * Disable checkbox. * * @public */ MaterialCheckbox.prototype.disable = function () { this.inputElement_.disabled = true; this.updateClasses_(); }; MaterialCheckbox.prototype['disable'] = MaterialCheckbox.prototype.disable; /** * Enable checkbox. * * @public */ MaterialCheckbox.prototype.enable = function () { this.inputElement_.disabled = false; this.updateClasses_(); }; MaterialCheckbox.prototype['enable'] = MaterialCheckbox.prototype.enable; /** * Check checkbox. * * @public */ MaterialCheckbox.prototype.check = function () { this.inputElement_.checked = true; this.updateClasses_(); }; MaterialCheckbox.prototype['check'] = MaterialCheckbox.prototype.check; /** * Uncheck checkbox. * * @public */ MaterialCheckbox.prototype.uncheck = function () { this.inputElement_.checked = false; this.updateClasses_(); }; MaterialCheckbox.prototype['uncheck'] = MaterialCheckbox.prototype.uncheck; /** * Initialize element. */ MaterialCheckbox.prototype.init = function () { if (this.element_) { this.inputElement_ = this.element_.querySelector('.' + this.CssClasses_.INPUT); var boxOutline = document.createElement('span'); boxOutline.classList.add(this.CssClasses_.BOX_OUTLINE); var tickContainer = document.createElement('span'); tickContainer.classList.add(this.CssClasses_.FOCUS_HELPER); var tickOutline = document.createElement('span'); tickOutline.classList.add(this.CssClasses_.TICK_OUTLINE); boxOutline.appendChild(tickOutline); this.element_.appendChild(tickContainer); this.element_.appendChild(boxOutline); if (this.element_.classList.contains(this.CssClasses_.RIPPLE_EFFECT)) { this.element_.classList.add(this.CssClasses_.RIPPLE_IGNORE_EVENTS); this.rippleContainerElement_ = document.createElement('span'); this.rippleContainerElement_.classList.add(this.CssClasses_.RIPPLE_CONTAINER); this.rippleContainerElement_.classList.add(this.CssClasses_.RIPPLE_EFFECT); this.rippleContainerElement_.classList.add(this.CssClasses_.RIPPLE_CENTER); this.boundRippleMouseUp = this.onMouseUp_.bind(this); this.rippleContainerElement_.addEventListener('mouseup', this.boundRippleMouseUp); var ripple = document.createElement('span'); ripple.classList.add(this.CssClasses_.RIPPLE); this.rippleContainerElement_.appendChild(ripple); this.element_.appendChild(this.rippleContainerElement_); } this.boundInputOnChange = this.onChange_.bind(this); this.boundInputOnFocus = this.onFocus_.bind(this); this.boundInputOnBlur = this.onBlur_.bind(this); this.boundElementMouseUp = this.onMouseUp_.bind(this); this.inputElement_.addEventListener('change', this.boundInputOnChange); this.inputElement_.addEventListener('focus', this.boundInputOnFocus); this.inputElement_.addEventListener('blur', this.boundInputOnBlur); this.element_.addEventListener('mouseup', this.boundElementMouseUp); this.updateClasses_(); this.element_.classList.add(this.CssClasses_.IS_UPGRADED); } }; // The component registers itself. It can assume componentHandler is available // in the global scope. componentHandler.register({ constructor: MaterialCheckbox, classAsString: 'MaterialCheckbox', cssClass: 'mdl-js-checkbox', widget: true }); /** * @license * Copyright 2015 Google Inc. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * Class constructor for icon toggle MDL component. * Implements MDL component design pattern defined at: * https://github.com/jasonmayes/mdl-component-design-pattern * * @constructor * @param {HTMLElement} element The element that will be upgraded. */ var MaterialIconToggle = function MaterialIconToggle(element) { this.element_ = element; // Initialize instance. this.init(); }; window['MaterialIconToggle'] = MaterialIconToggle; /** * Store constants in one place so they can be updated easily. * * @enum {string | number} * @private */ MaterialIconToggle.prototype.Constant_ = {TINY_TIMEOUT: 0.001}; /** * Store strings for class names defined by this component that are used in * JavaScript. This allows us to simply change it in one place should we * decide to modify at a later date. * * @enum {string} * @private */ MaterialIconToggle.prototype.CssClasses_ = { INPUT: 'mdl-icon-toggle__input', JS_RIPPLE_EFFECT: 'mdl-js-ripple-effect', RIPPLE_IGNORE_EVENTS: 'mdl-js-ripple-effect--ignore-events', RIPPLE_CONTAINER: 'mdl-icon-toggle__ripple-container', RIPPLE_CENTER: 'mdl-ripple--center', RIPPLE: 'mdl-ripple', IS_FOCUSED: 'is-focused', IS_DISABLED: 'is-disabled', IS_CHECKED: 'is-checked' }; /** * Handle change of state. * * @param {Event} event The event that fired. * @private */ MaterialIconToggle.prototype.onChange_ = function (event) { this.updateClasses_(); }; /** * Handle focus of element. * * @param {Event} event The event that fired. * @private */ MaterialIconToggle.prototype.onFocus_ = function (event) { this.element_.classList.add(this.CssClasses_.IS_FOCUSED); }; /** * Handle lost focus of element. * * @param {Event} event The event that fired. * @private */ MaterialIconToggle.prototype.onBlur_ = function (event) { this.element_.classList.remove(this.CssClasses_.IS_FOCUSED); }; /** * Handle mouseup. * * @param {Event} event The event that fired. * @private */ MaterialIconToggle.prototype.onMouseUp_ = function (event) { this.blur_(); }; /** * Handle class updates. * * @private */ MaterialIconToggle.prototype.updateClasses_ = function () { this.checkDisabled(); this.checkToggleState(); }; /** * Add blur. * * @private */ MaterialIconToggle.prototype.blur_ = function () { // TODO: figure out why there's a focus event being fired after our blur, // so that we can avoid this hack. window.setTimeout(function () { this.inputElement_.blur(); }.bind(this), this.Constant_.TINY_TIMEOUT); }; // Public methods. /** * Check the inputs toggle state and update display. * * @public */ MaterialIconToggle.prototype.checkToggleState = function () { if (this.inputElement_.checked) { this.element_.classList.add(this.CssClasses_.IS_CHECKED); } else { this.element_.classList.remove(this.CssClasses_.IS_CHECKED); } }; MaterialIconToggle.prototype['checkToggleState'] = MaterialIconToggle.prototype.checkToggleState; /** * Check the inputs disabled state and update display. * * @public */ MaterialIconToggle.prototype.checkDisabled = function () { if (this.inputElement_.disabled) { this.element_.classList.add(this.CssClasses_.IS_DISABLED); } else { this.element_.classList.remove(this.CssClasses_.IS_DISABLED); } }; MaterialIconToggle.prototype['checkDisabled'] = MaterialIconToggle.prototype.checkDisabled; /** * Disable icon toggle. * * @public */ MaterialIconToggle.prototype.disable = function () { this.inputElement_.disabled = true; this.updateClasses_(); }; MaterialIconToggle.prototype['disable'] = MaterialIconToggle.prototype.disable; /** * Enable icon toggle. * * @public */ MaterialIconToggle.prototype.enable = function () { this.inputElement_.disabled = false; this.updateClasses_(); }; MaterialIconToggle.prototype['enable'] = MaterialIconToggle.prototype.enable; /** * Check icon toggle. * * @public */ MaterialIconToggle.prototype.check = function () { this.inputElement_.checked = true; this.updateClasses_(); }; MaterialIconToggle.prototype['check'] = MaterialIconToggle.prototype.check; /** * Uncheck icon toggle. * * @public */ MaterialIconToggle.prototype.uncheck = function () { this.inputElement_.checked = false; this.updateClasses_(); }; MaterialIconToggle.prototype['uncheck'] = MaterialIconToggle.prototype.uncheck; /** * Initialize element. */ MaterialIconToggle.prototype.init = function () { if (this.element_) { this.inputElement_ = this.element_.querySelector('.' + this.CssClasses_.INPUT); if (this.element_.classList.contains(this.CssClasses_.JS_RIPPLE_EFFECT)) { this.element_.classList.add(this.CssClasses_.RIPPLE_IGNORE_EVENTS); this.rippleContainerElement_ = document.createElement('span'); this.rippleContainerElement_.classList.add(this.CssClasses_.RIPPLE_CONTAINER); this.rippleContainerElement_.classList.add(this.CssClasses_.JS_RIPPLE_EFFECT); this.rippleContainerElement_.classList.add(this.CssClasses_.RIPPLE_CENTER); this.boundRippleMouseUp = this.onMouseUp_.bind(this); this.rippleContainerElement_.addEventListener('mouseup', this.boundRippleMouseUp); var ripple = document.createElement('span'); ripple.classList.add(this.CssClasses_.RIPPLE); this.rippleContainerElement_.appendChild(ripple); this.element_.appendChild(this.rippleContainerElement_); } this.boundInputOnChange = this.onChange_.bind(this); this.boundInputOnFocus = this.onFocus_.bind(this); this.boundInputOnBlur = this.onBlur_.bind(this); this.boundElementOnMouseUp = this.onMouseUp_.bind(this); this.inputElement_.addEventListener('change', this.boundInputOnChange); this.inputElement_.addEventListener('focus', this.boundInputOnFocus); this.inputElement_.addEventListener('blur', this.boundInputOnBlur); this.element_.addEventListener('mouseup', this.boundElementOnMouseUp); this.updateClasses_(); this.element_.classList.add('is-upgraded'); } }; // The component registers itself. It can assume componentHandler is available // in the global scope. componentHandler.register({ constructor: MaterialIconToggle, classAsString: 'MaterialIconToggle', cssClass: 'mdl-js-icon-toggle', widget: true }); /** * @license * Copyright 2015 Google Inc. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * Class constructor for dropdown MDL component. * Implements MDL component design pattern defined at: * https://github.com/jasonmayes/mdl-component-design-pattern * * @constructor * @param {HTMLElement} element The element that will be upgraded. */ var MaterialMenu = function MaterialMenu(element) { this.element_ = element; // Initialize instance. this.init(); }; window['MaterialMenu'] = MaterialMenu; /** * Store constants in one place so they can be updated easily. * * @enum {string | number} * @private */ MaterialMenu.prototype.Constant_ = { // Total duration of the menu animation. TRANSITION_DURATION_SECONDS: 0.3, // The fraction of the total duration we want to use for menu item animations. TRANSITION_DURATION_FRACTION: 0.8, // How long the menu stays open after choosing an option (so the user can see // the ripple). CLOSE_TIMEOUT: 150 }; /** * Keycodes, for code readability. * * @enum {number} * @private */ MaterialMenu.prototype.Keycodes_ = { ENTER: 13, ESCAPE: 27, SPACE: 32, UP_ARROW: 38, DOWN_ARROW: 40 }; /** * Store strings for class names defined by this component that are used in * JavaScript. This allows us to simply change it in one place should we * decide to modify at a later date. * * @enum {string} * @private */ MaterialMenu.prototype.CssClasses_ = { CONTAINER: 'mdl-menu__container', OUTLINE: 'mdl-menu__outline', ITEM: 'mdl-menu__item', ITEM_RIPPLE_CONTAINER: 'mdl-menu__item-ripple-container', RIPPLE_EFFECT: 'mdl-js-ripple-effect', RIPPLE_IGNORE_EVENTS: 'mdl-js-ripple-effect--ignore-events', RIPPLE: 'mdl-ripple', // Statuses IS_UPGRADED: 'is-upgraded', IS_VISIBLE: 'is-visible', IS_ANIMATING: 'is-animating', // Alignment options BOTTOM_LEFT: 'mdl-menu--bottom-left', // This is the default. BOTTOM_RIGHT: 'mdl-menu--bottom-right', TOP_LEFT: 'mdl-menu--top-left', TOP_RIGHT: 'mdl-menu--top-right', UNALIGNED: 'mdl-menu--unaligned' }; /** * Initialize element. */ MaterialMenu.prototype.init = function () { if (this.element_) { // Create container for the menu. var container = document.createElement('div'); container.classList.add(this.CssClasses_.CONTAINER); this.element_.parentElement.insertBefore(container, this.element_); this.element_.parentElement.removeChild(this.element_); container.appendChild(this.element_); this.container_ = container; // Create outline for the menu (shadow and background). var outline = document.createElement('div'); outline.classList.add(this.CssClasses_.OUTLINE); this.outline_ = outline; container.insertBefore(outline, this.element_); // Find the "for" element and bind events to it. var forElId = this.element_.getAttribute('for') || this.element_.getAttribute('data-mdl-for'); var forEl = null; if (forElId) { forEl = document.getElementById(forElId); if (forEl) { this.forElement_ = forEl; forEl.addEventListener('click', this.handleForClick_.bind(this)); forEl.addEventListener('keydown', this.handleForKeyboardEvent_.bind(this)); } } var items = this.element_.querySelectorAll('.' + this.CssClasses_.ITEM); this.boundItemKeydown_ = this.handleItemKeyboardEvent_.bind(this); this.boundItemClick_ = this.handleItemClick_.bind(this); for (var i = 0; i < items.length; i++) { // Add a listener to each menu item. items[i].addEventListener('click', this.boundItemClick_); // Add a tab index to each menu item. items[i].tabIndex = '-1'; // Add a keyboard listener to each menu item. items[i].addEventListener('keydown', this.boundItemKeydown_); } // Add ripple classes to each item, if the user has enabled ripples. if (this.element_.classList.contains(this.CssClasses_.RIPPLE_EFFECT)) { this.element_.classList.add(this.CssClasses_.RIPPLE_IGNORE_EVENTS); for (i = 0; i < items.length; i++) { var item = items[i]; var rippleContainer = document.createElement('span'); rippleContainer.classList.add(this.CssClasses_.ITEM_RIPPLE_CONTAINER); var ripple = document.createElement('span'); ripple.classList.add(this.CssClasses_.RIPPLE); rippleContainer.appendChild(ripple); item.appendChild(rippleContainer); item.classList.add(this.CssClasses_.RIPPLE_EFFECT); } } // Copy alignment classes to the container, so the outline can use them. if (this.element_.classList.contains(this.CssClasses_.BOTTOM_LEFT)) { this.outline_.classList.add(this.CssClasses_.BOTTOM_LEFT); } if (this.element_.classList.contains(this.CssClasses_.BOTTOM_RIGHT)) { this.outline_.classList.add(this.CssClasses_.BOTTOM_RIGHT); } if (this.element_.classList.contains(this.CssClasses_.TOP_LEFT)) { this.outline_.classList.add(this.CssClasses_.TOP_LEFT); } if (this.element_.classList.contains(this.CssClasses_.TOP_RIGHT)) { this.outline_.classList.add(this.CssClasses_.TOP_RIGHT); } if (this.element_.classList.contains(this.CssClasses_.UNALIGNED)) { this.outline_.classList.add(this.CssClasses_.UNALIGNED); } container.classList.add(this.CssClasses_.IS_UPGRADED); } }; /** * Handles a click on the "for" element, by positioning the menu and then * toggling it. * * @param {Event} evt The event that fired. * @private */ MaterialMenu.prototype.handleForClick_ = function (evt) { if (this.element_ && this.forElement_) { var rect = this.forElement_.getBoundingClientRect(); var forRect = this.forElement_.parentElement.getBoundingClientRect(); if (this.element_.classList.contains(this.CssClasses_.UNALIGNED)) { } else if (this.element_.classList.contains(this.CssClasses_.BOTTOM_RIGHT)) { // Position below the "for" element, aligned to its right. this.container_.style.right = forRect.right - rect.right + 'px'; this.container_.style.top = this.forElement_.offsetTop + this.forElement_.offsetHeight + 'px'; } else if (this.element_.classList.contains(this.CssClasses_.TOP_LEFT)) { // Position above the "for" element, aligned to its left. this.container_.style.left = this.forElement_.offsetLeft + 'px'; this.container_.style.bottom = forRect.bottom - rect.top + 'px'; } else if (this.element_.classList.contains(this.CssClasses_.TOP_RIGHT)) { // Position above the "for" element, aligned to its right. this.container_.style.right = forRect.right - rect.right + 'px'; this.container_.style.bottom = forRect.bottom - rect.top + 'px'; } else { // Default: position below the "for" element, aligned to its left. this.container_.style.left = this.forElement_.offsetLeft + 'px'; this.container_.style.top = this.forElement_.offsetTop + this.forElement_.offsetHeight + 'px'; } } this.toggle(evt); }; /** * Handles a keyboard event on the "for" element. * * @param {Event} evt The event that fired. * @private */ MaterialMenu.prototype.handleForKeyboardEvent_ = function (evt) { if (this.element_ && this.container_ && this.forElement_) { var items = this.element_.querySelectorAll('.' + this.CssClasses_.ITEM + ':not([disabled])'); if (items && items.length > 0 && this.container_.classList.contains(this.CssClasses_.IS_VISIBLE)) { if (evt.keyCode === this.Keycodes_.UP_ARROW) { evt.preventDefault(); items[items.length - 1].focus(); } else if (evt.keyCode === this.Keycodes_.DOWN_ARROW) { evt.preventDefault(); items[0].focus(); } } } }; /** * Handles a keyboard event on an item. * * @param {Event} evt The event that fired. * @private */ MaterialMenu.prototype.handleItemKeyboardEvent_ = function (evt) { if (this.element_ && this.container_) { var items = this.element_.querySelectorAll('.' + this.CssClasses_.ITEM + ':not([disabled])'); if (items && items.length > 0 && this.container_.classList.contains(this.CssClasses_.IS_VISIBLE)) { var currentIndex = Array.prototype.slice.call(items).indexOf(evt.target); if (evt.keyCode === this.Keycodes_.UP_ARROW) { evt.preventDefault(); if (currentIndex > 0) { items[currentIndex - 1].focus(); } else { items[items.length - 1].focus(); } } else if (evt.keyCode === this.Keycodes_.DOWN_ARROW) { evt.preventDefault(); if (items.length > currentIndex + 1) { items[currentIndex + 1].focus(); } else { items[0].focus(); } } else if (evt.keyCode === this.Keycodes_.SPACE || evt.keyCode === this.Keycodes_.ENTER) { evt.preventDefault(); // Send mousedown and mouseup to trigger ripple. var e = new MouseEvent('mousedown'); evt.target.dispatchEvent(e); e = new MouseEvent('mouseup'); evt.target.dispatchEvent(e); // Send click. evt.target.click(); } else if (evt.keyCode === this.Keycodes_.ESCAPE) { evt.preventDefault(); this.hide(); } } } }; /** * Handles a click event on an item. * * @param {Event} evt The event that fired. * @private */ MaterialMenu.prototype.handleItemClick_ = function (evt) { if (evt.target.hasAttribute('disabled')) { evt.stopPropagation(); } else { // Wait some time before closing menu, so the user can see the ripple. this.closing_ = true; window.setTimeout(function (evt) { this.hide(); this.closing_ = false; }.bind(this), this.Constant_.CLOSE_TIMEOUT); } }; /** * Calculates the initial clip (for opening the menu) or final clip (for closing * it), and applies it. This allows us to animate from or to the correct point, * that is, the point it's aligned to in the "for" element. * * @param {number} height Height of the clip rectangle * @param {number} width Width of the clip rectangle * @private */ MaterialMenu.prototype.applyClip_ = function (height, width) { if (this.element_.classList.contains(this.CssClasses_.UNALIGNED)) { // Do not clip. this.element_.style.clip = ''; } else if (this.element_.classList.contains(this.CssClasses_.BOTTOM_RIGHT)) { // Clip to the top right corner of the menu. this.element_.style.clip = 'rect(0 ' + width + 'px ' + '0 ' + width + 'px)'; } else if (this.element_.classList.contains(this.CssClasses_.TOP_LEFT)) { // Clip to the bottom left corner of the menu. this.element_.style.clip = 'rect(' + height + 'px 0 ' + height + 'px 0)'; } else if (this.element_.classList.contains(this.CssClasses_.TOP_RIGHT)) { // Clip to the bottom right corner of the menu. this.element_.style.clip = 'rect(' + height + 'px ' + width + 'px ' + height + 'px ' + width + 'px)'; } else { // Default: do not clip (same as clipping to the top left corner). this.element_.style.clip = ''; } }; /** * Cleanup function to remove animation listeners. * * @param {Event} evt * @private */ MaterialMenu.prototype.removeAnimationEndListener_ = function (evt) { evt.target.classList.remove(MaterialMenu.prototype.CssClasses_.IS_ANIMATING); }; /** * Adds an event listener to clean up after the animation ends. * * @private */ MaterialMenu.prototype.addAnimationEndListener_ = function () { this.element_.addEventListener('transitionend', this.removeAnimationEndListener_); this.element_.addEventListener('webkitTransitionEnd', this.removeAnimationEndListener_); }; /** * Displays the menu. * * @public */ MaterialMenu.prototype.show = function (evt) { if (this.element_ && this.container_ && this.outline_) { // Measure the inner element. var height = this.element_.getBoundingClientRect().height; var width = this.element_.getBoundingClientRect().width; // Apply the inner element's size to the container and outline. this.container_.style.width = width + 'px'; this.container_.style.height = height + 'px'; this.outline_.style.width = width + 'px'; this.outline_.style.height = height + 'px'; var transitionDuration = this.Constant_.TRANSITION_DURATION_SECONDS * this.Constant_.TRANSITION_DURATION_FRACTION; // Calculate transition delays for individual menu items, so that they fade // in one at a time. var items = this.element_.querySelectorAll('.' + this.CssClasses_.ITEM); for (var i = 0; i < items.length; i++) { var itemDelay = null; if (this.element_.classList.contains(this.CssClasses_.TOP_LEFT) || this.element_.classList.contains(this.CssClasses_.TOP_RIGHT)) { itemDelay = (height - items[i].offsetTop - items[i].offsetHeight) / height * transitionDuration + 's'; } else { itemDelay = items[i].offsetTop / height * transitionDuration + 's'; } items[i].style.transitionDelay = itemDelay; } // Apply the initial clip to the text before we start animating. this.applyClip_(height, width); // Wait for the next frame, turn on animation, and apply the final clip. // Also make it visible. This triggers the transitions. window.requestAnimationFrame(function () { this.element_.classList.add(this.CssClasses_.IS_ANIMATING); this.element_.style.clip = 'rect(0 ' + width + 'px ' + height + 'px 0)'; this.container_.classList.add(this.CssClasses_.IS_VISIBLE); }.bind(this)); // Clean up after the animation is complete. this.addAnimationEndListener_(); // Add a click listener to the document, to close the menu. var callback = function (e) { // Check to see if the document is processing the same event that // displayed the menu in the first place. If so, do nothing. // Also check to see if the menu is in the process of closing itself, and // do nothing in that case. // Also check if the clicked element is a menu item // if so, do nothing. if (e !== evt && !this.closing_ && e.target.parentNode !== this.element_) { document.removeEventListener('click', callback); this.hide(); } }.bind(this); document.addEventListener('click', callback); } }; MaterialMenu.prototype['show'] = MaterialMenu.prototype.show; /** * Hides the menu. * * @public */ MaterialMenu.prototype.hide = function () { if (this.element_ && this.container_ && this.outline_) { var items = this.element_.querySelectorAll('.' + this.CssClasses_.ITEM); // Remove all transition delays; menu items fade out concurrently. for (var i = 0; i < items.length; i++) { items[i].style.removeProperty('transition-delay'); } // Measure the inner element. var rect = this.element_.getBoundingClientRect(); var height = rect.height; var width = rect.width; // Turn on animation, and apply the final clip. Also make invisible. // This triggers the transitions. this.element_.classList.add(this.CssClasses_.IS_ANIMATING); this.applyClip_(height, width); this.container_.classList.remove(this.CssClasses_.IS_VISIBLE); // Clean up after the animation is complete. this.addAnimationEndListener_(); } }; MaterialMenu.prototype['hide'] = MaterialMenu.prototype.hide; /** * Displays or hides the menu, depending on current state. * * @public */ MaterialMenu.prototype.toggle = function (evt) { if (this.container_.classList.contains(this.CssClasses_.IS_VISIBLE)) { this.hide(); } else { this.show(evt); } }; MaterialMenu.prototype['toggle'] = MaterialMenu.prototype.toggle; // The component registers itself. It can assume componentHandler is available // in the global scope. componentHandler.register({ constructor: MaterialMenu, classAsString: 'MaterialMenu', cssClass: 'mdl-js-menu', widget: true }); /** * @license * Copyright 2015 Google Inc. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * Class constructor for Progress MDL component. * Implements MDL component design pattern defined at: * https://github.com/jasonmayes/mdl-component-design-pattern * * @constructor * @param {HTMLElement} element The element that will be upgraded. */ var MaterialProgress = function MaterialProgress(element) { this.element_ = element; // Initialize instance. this.init(); }; window['MaterialProgress'] = MaterialProgress; /** * Store constants in one place so they can be updated easily. * * @enum {string | number} * @private */ MaterialProgress.prototype.Constant_ = {}; /** * Store strings for class names defined by this component that are used in * JavaScript. This allows us to simply change it in one place should we * decide to modify at a later date. * * @enum {string} * @private */ MaterialProgress.prototype.CssClasses_ = {INDETERMINATE_CLASS: 'mdl-progress__indeterminate'}; /** * Set the current progress of the progressbar. * * @param {number} p Percentage of the progress (0-100) * @public */ MaterialProgress.prototype.setProgress = function (p) { if (this.element_.classList.contains(this.CssClasses_.INDETERMINATE_CLASS)) { return; } this.progressbar_.style.width = p + '%'; }; MaterialProgress.prototype['setProgress'] = MaterialProgress.prototype.setProgress; /** * Set the current progress of the buffer. * * @param {number} p Percentage of the buffer (0-100) * @public */ MaterialProgress.prototype.setBuffer = function (p) { this.bufferbar_.style.width = p + '%'; this.auxbar_.style.width = 100 - p + '%'; }; MaterialProgress.prototype['setBuffer'] = MaterialProgress.prototype.setBuffer; /** * Initialize element. */ MaterialProgress.prototype.init = function () { if (this.element_) { var el = document.createElement('div'); el.className = 'progressbar bar bar1'; this.element_.appendChild(el); this.progressbar_ = el; el = document.createElement('div'); el.className = 'bufferbar bar bar2'; this.element_.appendChild(el); this.bufferbar_ = el; el = document.createElement('div'); el.className = 'auxbar bar bar3'; this.element_.appendChild(el); this.auxbar_ = el; this.progressbar_.style.width = '0%'; this.bufferbar_.style.width = '100%'; this.auxbar_.style.width = '0%'; this.element_.classList.add('is-upgraded'); } }; // The component registers itself. It can assume componentHandler is available // in the global scope. componentHandler.register({ constructor: MaterialProgress, classAsString: 'MaterialProgress', cssClass: 'mdl-js-progress', widget: true }); /** * @license * Copyright 2015 Google Inc. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * Class constructor for Radio MDL component. * Implements MDL component design pattern defined at: * https://github.com/jasonmayes/mdl-component-design-pattern * * @constructor * @param {HTMLElement} element The element that will be upgraded. */ var MaterialRadio = function MaterialRadio(element) { this.element_ = element; // Initialize instance. this.init(); }; window['MaterialRadio'] = MaterialRadio; /** * Store constants in one place so they can be updated easily. * * @enum {string | number} * @private */ MaterialRadio.prototype.Constant_ = {TINY_TIMEOUT: 0.001}; /** * Store strings for class names defined by this component that are used in * JavaScript. This allows us to simply change it in one place should we * decide to modify at a later date. * * @enum {string} * @private */ MaterialRadio.prototype.CssClasses_ = { IS_FOCUSED: 'is-focused', IS_DISABLED: 'is-disabled', IS_CHECKED: 'is-checked', IS_UPGRADED: 'is-upgraded', JS_RADIO: 'mdl-js-radio', RADIO_BTN: 'mdl-radio__button', RADIO_OUTER_CIRCLE: 'mdl-radio__outer-circle', RADIO_INNER_CIRCLE: 'mdl-radio__inner-circle', RIPPLE_EFFECT: 'mdl-js-ripple-effect', RIPPLE_IGNORE_EVENTS: 'mdl-js-ripple-effect--ignore-events', RIPPLE_CONTAINER: 'mdl-radio__ripple-container', RIPPLE_CENTER: 'mdl-ripple--center', RIPPLE: 'mdl-ripple' }; /** * Handle change of state. * * @param {Event} event The event that fired. * @private */ MaterialRadio.prototype.onChange_ = function (event) { // Since other radio buttons don't get change events, we need to look for // them to update their classes. var radios = document.getElementsByClassName(this.CssClasses_.JS_RADIO); for (var i = 0; i < radios.length; i++) { var button = radios[i].querySelector('.' + this.CssClasses_.RADIO_BTN); // Different name == different group, so no point updating those. if (button.getAttribute('name') === this.btnElement_.getAttribute('name')) { if (typeof radios[i]['MaterialRadio'] !== 'undefined') { radios[i]['MaterialRadio'].updateClasses_(); } } } }; /** * Handle focus. * * @param {Event} event The event that fired. * @private */ MaterialRadio.prototype.onFocus_ = function (event) { this.element_.classList.add(this.CssClasses_.IS_FOCUSED); }; /** * Handle lost focus. * * @param {Event} event The event that fired. * @private */ MaterialRadio.prototype.onBlur_ = function (event) { this.element_.classList.remove(this.CssClasses_.IS_FOCUSED); }; /** * Handle mouseup. * * @param {Event} event The event that fired. * @private */ MaterialRadio.prototype.onMouseup_ = function (event) { this.blur_(); }; /** * Update classes. * * @private */ MaterialRadio.prototype.updateClasses_ = function () { this.checkDisabled(); this.checkToggleState(); }; /** * Add blur. * * @private */ MaterialRadio.prototype.blur_ = function () { // TODO: figure out why there's a focus event being fired after our blur, // so that we can avoid this hack. window.setTimeout(function () { this.btnElement_.blur(); }.bind(this), this.Constant_.TINY_TIMEOUT); }; // Public methods. /** * Check the components disabled state. * * @public */ MaterialRadio.prototype.checkDisabled = function () { if (this.btnElement_.disabled) { this.element_.classList.add(this.CssClasses_.IS_DISABLED); } else { this.element_.classList.remove(this.CssClasses_.IS_DISABLED); } }; MaterialRadio.prototype['checkDisabled'] = MaterialRadio.prototype.checkDisabled; /** * Check the components toggled state. * * @public */ MaterialRadio.prototype.checkToggleState = function () { if (this.btnElement_.checked) { this.element_.classList.add(this.CssClasses_.IS_CHECKED); } else { this.element_.classList.remove(this.CssClasses_.IS_CHECKED); } }; MaterialRadio.prototype['checkToggleState'] = MaterialRadio.prototype.checkToggleState; /** * Disable radio. * * @public */ MaterialRadio.prototype.disable = function () { this.btnElement_.disabled = true; this.updateClasses_(); }; MaterialRadio.prototype['disable'] = MaterialRadio.prototype.disable; /** * Enable radio. * * @public */ MaterialRadio.prototype.enable = function () { this.btnElement_.disabled = false; this.updateClasses_(); }; MaterialRadio.prototype['enable'] = MaterialRadio.prototype.enable; /** * Check radio. * * @public */ MaterialRadio.prototype.check = function () { this.btnElement_.checked = true; this.onChange_(null); }; MaterialRadio.prototype['check'] = MaterialRadio.prototype.check; /** * Uncheck radio. * * @public */ MaterialRadio.prototype.uncheck = function () { this.btnElement_.checked = false; this.onChange_(null); }; MaterialRadio.prototype['uncheck'] = MaterialRadio.prototype.uncheck; /** * Initialize element. */ MaterialRadio.prototype.init = function () { if (this.element_) { this.btnElement_ = this.element_.querySelector('.' + this.CssClasses_.RADIO_BTN); this.boundChangeHandler_ = this.onChange_.bind(this); this.boundFocusHandler_ = this.onChange_.bind(this); this.boundBlurHandler_ = this.onBlur_.bind(this); this.boundMouseUpHandler_ = this.onMouseup_.bind(this); var outerCircle = document.createElement('span'); outerCircle.classList.add(this.CssClasses_.RADIO_OUTER_CIRCLE); var innerCircle = document.createElement('span'); innerCircle.classList.add(this.CssClasses_.RADIO_INNER_CIRCLE); this.element_.appendChild(outerCircle); this.element_.appendChild(innerCircle); var rippleContainer; if (this.element_.classList.contains(this.CssClasses_.RIPPLE_EFFECT)) { this.element_.classList.add(this.CssClasses_.RIPPLE_IGNORE_EVENTS); rippleContainer = document.createElement('span'); rippleContainer.classList.add(this.CssClasses_.RIPPLE_CONTAINER); rippleContainer.classList.add(this.CssClasses_.RIPPLE_EFFECT); rippleContainer.classList.add(this.CssClasses_.RIPPLE_CENTER); rippleContainer.addEventListener('mouseup', this.boundMouseUpHandler_); var ripple = document.createElement('span'); ripple.classList.add(this.CssClasses_.RIPPLE); rippleContainer.appendChild(ripple); this.element_.appendChild(rippleContainer); } this.btnElement_.addEventListener('change', this.boundChangeHandler_); this.btnElement_.addEventListener('focus', this.boundFocusHandler_); this.btnElement_.addEventListener('blur', this.boundBlurHandler_); this.element_.addEventListener('mouseup', this.boundMouseUpHandler_); this.updateClasses_(); this.element_.classList.add(this.CssClasses_.IS_UPGRADED); } }; // The component registers itself. It can assume componentHandler is available // in the global scope. componentHandler.register({ constructor: MaterialRadio, classAsString: 'MaterialRadio', cssClass: 'mdl-js-radio', widget: true }); /** * @license * Copyright 2015 Google Inc. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * Class constructor for Slider MDL component. * Implements MDL component design pattern defined at: * https://github.com/jasonmayes/mdl-component-design-pattern * * @constructor * @param {HTMLElement} element The element that will be upgraded. */ var MaterialSlider = function MaterialSlider(element) { this.element_ = element; // Browser feature detection. this.isIE_ = window.navigator.msPointerEnabled; // Initialize instance. this.init(); }; window['MaterialSlider'] = MaterialSlider; /** * Store constants in one place so they can be updated easily. * * @enum {string | number} * @private */ MaterialSlider.prototype.Constant_ = {}; /** * Store strings for class names defined by this component that are used in * JavaScript. This allows us to simply change it in one place should we * decide to modify at a later date. * * @enum {string} * @private */ MaterialSlider.prototype.CssClasses_ = { IE_CONTAINER: 'mdl-slider__ie-container', SLIDER_CONTAINER: 'mdl-slider__container', BACKGROUND_FLEX: 'mdl-slider__background-flex', BACKGROUND_LOWER: 'mdl-slider__background-lower', BACKGROUND_UPPER: 'mdl-slider__background-upper', IS_LOWEST_VALUE: 'is-lowest-value', IS_UPGRADED: 'is-upgraded' }; /** * Handle input on element. * * @param {Event} event The event that fired. * @private */ MaterialSlider.prototype.onInput_ = function (event) { this.updateValueStyles_(); }; /** * Handle change on element. * * @param {Event} event The event that fired. * @private */ MaterialSlider.prototype.onChange_ = function (event) { this.updateValueStyles_(); }; /** * Handle mouseup on element. * * @param {Event} event The event that fired. * @private */ MaterialSlider.prototype.onMouseUp_ = function (event) { event.target.blur(); }; /** * Handle mousedown on container element. * This handler is purpose is to not require the use to click * exactly on the 2px slider element, as FireFox seems to be very * strict about this. * * @param {Event} event The event that fired. * @private * @suppress {missingProperties} */ MaterialSlider.prototype.onContainerMouseDown_ = function (event) { // If this click is not on the parent element (but rather some child) // ignore. It may still bubble up. if (event.target !== this.element_.parentElement) { return; } // Discard the original event and create a new event that // is on the slider element. event.preventDefault(); var newEvent = new MouseEvent('mousedown', { target: event.target, buttons: event.buttons, clientX: event.clientX, clientY: this.element_.getBoundingClientRect().y }); this.element_.dispatchEvent(newEvent); }; /** * Handle updating of values. * * @private */ MaterialSlider.prototype.updateValueStyles_ = function () { // Calculate and apply percentages to div structure behind slider. var fraction = (this.element_.value - this.element_.min) / (this.element_.max - this.element_.min); if (fraction === 0) { this.element_.classList.add(this.CssClasses_.IS_LOWEST_VALUE); } else { this.element_.classList.remove(this.CssClasses_.IS_LOWEST_VALUE); } if (!this.isIE_) { this.backgroundLower_.style.flex = fraction; this.backgroundLower_.style.webkitFlex = fraction; this.backgroundUpper_.style.flex = 1 - fraction; this.backgroundUpper_.style.webkitFlex = 1 - fraction; } }; // Public methods. /** * Disable slider. * * @public */ MaterialSlider.prototype.disable = function () { this.element_.disabled = true; }; MaterialSlider.prototype['disable'] = MaterialSlider.prototype.disable; /** * Enable slider. * * @public */ MaterialSlider.prototype.enable = function () { this.element_.disabled = false; }; MaterialSlider.prototype['enable'] = MaterialSlider.prototype.enable; /** * Update slider value. * * @param {number} value The value to which to set the control (optional). * @public */ MaterialSlider.prototype.change = function (value) { if (typeof value !== 'undefined') { this.element_.value = value; } this.updateValueStyles_(); }; MaterialSlider.prototype['change'] = MaterialSlider.prototype.change; /** * Initialize element. */ MaterialSlider.prototype.init = function () { if (this.element_) { if (this.isIE_) { // Since we need to specify a very large height in IE due to // implementation limitations, we add a parent here that trims it down to // a reasonable size. var containerIE = document.createElement('div'); containerIE.classList.add(this.CssClasses_.IE_CONTAINER); this.element_.parentElement.insertBefore(containerIE, this.element_); this.element_.parentElement.removeChild(this.element_); containerIE.appendChild(this.element_); } else { // For non-IE browsers, we need a div structure that sits behind the // slider and allows us to style the left and right sides of it with // different colors. var container = document.createElement('div'); container.classList.add(this.CssClasses_.SLIDER_CONTAINER); this.element_.parentElement.insertBefore(container, this.element_); this.element_.parentElement.removeChild(this.element_); container.appendChild(this.element_); var backgroundFlex = document.createElement('div'); backgroundFlex.classList.add(this.CssClasses_.BACKGROUND_FLEX); container.appendChild(backgroundFlex); this.backgroundLower_ = document.createElement('div'); this.backgroundLower_.classList.add(this.CssClasses_.BACKGROUND_LOWER); backgroundFlex.appendChild(this.backgroundLower_); this.backgroundUpper_ = document.createElement('div'); this.backgroundUpper_.classList.add(this.CssClasses_.BACKGROUND_UPPER); backgroundFlex.appendChild(this.backgroundUpper_); } this.boundInputHandler = this.onInput_.bind(this); this.boundChangeHandler = this.onChange_.bind(this); this.boundMouseUpHandler = this.onMouseUp_.bind(this); this.boundContainerMouseDownHandler = this.onContainerMouseDown_.bind(this); this.element_.addEventListener('input', this.boundInputHandler); this.element_.addEventListener('change', this.boundChangeHandler); this.element_.addEventListener('mouseup', this.boundMouseUpHandler); this.element_.parentElement.addEventListener('mousedown', this.boundContainerMouseDownHandler); this.updateValueStyles_(); this.element_.classList.add(this.CssClasses_.IS_UPGRADED); } }; // The component registers itself. It can assume componentHandler is available // in the global scope. componentHandler.register({ constructor: MaterialSlider, classAsString: 'MaterialSlider', cssClass: 'mdl-js-slider', widget: true }); /** * Copyright 2015 Google Inc. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * Class constructor for Snackbar MDL component. * Implements MDL component design pattern defined at: * https://github.com/jasonmayes/mdl-component-design-pattern * * @constructor * @param {HTMLElement} element The element that will be upgraded. */ var MaterialSnackbar = function MaterialSnackbar(element) { this.element_ = element; this.textElement_ = this.element_.querySelector('.' + this.cssClasses_.MESSAGE); this.actionElement_ = this.element_.querySelector('.' + this.cssClasses_.ACTION); if (!this.textElement_) { throw new Error('There must be a message element for a snackbar.'); } if (!this.actionElement_) { throw new Error('There must be an action element for a snackbar.'); } this.active = false; this.actionHandler_ = undefined; this.message_ = undefined; this.actionText_ = undefined; this.queuedNotifications_ = []; this.setActionHidden_(true); }; window['MaterialSnackbar'] = MaterialSnackbar; /** * Store constants in one place so they can be updated easily. * * @enum {string | number} * @private */ MaterialSnackbar.prototype.Constant_ = { // The duration of the snackbar show/hide animation, in ms. ANIMATION_LENGTH: 250 }; /** * Store strings for class names defined by this component that are used in * JavaScript. This allows us to simply change it in one place should we * decide to modify at a later date. * * @enum {string} * @private */ MaterialSnackbar.prototype.cssClasses_ = { SNACKBAR: 'mdl-snackbar', MESSAGE: 'mdl-snackbar__text', ACTION: 'mdl-snackbar__action', ACTIVE: 'mdl-snackbar--active' }; /** * Display the snackbar. * * @private */ MaterialSnackbar.prototype.displaySnackbar_ = function () { this.element_.setAttribute('aria-hidden', 'true'); if (this.actionHandler_) { this.actionElement_.textContent = this.actionText_; this.actionElement_.addEventListener('click', this.actionHandler_); this.setActionHidden_(false); } this.textElement_.textContent = this.message_; this.element_.classList.add(this.cssClasses_.ACTIVE); this.element_.setAttribute('aria-hidden', 'false'); setTimeout(this.cleanup_.bind(this), this.timeout_); }; /** * Show the snackbar. * * @param {Object} data The data for the notification. * @public */ MaterialSnackbar.prototype.showSnackbar = function (data) { if (data === undefined) { throw new Error('Please provide a data object with at least a message to display.'); } if (data['message'] === undefined) { throw new Error('Please provide a message to be displayed.'); } if (data['actionHandler'] && !data['actionText']) { throw new Error('Please provide action text with the handler.'); } if (this.active) { this.queuedNotifications_.push(data); } else { this.active = true; this.message_ = data['message']; if (data['timeout']) { this.timeout_ = data['timeout']; } else { this.timeout_ = 2750; } if (data['actionHandler']) { this.actionHandler_ = data['actionHandler']; } if (data['actionText']) { this.actionText_ = data['actionText']; } this.displaySnackbar_(); } }; MaterialSnackbar.prototype['showSnackbar'] = MaterialSnackbar.prototype.showSnackbar; /** * Check if the queue has items within it. * If it does, display the next entry. * * @private */ MaterialSnackbar.prototype.checkQueue_ = function () { if (this.queuedNotifications_.length > 0) { this.showSnackbar(this.queuedNotifications_.shift()); } }; /** * Cleanup the snackbar event listeners and accessiblity attributes. * * @private */ MaterialSnackbar.prototype.cleanup_ = function () { this.element_.classList.remove(this.cssClasses_.ACTIVE); setTimeout(function () { this.element_.setAttribute('aria-hidden', 'true'); this.textElement_.textContent = ''; if (!Boolean(this.actionElement_.getAttribute('aria-hidden'))) { this.setActionHidden_(true); this.actionElement_.textContent = ''; this.actionElement_.removeEventListener('click', this.actionHandler_); } this.actionHandler_ = undefined; this.message_ = undefined; this.actionText_ = undefined; this.active = false; this.checkQueue_(); }.bind(this), this.Constant_.ANIMATION_LENGTH); }; /** * Set the action handler hidden state. * * @param {boolean} value * @private */ MaterialSnackbar.prototype.setActionHidden_ = function (value) { if (value) { this.actionElement_.setAttribute('aria-hidden', 'true'); } else { this.actionElement_.removeAttribute('aria-hidden'); } }; // The component registers itself. It can assume componentHandler is available // in the global scope. componentHandler.register({ constructor: MaterialSnackbar, classAsString: 'MaterialSnackbar', cssClass: 'mdl-js-snackbar', widget: true }); /** * @license * Copyright 2015 Google Inc. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * Class constructor for Spinner MDL component. * Implements MDL component design pattern defined at: * https://github.com/jasonmayes/mdl-component-design-pattern * * @param {HTMLElement} element The element that will be upgraded. * @constructor */ var MaterialSpinner = function MaterialSpinner(element) { this.element_ = element; // Initialize instance. this.init(); }; window['MaterialSpinner'] = MaterialSpinner; /** * Store constants in one place so they can be updated easily. * * @enum {string | number} * @private */ MaterialSpinner.prototype.Constant_ = {MDL_SPINNER_LAYER_COUNT: 4}; /** * Store strings for class names defined by this component that are used in * JavaScript. This allows us to simply change it in one place should we * decide to modify at a later date. * * @enum {string} * @private */ MaterialSpinner.prototype.CssClasses_ = { MDL_SPINNER_LAYER: 'mdl-spinner__layer', MDL_SPINNER_CIRCLE_CLIPPER: 'mdl-spinner__circle-clipper', MDL_SPINNER_CIRCLE: 'mdl-spinner__circle', MDL_SPINNER_GAP_PATCH: 'mdl-spinner__gap-patch', MDL_SPINNER_LEFT: 'mdl-spinner__left', MDL_SPINNER_RIGHT: 'mdl-spinner__right' }; /** * Auxiliary method to create a spinner layer. * * @param {number} index Index of the layer to be created. * @public */ MaterialSpinner.prototype.createLayer = function (index) { var layer = document.createElement('div'); layer.classList.add(this.CssClasses_.MDL_SPINNER_LAYER); layer.classList.add(this.CssClasses_.MDL_SPINNER_LAYER + '-' + index); var leftClipper = document.createElement('div'); leftClipper.classList.add(this.CssClasses_.MDL_SPINNER_CIRCLE_CLIPPER); leftClipper.classList.add(this.CssClasses_.MDL_SPINNER_LEFT); var gapPatch = document.createElement('div'); gapPatch.classList.add(this.CssClasses_.MDL_SPINNER_GAP_PATCH); var rightClipper = document.createElement('div'); rightClipper.classList.add(this.CssClasses_.MDL_SPINNER_CIRCLE_CLIPPER); rightClipper.classList.add(this.CssClasses_.MDL_SPINNER_RIGHT); var circleOwners = [ leftClipper, gapPatch, rightClipper ]; for (var i = 0; i < circleOwners.length; i++) { var circle = document.createElement('div'); circle.classList.add(this.CssClasses_.MDL_SPINNER_CIRCLE); circleOwners[i].appendChild(circle); } layer.appendChild(leftClipper); layer.appendChild(gapPatch); layer.appendChild(rightClipper); this.element_.appendChild(layer); }; MaterialSpinner.prototype['createLayer'] = MaterialSpinner.prototype.createLayer; /** * Stops the spinner animation. * Public method for users who need to stop the spinner for any reason. * * @public */ MaterialSpinner.prototype.stop = function () { this.element_.classList.remove('is-active'); }; MaterialSpinner.prototype['stop'] = MaterialSpinner.prototype.stop; /** * Starts the spinner animation. * Public method for users who need to manually start the spinner for any reason * (instead of just adding the 'is-active' class to their markup). * * @public */ MaterialSpinner.prototype.start = function () { this.element_.classList.add('is-active'); }; MaterialSpinner.prototype['start'] = MaterialSpinner.prototype.start; /** * Initialize element. */ MaterialSpinner.prototype.init = function () { if (this.element_) { for (var i = 1; i <= this.Constant_.MDL_SPINNER_LAYER_COUNT; i++) { this.createLayer(i); } this.element_.classList.add('is-upgraded'); } }; // The component registers itself. It can assume componentHandler is available // in the global scope. componentHandler.register({ constructor: MaterialSpinner, classAsString: 'MaterialSpinner', cssClass: 'mdl-js-spinner', widget: true }); /** * @license * Copyright 2015 Google Inc. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * Class constructor for Checkbox MDL component. * Implements MDL component design pattern defined at: * https://github.com/jasonmayes/mdl-component-design-pattern * * @constructor * @param {HTMLElement} element The element that will be upgraded. */ var MaterialSwitch = function MaterialSwitch(element) { this.element_ = element; // Initialize instance. this.init(); }; window['MaterialSwitch'] = MaterialSwitch; /** * Store constants in one place so they can be updated easily. * * @enum {string | number} * @private */ MaterialSwitch.prototype.Constant_ = {TINY_TIMEOUT: 0.001}; /** * Store strings for class names defined by this component that are used in * JavaScript. This allows us to simply change it in one place should we * decide to modify at a later date. * * @enum {string} * @private */ MaterialSwitch.prototype.CssClasses_ = { INPUT: 'mdl-switch__input', TRACK: 'mdl-switch__track', THUMB: 'mdl-switch__thumb', FOCUS_HELPER: 'mdl-switch__focus-helper', RIPPLE_EFFECT: 'mdl-js-ripple-effect', RIPPLE_IGNORE_EVENTS: 'mdl-js-ripple-effect--ignore-events', RIPPLE_CONTAINER: 'mdl-switch__ripple-container', RIPPLE_CENTER: 'mdl-ripple--center', RIPPLE: 'mdl-ripple', IS_FOCUSED: 'is-focused', IS_DISABLED: 'is-disabled', IS_CHECKED: 'is-checked' }; /** * Handle change of state. * * @param {Event} event The event that fired. * @private */ MaterialSwitch.prototype.onChange_ = function (event) { this.updateClasses_(); }; /** * Handle focus of element. * * @param {Event} event The event that fired. * @private */ MaterialSwitch.prototype.onFocus_ = function (event) { this.element_.classList.add(this.CssClasses_.IS_FOCUSED); }; /** * Handle lost focus of element. * * @param {Event} event The event that fired. * @private */ MaterialSwitch.prototype.onBlur_ = function (event) { this.element_.classList.remove(this.CssClasses_.IS_FOCUSED); }; /** * Handle mouseup. * * @param {Event} event The event that fired. * @private */ MaterialSwitch.prototype.onMouseUp_ = function (event) { this.blur_(); }; /** * Handle class updates. * * @private */ MaterialSwitch.prototype.updateClasses_ = function () { this.checkDisabled(); this.checkToggleState(); }; /** * Add blur. * * @private */ MaterialSwitch.prototype.blur_ = function () { // TODO: figure out why there's a focus event being fired after our blur, // so that we can avoid this hack. window.setTimeout(function () { this.inputElement_.blur(); }.bind(this), this.Constant_.TINY_TIMEOUT); }; // Public methods. /** * Check the components disabled state. * * @public */ MaterialSwitch.prototype.checkDisabled = function () { if (this.inputElement_.disabled) { this.element_.classList.add(this.CssClasses_.IS_DISABLED); } else { this.element_.classList.remove(this.CssClasses_.IS_DISABLED); } }; MaterialSwitch.prototype['checkDisabled'] = MaterialSwitch.prototype.checkDisabled; /** * Check the components toggled state. * * @public */ MaterialSwitch.prototype.checkToggleState = function () { if (this.inputElement_.checked) { this.element_.classList.add(this.CssClasses_.IS_CHECKED); } else { this.element_.classList.remove(this.CssClasses_.IS_CHECKED); } }; MaterialSwitch.prototype['checkToggleState'] = MaterialSwitch.prototype.checkToggleState; /** * Disable switch. * * @public */ MaterialSwitch.prototype.disable = function () { this.inputElement_.disabled = true; this.updateClasses_(); }; MaterialSwitch.prototype['disable'] = MaterialSwitch.prototype.disable; /** * Enable switch. * * @public */ MaterialSwitch.prototype.enable = function () { this.inputElement_.disabled = false; this.updateClasses_(); }; MaterialSwitch.prototype['enable'] = MaterialSwitch.prototype.enable; /** * Activate switch. * * @public */ MaterialSwitch.prototype.on = function () { this.inputElement_.checked = true; this.updateClasses_(); }; MaterialSwitch.prototype['on'] = MaterialSwitch.prototype.on; /** * Deactivate switch. * * @public */ MaterialSwitch.prototype.off = function () { this.inputElement_.checked = false; this.updateClasses_(); }; MaterialSwitch.prototype['off'] = MaterialSwitch.prototype.off; /** * Initialize element. */ MaterialSwitch.prototype.init = function () { if (this.element_) { this.inputElement_ = this.element_.querySelector('.' + this.CssClasses_.INPUT); var track = document.createElement('div'); track.classList.add(this.CssClasses_.TRACK); var thumb = document.createElement('div'); thumb.classList.add(this.CssClasses_.THUMB); var focusHelper = document.createElement('span'); focusHelper.classList.add(this.CssClasses_.FOCUS_HELPER); thumb.appendChild(focusHelper); this.element_.appendChild(track); this.element_.appendChild(thumb); this.boundMouseUpHandler = this.onMouseUp_.bind(this); if (this.element_.classList.contains(this.CssClasses_.RIPPLE_EFFECT)) { this.element_.classList.add(this.CssClasses_.RIPPLE_IGNORE_EVENTS); this.rippleContainerElement_ = document.createElement('span'); this.rippleContainerElement_.classList.add(this.CssClasses_.RIPPLE_CONTAINER); this.rippleContainerElement_.classList.add(this.CssClasses_.RIPPLE_EFFECT); this.rippleContainerElement_.classList.add(this.CssClasses_.RIPPLE_CENTER); this.rippleContainerElement_.addEventListener('mouseup', this.boundMouseUpHandler); var ripple = document.createElement('span'); ripple.classList.add(this.CssClasses_.RIPPLE); this.rippleContainerElement_.appendChild(ripple); this.element_.appendChild(this.rippleContainerElement_); } this.boundChangeHandler = this.onChange_.bind(this); this.boundFocusHandler = this.onFocus_.bind(this); this.boundBlurHandler = this.onBlur_.bind(this); this.inputElement_.addEventListener('change', this.boundChangeHandler); this.inputElement_.addEventListener('focus', this.boundFocusHandler); this.inputElement_.addEventListener('blur', this.boundBlurHandler); this.element_.addEventListener('mouseup', this.boundMouseUpHandler); this.updateClasses_(); this.element_.classList.add('is-upgraded'); } }; // The component registers itself. It can assume componentHandler is available // in the global scope. componentHandler.register({ constructor: MaterialSwitch, classAsString: 'MaterialSwitch', cssClass: 'mdl-js-switch', widget: true }); /** * @license * Copyright 2015 Google Inc. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * Class constructor for Tabs MDL component. * Implements MDL component design pattern defined at: * https://github.com/jasonmayes/mdl-component-design-pattern * * @constructor * @param {Element} element The element that will be upgraded. */ var MaterialTabs = function MaterialTabs(element) { // Stores the HTML element. this.element_ = element; // Initialize instance. this.init(); }; window['MaterialTabs'] = MaterialTabs; /** * Store constants in one place so they can be updated easily. * * @enum {string} * @private */ MaterialTabs.prototype.Constant_ = {}; /** * Store strings for class names defined by this component that are used in * JavaScript. This allows us to simply change it in one place should we * decide to modify at a later date. * * @enum {string} * @private */ MaterialTabs.prototype.CssClasses_ = { TAB_CLASS: 'mdl-tabs__tab', PANEL_CLASS: 'mdl-tabs__panel', ACTIVE_CLASS: 'is-active', UPGRADED_CLASS: 'is-upgraded', MDL_JS_RIPPLE_EFFECT: 'mdl-js-ripple-effect', MDL_RIPPLE_CONTAINER: 'mdl-tabs__ripple-container', MDL_RIPPLE: 'mdl-ripple', MDL_JS_RIPPLE_EFFECT_IGNORE_EVENTS: 'mdl-js-ripple-effect--ignore-events' }; /** * Handle clicks to a tabs component * * @private */ MaterialTabs.prototype.initTabs_ = function () { if (this.element_.classList.contains(this.CssClasses_.MDL_JS_RIPPLE_EFFECT)) { this.element_.classList.add(this.CssClasses_.MDL_JS_RIPPLE_EFFECT_IGNORE_EVENTS); } // Select element tabs, document panels this.tabs_ = this.element_.querySelectorAll('.' + this.CssClasses_.TAB_CLASS); this.panels_ = this.element_.querySelectorAll('.' + this.CssClasses_.PANEL_CLASS); // Create new tabs for each tab element for (var i = 0; i < this.tabs_.length; i++) { new MaterialTab(this.tabs_[i], this); } this.element_.classList.add(this.CssClasses_.UPGRADED_CLASS); }; /** * Reset tab state, dropping active classes * * @private */ MaterialTabs.prototype.resetTabState_ = function () { for (var k = 0; k < this.tabs_.length; k++) { this.tabs_[k].classList.remove(this.CssClasses_.ACTIVE_CLASS); } }; /** * Reset panel state, droping active classes * * @private */ MaterialTabs.prototype.resetPanelState_ = function () { for (var j = 0; j < this.panels_.length; j++) { this.panels_[j].classList.remove(this.CssClasses_.ACTIVE_CLASS); } }; /** * Initialize element. */ MaterialTabs.prototype.init = function () { if (this.element_) { this.initTabs_(); } }; /** * Constructor for an individual tab. * * @constructor * @param {Element} tab The HTML element for the tab. * @param {MaterialTabs} ctx The MaterialTabs object that owns the tab. */ function MaterialTab(tab, ctx) { if (tab) { if (ctx.element_.classList.contains(ctx.CssClasses_.MDL_JS_RIPPLE_EFFECT)) { var rippleContainer = document.createElement('span'); rippleContainer.classList.add(ctx.CssClasses_.MDL_RIPPLE_CONTAINER); rippleContainer.classList.add(ctx.CssClasses_.MDL_JS_RIPPLE_EFFECT); var ripple = document.createElement('span'); ripple.classList.add(ctx.CssClasses_.MDL_RIPPLE); rippleContainer.appendChild(ripple); tab.appendChild(rippleContainer); } tab.addEventListener('click', function (e) { if (tab.getAttribute('href').charAt(0) === '#') { e.preventDefault(); var href = tab.href.split('#')[1]; var panel = ctx.element_.querySelector('#' + href); ctx.resetTabState_(); ctx.resetPanelState_(); tab.classList.add(ctx.CssClasses_.ACTIVE_CLASS); panel.classList.add(ctx.CssClasses_.ACTIVE_CLASS); } }); } } // The component registers itself. It can assume componentHandler is available // in the global scope. componentHandler.register({ constructor: MaterialTabs, classAsString: 'MaterialTabs', cssClass: 'mdl-js-tabs' }); /** * @license * Copyright 2015 Google Inc. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * Class constructor for Textfield MDL component. * Implements MDL component design pattern defined at: * https://github.com/jasonmayes/mdl-component-design-pattern * * @constructor * @param {HTMLElement} element The element that will be upgraded. */ var MaterialTextfield = function MaterialTextfield(element) { this.element_ = element; this.maxRows = this.Constant_.NO_MAX_ROWS; // Initialize instance. this.init(); }; window['MaterialTextfield'] = MaterialTextfield; /** * Store constants in one place so they can be updated easily. * * @enum {string | number} * @private */ MaterialTextfield.prototype.Constant_ = { NO_MAX_ROWS: -1, MAX_ROWS_ATTRIBUTE: 'maxrows' }; /** * Store strings for class names defined by this component that are used in * JavaScript. This allows us to simply change it in one place should we * decide to modify at a later date. * * @enum {string} * @private */ MaterialTextfield.prototype.CssClasses_ = { LABEL: 'mdl-textfield__label', INPUT: 'mdl-textfield__input', IS_DIRTY: 'is-dirty', IS_FOCUSED: 'is-focused', IS_DISABLED: 'is-disabled', IS_INVALID: 'is-invalid', IS_UPGRADED: 'is-upgraded', HAS_PLACEHOLDER: 'has-placeholder' }; /** * Handle input being entered. * * @param {Event} event The event that fired. * @private */ MaterialTextfield.prototype.onKeyDown_ = function (event) { var currentRowCount = event.target.value.split('\n').length; if (event.keyCode === 13) { if (currentRowCount >= this.maxRows) { event.preventDefault(); } } }; /** * Handle focus. * * @param {Event} event The event that fired. * @private */ MaterialTextfield.prototype.onFocus_ = function (event) { this.element_.classList.add(this.CssClasses_.IS_FOCUSED); }; /** * Handle lost focus. * * @param {Event} event The event that fired. * @private */ MaterialTextfield.prototype.onBlur_ = function (event) { this.element_.classList.remove(this.CssClasses_.IS_FOCUSED); }; /** * Handle reset event from out side. * * @param {Event} event The event that fired. * @private */ MaterialTextfield.prototype.onReset_ = function (event) { this.updateClasses_(); }; /** * Handle class updates. * * @private */ MaterialTextfield.prototype.updateClasses_ = function () { this.checkDisabled(); this.checkValidity(); this.checkDirty(); this.checkFocus(); }; // Public methods. /** * Check the disabled state and update field accordingly. * * @public */ MaterialTextfield.prototype.checkDisabled = function () { if (this.input_.disabled) { this.element_.classList.add(this.CssClasses_.IS_DISABLED); } else { this.element_.classList.remove(this.CssClasses_.IS_DISABLED); } }; MaterialTextfield.prototype['checkDisabled'] = MaterialTextfield.prototype.checkDisabled; /** * Check the focus state and update field accordingly. * * @public */ MaterialTextfield.prototype.checkFocus = function () { if (Boolean(this.element_.querySelector(':focus'))) { this.element_.classList.add(this.CssClasses_.IS_FOCUSED); } else { this.element_.classList.remove(this.CssClasses_.IS_FOCUSED); } }; MaterialTextfield.prototype['checkFocus'] = MaterialTextfield.prototype.checkFocus; /** * Check the validity state and update field accordingly. * * @public */ MaterialTextfield.prototype.checkValidity = function () { if (this.input_.validity) { if (this.input_.validity.valid) { this.element_.classList.remove(this.CssClasses_.IS_INVALID); } else { this.element_.classList.add(this.CssClasses_.IS_INVALID); } } }; MaterialTextfield.prototype['checkValidity'] = MaterialTextfield.prototype.checkValidity; /** * Check the dirty state and update field accordingly. * * @public */ MaterialTextfield.prototype.checkDirty = function () { if (this.input_.value && this.input_.value.length > 0) { this.element_.classList.add(this.CssClasses_.IS_DIRTY); } else { this.element_.classList.remove(this.CssClasses_.IS_DIRTY); } }; MaterialTextfield.prototype['checkDirty'] = MaterialTextfield.prototype.checkDirty; /** * Disable text field. * * @public */ MaterialTextfield.prototype.disable = function () { this.input_.disabled = true; this.updateClasses_(); }; MaterialTextfield.prototype['disable'] = MaterialTextfield.prototype.disable; /** * Enable text field. * * @public */ MaterialTextfield.prototype.enable = function () { this.input_.disabled = false; this.updateClasses_(); }; MaterialTextfield.prototype['enable'] = MaterialTextfield.prototype.enable; /** * Update text field value. * * @param {string} value The value to which to set the control (optional). * @public */ MaterialTextfield.prototype.change = function (value) { this.input_.value = value || ''; this.updateClasses_(); }; MaterialTextfield.prototype['change'] = MaterialTextfield.prototype.change; /** * Initialize element. */ MaterialTextfield.prototype.init = function () { if (this.element_) { this.label_ = this.element_.querySelector('.' + this.CssClasses_.LABEL); this.input_ = this.element_.querySelector('.' + this.CssClasses_.INPUT); if (this.input_) { if (this.input_.hasAttribute(this.Constant_.MAX_ROWS_ATTRIBUTE)) { this.maxRows = parseInt(this.input_.getAttribute(this.Constant_.MAX_ROWS_ATTRIBUTE), 10); if (isNaN(this.maxRows)) { this.maxRows = this.Constant_.NO_MAX_ROWS; } } if (this.input_.hasAttribute('placeholder')) { this.element_.classList.add(this.CssClasses_.HAS_PLACEHOLDER); } this.boundUpdateClassesHandler = this.updateClasses_.bind(this); this.boundFocusHandler = this.onFocus_.bind(this); this.boundBlurHandler = this.onBlur_.bind(this); this.boundResetHandler = this.onReset_.bind(this); this.input_.addEventListener('input', this.boundUpdateClassesHandler); this.input_.addEventListener('focus', this.boundFocusHandler); this.input_.addEventListener('blur', this.boundBlurHandler); this.input_.addEventListener('reset', this.boundResetHandler); if (this.maxRows !== this.Constant_.NO_MAX_ROWS) { // TODO: This should handle pasting multi line text. // Currently doesn't. this.boundKeyDownHandler = this.onKeyDown_.bind(this); this.input_.addEventListener('keydown', this.boundKeyDownHandler); } var invalid = this.element_.classList.contains(this.CssClasses_.IS_INVALID); this.updateClasses_(); this.element_.classList.add(this.CssClasses_.IS_UPGRADED); if (invalid) { this.element_.classList.add(this.CssClasses_.IS_INVALID); } if (this.input_.hasAttribute('autofocus')) { this.element_.focus(); this.checkFocus(); } } } }; // The component registers itself. It can assume componentHandler is available // in the global scope. componentHandler.register({ constructor: MaterialTextfield, classAsString: 'MaterialTextfield', cssClass: 'mdl-js-textfield', widget: true }); /** * @license * Copyright 2015 Google Inc. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * Class constructor for Tooltip MDL component. * Implements MDL component design pattern defined at: * https://github.com/jasonmayes/mdl-component-design-pattern * * @constructor * @param {HTMLElement} element The element that will be upgraded. */ var MaterialTooltip = function MaterialTooltip(element) { this.element_ = element; // Initialize instance. this.init(); }; window['MaterialTooltip'] = MaterialTooltip; /** * Store constants in one place so they can be updated easily. * * @enum {string | number} * @private */ MaterialTooltip.prototype.Constant_ = {}; /** * Store strings for class names defined by this component that are used in * JavaScript. This allows us to simply change it in one place should we * decide to modify at a later date. * * @enum {string} * @private */ MaterialTooltip.prototype.CssClasses_ = { IS_ACTIVE: 'is-active', BOTTOM: 'mdl-tooltip--bottom', LEFT: 'mdl-tooltip--left', RIGHT: 'mdl-tooltip--right', TOP: 'mdl-tooltip--top' }; /** * Handle mouseenter for tooltip. * * @param {Event} event The event that fired. * @private */ MaterialTooltip.prototype.handleMouseEnter_ = function (event) { var props = event.target.getBoundingClientRect(); var left = props.left + props.width / 2; var top = props.top + props.height / 2; var marginLeft = -1 * (this.element_.offsetWidth / 2); var marginTop = -1 * (this.element_.offsetHeight / 2); if (this.element_.classList.contains(this.CssClasses_.LEFT) || this.element_.classList.contains(this.CssClasses_.RIGHT)) { left = props.width / 2; if (top + marginTop < 0) { this.element_.style.top = '0'; this.element_.style.marginTop = '0'; } else { this.element_.style.top = top + 'px'; this.element_.style.marginTop = marginTop + 'px'; } } else { if (left + marginLeft < 0) { this.element_.style.left = '0'; this.element_.style.marginLeft = '0'; } else { this.element_.style.left = left + 'px'; this.element_.style.marginLeft = marginLeft + 'px'; } } if (this.element_.classList.contains(this.CssClasses_.TOP)) { this.element_.style.top = props.top - this.element_.offsetHeight - 10 + 'px'; } else if (this.element_.classList.contains(this.CssClasses_.RIGHT)) { this.element_.style.left = props.left + props.width + 10 + 'px'; } else if (this.element_.classList.contains(this.CssClasses_.LEFT)) { this.element_.style.left = props.left - this.element_.offsetWidth - 10 + 'px'; } else { this.element_.style.top = props.top + props.height + 10 + 'px'; } this.element_.classList.add(this.CssClasses_.IS_ACTIVE); }; /** * Hide tooltip on mouseleave or scroll * * @private */ MaterialTooltip.prototype.hideTooltip_ = function () { this.element_.classList.remove(this.CssClasses_.IS_ACTIVE); }; /** * Initialize element. */ MaterialTooltip.prototype.init = function () { if (this.element_) { var forElId = this.element_.getAttribute('for') || this.element_.getAttribute('data-mdl-for'); if (forElId) { this.forElement_ = document.getElementById(forElId); } if (this.forElement_) { // It's left here because it prevents accidental text selection on Android if (!this.forElement_.hasAttribute('tabindex')) { this.forElement_.setAttribute('tabindex', '0'); } this.boundMouseEnterHandler = this.handleMouseEnter_.bind(this); this.boundMouseLeaveAndScrollHandler = this.hideTooltip_.bind(this); this.forElement_.addEventListener('mouseenter', this.boundMouseEnterHandler, false); this.forElement_.addEventListener('touchend', this.boundMouseEnterHandler, false); this.forElement_.addEventListener('mouseleave', this.boundMouseLeaveAndScrollHandler, false); window.addEventListener('scroll', this.boundMouseLeaveAndScrollHandler, true); window.addEventListener('touchstart', this.boundMouseLeaveAndScrollHandler); } } }; // The component registers itself. It can assume componentHandler is available // in the global scope. componentHandler.register({ constructor: MaterialTooltip, classAsString: 'MaterialTooltip', cssClass: 'mdl-tooltip' }); /** * @license * Copyright 2015 Google Inc. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * Class constructor for Layout MDL component. * Implements MDL component design pattern defined at: * https://github.com/jasonmayes/mdl-component-design-pattern * * @constructor * @param {HTMLElement} element The element that will be upgraded. */ var MaterialLayout = function MaterialLayout(element) { this.element_ = element; // Initialize instance. this.init(); }; window['MaterialLayout'] = MaterialLayout; /** * Store constants in one place so they can be updated easily. * * @enum {string | number} * @private */ MaterialLayout.prototype.Constant_ = { MAX_WIDTH: '(max-width: 1024px)', TAB_SCROLL_PIXELS: 100, RESIZE_TIMEOUT: 100, MENU_ICON: '', CHEVRON_LEFT: 'chevron_left', CHEVRON_RIGHT: 'chevron_right' }; /** * Keycodes, for code readability. * * @enum {number} * @private */ MaterialLayout.prototype.Keycodes_ = { ENTER: 13, ESCAPE: 27, SPACE: 32 }; /** * Modes. * * @enum {number} * @private */ MaterialLayout.prototype.Mode_ = { STANDARD: 0, SEAMED: 1, WATERFALL: 2, SCROLL: 3 }; /** * Store strings for class names defined by this component that are used in * JavaScript. This allows us to simply change it in one place should we * decide to modify at a later date. * * @enum {string} * @private */ MaterialLayout.prototype.CssClasses_ = { CONTAINER: 'mdl-layout__container', HEADER: 'mdl-layout__header', DRAWER: 'mdl-layout__drawer', CONTENT: 'mdl-layout__content', DRAWER_BTN: 'mdl-layout__drawer-button', ICON: 'material-icons', JS_RIPPLE_EFFECT: 'mdl-js-ripple-effect', RIPPLE_CONTAINER: 'mdl-layout__tab-ripple-container', RIPPLE: 'mdl-ripple', RIPPLE_IGNORE_EVENTS: 'mdl-js-ripple-effect--ignore-events', HEADER_SEAMED: 'mdl-layout__header--seamed', HEADER_WATERFALL: 'mdl-layout__header--waterfall', HEADER_SCROLL: 'mdl-layout__header--scroll', FIXED_HEADER: 'mdl-layout--fixed-header', OBFUSCATOR: 'mdl-layout__obfuscator', TAB_BAR: 'mdl-layout__tab-bar', TAB_CONTAINER: 'mdl-layout__tab-bar-container', TAB: 'mdl-layout__tab', TAB_BAR_BUTTON: 'mdl-layout__tab-bar-button', TAB_BAR_LEFT_BUTTON: 'mdl-layout__tab-bar-left-button', TAB_BAR_RIGHT_BUTTON: 'mdl-layout__tab-bar-right-button', TAB_MANUAL_SWITCH: 'mdl-layout__tab-manual-switch', PANEL: 'mdl-layout__tab-panel', HAS_DRAWER: 'has-drawer', HAS_TABS: 'has-tabs', HAS_SCROLLING_HEADER: 'has-scrolling-header', CASTING_SHADOW: 'is-casting-shadow', IS_COMPACT: 'is-compact', IS_SMALL_SCREEN: 'is-small-screen', IS_DRAWER_OPEN: 'is-visible', IS_ACTIVE: 'is-active', IS_UPGRADED: 'is-upgraded', IS_ANIMATING: 'is-animating', ON_LARGE_SCREEN: 'mdl-layout--large-screen-only', ON_SMALL_SCREEN: 'mdl-layout--small-screen-only' }; /** * Handles scrolling on the content. * * @private */ MaterialLayout.prototype.contentScrollHandler_ = function () { if (this.header_.classList.contains(this.CssClasses_.IS_ANIMATING)) { return; } var headerVisible = !this.element_.classList.contains(this.CssClasses_.IS_SMALL_SCREEN) || this.element_.classList.contains(this.CssClasses_.FIXED_HEADER); if (this.content_.scrollTop > 0 && !this.header_.classList.contains(this.CssClasses_.IS_COMPACT)) { this.header_.classList.add(this.CssClasses_.CASTING_SHADOW); this.header_.classList.add(this.CssClasses_.IS_COMPACT); if (headerVisible) { this.header_.classList.add(this.CssClasses_.IS_ANIMATING); } } else if (this.content_.scrollTop <= 0 && this.header_.classList.contains(this.CssClasses_.IS_COMPACT)) { this.header_.classList.remove(this.CssClasses_.CASTING_SHADOW); this.header_.classList.remove(this.CssClasses_.IS_COMPACT); if (headerVisible) { this.header_.classList.add(this.CssClasses_.IS_ANIMATING); } } }; /** * Handles a keyboard event on the drawer. * * @param {Event} evt The event that fired. * @private */ MaterialLayout.prototype.keyboardEventHandler_ = function (evt) { // Only react when the drawer is open. if (evt.keyCode === this.Keycodes_.ESCAPE && this.drawer_.classList.contains(this.CssClasses_.IS_DRAWER_OPEN)) { this.toggleDrawer(); } }; /** * Handles changes in screen size. * * @private */ MaterialLayout.prototype.screenSizeHandler_ = function () { if (this.screenSizeMediaQuery_.matches) { this.element_.classList.add(this.CssClasses_.IS_SMALL_SCREEN); } else { this.element_.classList.remove(this.CssClasses_.IS_SMALL_SCREEN); // Collapse drawer (if any) when moving to a large screen size. if (this.drawer_) { this.drawer_.classList.remove(this.CssClasses_.IS_DRAWER_OPEN); this.obfuscator_.classList.remove(this.CssClasses_.IS_DRAWER_OPEN); } } }; /** * Handles events of drawer button. * * @param {Event} evt The event that fired. * @private */ MaterialLayout.prototype.drawerToggleHandler_ = function (evt) { if (evt && evt.type === 'keydown') { if (evt.keyCode === this.Keycodes_.SPACE || evt.keyCode === this.Keycodes_.ENTER) { // prevent scrolling in drawer nav evt.preventDefault(); } else { // prevent other keys return; } } this.toggleDrawer(); }; /** * Handles (un)setting the `is-animating` class * * @private */ MaterialLayout.prototype.headerTransitionEndHandler_ = function () { this.header_.classList.remove(this.CssClasses_.IS_ANIMATING); }; /** * Handles expanding the header on click * * @private */ MaterialLayout.prototype.headerClickHandler_ = function () { if (this.header_.classList.contains(this.CssClasses_.IS_COMPACT)) { this.header_.classList.remove(this.CssClasses_.IS_COMPACT); this.header_.classList.add(this.CssClasses_.IS_ANIMATING); } }; /** * Reset tab state, dropping active classes * * @private */ MaterialLayout.prototype.resetTabState_ = function (tabBar) { for (var k = 0; k < tabBar.length; k++) { tabBar[k].classList.remove(this.CssClasses_.IS_ACTIVE); } }; /** * Reset panel state, droping active classes * * @private */ MaterialLayout.prototype.resetPanelState_ = function (panels) { for (var j = 0; j < panels.length; j++) { panels[j].classList.remove(this.CssClasses_.IS_ACTIVE); } }; /** * Toggle drawer state * * @public */ MaterialLayout.prototype.toggleDrawer = function () { var drawerButton = this.element_.querySelector('.' + this.CssClasses_.DRAWER_BTN); this.drawer_.classList.toggle(this.CssClasses_.IS_DRAWER_OPEN); this.obfuscator_.classList.toggle(this.CssClasses_.IS_DRAWER_OPEN); // Set accessibility properties. if (this.drawer_.classList.contains(this.CssClasses_.IS_DRAWER_OPEN)) { this.drawer_.setAttribute('aria-hidden', 'false'); drawerButton.setAttribute('aria-expanded', 'true'); } else { this.drawer_.setAttribute('aria-hidden', 'true'); drawerButton.setAttribute('aria-expanded', 'false'); } }; MaterialLayout.prototype['toggleDrawer'] = MaterialLayout.prototype.toggleDrawer; /** * Initialize element. */ MaterialLayout.prototype.init = function () { if (this.element_) { var container = document.createElement('div'); container.classList.add(this.CssClasses_.CONTAINER); var focusedElement = this.element_.querySelector(':focus'); this.element_.parentElement.insertBefore(container, this.element_); this.element_.parentElement.removeChild(this.element_); container.appendChild(this.element_); if (focusedElement) { focusedElement.focus(); } var directChildren = this.element_.childNodes; var numChildren = directChildren.length; for (var c = 0; c < numChildren; c++) { var child = directChildren[c]; if (child.classList && child.classList.contains(this.CssClasses_.HEADER)) { this.header_ = child; } if (child.classList && child.classList.contains(this.CssClasses_.DRAWER)) { this.drawer_ = child; } if (child.classList && child.classList.contains(this.CssClasses_.CONTENT)) { this.content_ = child; } } window.addEventListener('pageshow', function (e) { if (e.persisted) { // when page is loaded from back/forward cache // trigger repaint to let layout scroll in safari this.element_.style.overflowY = 'hidden'; requestAnimationFrame(function () { this.element_.style.overflowY = ''; }.bind(this)); } }.bind(this), false); if (this.header_) { this.tabBar_ = this.header_.querySelector('.' + this.CssClasses_.TAB_BAR); } var mode = this.Mode_.STANDARD; if (this.header_) { if (this.header_.classList.contains(this.CssClasses_.HEADER_SEAMED)) { mode = this.Mode_.SEAMED; } else if (this.header_.classList.contains(this.CssClasses_.HEADER_WATERFALL)) { mode = this.Mode_.WATERFALL; this.header_.addEventListener('transitionend', this.headerTransitionEndHandler_.bind(this)); this.header_.addEventListener('click', this.headerClickHandler_.bind(this)); } else if (this.header_.classList.contains(this.CssClasses_.HEADER_SCROLL)) { mode = this.Mode_.SCROLL; container.classList.add(this.CssClasses_.HAS_SCROLLING_HEADER); } if (mode === this.Mode_.STANDARD) { this.header_.classList.add(this.CssClasses_.CASTING_SHADOW); if (this.tabBar_) { this.tabBar_.classList.add(this.CssClasses_.CASTING_SHADOW); } } else if (mode === this.Mode_.SEAMED || mode === this.Mode_.SCROLL) { this.header_.classList.remove(this.CssClasses_.CASTING_SHADOW); if (this.tabBar_) { this.tabBar_.classList.remove(this.CssClasses_.CASTING_SHADOW); } } else if (mode === this.Mode_.WATERFALL) { // Add and remove shadows depending on scroll position. // Also add/remove auxiliary class for styling of the compact version of // the header. this.content_.addEventListener('scroll', this.contentScrollHandler_.bind(this)); this.contentScrollHandler_(); } } // Add drawer toggling button to our layout, if we have an openable drawer. if (this.drawer_) { var drawerButton = this.element_.querySelector('.' + this.CssClasses_.DRAWER_BTN); if (!drawerButton) { drawerButton = document.createElement('div'); drawerButton.setAttribute('aria-expanded', 'false'); drawerButton.setAttribute('role', 'button'); drawerButton.setAttribute('tabindex', '0'); drawerButton.classList.add(this.CssClasses_.DRAWER_BTN); var drawerButtonIcon = document.createElement('i'); drawerButtonIcon.classList.add(this.CssClasses_.ICON); drawerButtonIcon.innerHTML = this.Constant_.MENU_ICON; drawerButton.appendChild(drawerButtonIcon); } if (this.drawer_.classList.contains(this.CssClasses_.ON_LARGE_SCREEN)) { //If drawer has ON_LARGE_SCREEN class then add it to the drawer toggle button as well. drawerButton.classList.add(this.CssClasses_.ON_LARGE_SCREEN); } else if (this.drawer_.classList.contains(this.CssClasses_.ON_SMALL_SCREEN)) { //If drawer has ON_SMALL_SCREEN class then add it to the drawer toggle button as well. drawerButton.classList.add(this.CssClasses_.ON_SMALL_SCREEN); } drawerButton.addEventListener('click', this.drawerToggleHandler_.bind(this)); drawerButton.addEventListener('keydown', this.drawerToggleHandler_.bind(this)); // Add a class if the layout has a drawer, for altering the left padding. // Adds the HAS_DRAWER to the elements since this.header_ may or may // not be present. this.element_.classList.add(this.CssClasses_.HAS_DRAWER); // If we have a fixed header, add the button to the header rather than // the layout. if (this.element_.classList.contains(this.CssClasses_.FIXED_HEADER)) { this.header_.insertBefore(drawerButton, this.header_.firstChild); } else { this.element_.insertBefore(drawerButton, this.content_); } var obfuscator = document.createElement('div'); obfuscator.classList.add(this.CssClasses_.OBFUSCATOR); this.element_.appendChild(obfuscator); obfuscator.addEventListener('click', this.drawerToggleHandler_.bind(this)); this.obfuscator_ = obfuscator; this.drawer_.addEventListener('keydown', this.keyboardEventHandler_.bind(this)); this.drawer_.setAttribute('aria-hidden', 'true'); } // Keep an eye on screen size, and add/remove auxiliary class for styling // of small screens. this.screenSizeMediaQuery_ = window.matchMedia(this.Constant_.MAX_WIDTH); this.screenSizeMediaQuery_.addListener(this.screenSizeHandler_.bind(this)); this.screenSizeHandler_(); // Initialize tabs, if any. if (this.header_ && this.tabBar_) { this.element_.classList.add(this.CssClasses_.HAS_TABS); var tabContainer = document.createElement('div'); tabContainer.classList.add(this.CssClasses_.TAB_CONTAINER); this.header_.insertBefore(tabContainer, this.tabBar_); this.header_.removeChild(this.tabBar_); var leftButton = document.createElement('div'); leftButton.classList.add(this.CssClasses_.TAB_BAR_BUTTON); leftButton.classList.add(this.CssClasses_.TAB_BAR_LEFT_BUTTON); var leftButtonIcon = document.createElement('i'); leftButtonIcon.classList.add(this.CssClasses_.ICON); leftButtonIcon.textContent = this.Constant_.CHEVRON_LEFT; leftButton.appendChild(leftButtonIcon); leftButton.addEventListener('click', function () { this.tabBar_.scrollLeft -= this.Constant_.TAB_SCROLL_PIXELS; }.bind(this)); var rightButton = document.createElement('div'); rightButton.classList.add(this.CssClasses_.TAB_BAR_BUTTON); rightButton.classList.add(this.CssClasses_.TAB_BAR_RIGHT_BUTTON); var rightButtonIcon = document.createElement('i'); rightButtonIcon.classList.add(this.CssClasses_.ICON); rightButtonIcon.textContent = this.Constant_.CHEVRON_RIGHT; rightButton.appendChild(rightButtonIcon); rightButton.addEventListener('click', function () { this.tabBar_.scrollLeft += this.Constant_.TAB_SCROLL_PIXELS; }.bind(this)); tabContainer.appendChild(leftButton); tabContainer.appendChild(this.tabBar_); tabContainer.appendChild(rightButton); // Add and remove tab buttons depending on scroll position and total // window size. var tabUpdateHandler = function () { if (this.tabBar_.scrollLeft > 0) { leftButton.classList.add(this.CssClasses_.IS_ACTIVE); } else { leftButton.classList.remove(this.CssClasses_.IS_ACTIVE); } if (this.tabBar_.scrollLeft < this.tabBar_.scrollWidth - this.tabBar_.offsetWidth) { rightButton.classList.add(this.CssClasses_.IS_ACTIVE); } else { rightButton.classList.remove(this.CssClasses_.IS_ACTIVE); } }.bind(this); this.tabBar_.addEventListener('scroll', tabUpdateHandler); tabUpdateHandler(); // Update tabs when the window resizes. var windowResizeHandler = function () { // Use timeouts to make sure it doesn't happen too often. if (this.resizeTimeoutId_) { clearTimeout(this.resizeTimeoutId_); } this.resizeTimeoutId_ = setTimeout(function () { tabUpdateHandler(); this.resizeTimeoutId_ = null; }.bind(this), this.Constant_.RESIZE_TIMEOUT); }.bind(this); window.addEventListener('resize', windowResizeHandler); if (this.tabBar_.classList.contains(this.CssClasses_.JS_RIPPLE_EFFECT)) { this.tabBar_.classList.add(this.CssClasses_.RIPPLE_IGNORE_EVENTS); } // Select element tabs, document panels var tabs = this.tabBar_.querySelectorAll('.' + this.CssClasses_.TAB); var panels = this.content_.querySelectorAll('.' + this.CssClasses_.PANEL); // Create new tabs for each tab element for (var i = 0; i < tabs.length; i++) { new MaterialLayoutTab(tabs[i], tabs, panels, this); } } this.element_.classList.add(this.CssClasses_.IS_UPGRADED); } }; /** * Constructor for an individual tab. * * @constructor * @param {HTMLElement} tab The HTML element for the tab. * @param {!Array} tabs Array with HTML elements for all tabs. * @param {!Array} panels Array with HTML elements for all panels. * @param {MaterialLayout} layout The MaterialLayout object that owns the tab. */ function MaterialLayoutTab(tab, tabs, panels, layout) { /** * Auxiliary method to programmatically select a tab in the UI. */ function selectTab() { var href = tab.href.split('#')[1]; var panel = layout.content_.querySelector('#' + href); layout.resetTabState_(tabs); layout.resetPanelState_(panels); tab.classList.add(layout.CssClasses_.IS_ACTIVE); panel.classList.add(layout.CssClasses_.IS_ACTIVE); } if (layout.tabBar_.classList.contains(layout.CssClasses_.JS_RIPPLE_EFFECT)) { var rippleContainer = document.createElement('span'); rippleContainer.classList.add(layout.CssClasses_.RIPPLE_CONTAINER); rippleContainer.classList.add(layout.CssClasses_.JS_RIPPLE_EFFECT); var ripple = document.createElement('span'); ripple.classList.add(layout.CssClasses_.RIPPLE); rippleContainer.appendChild(ripple); tab.appendChild(rippleContainer); } if (!layout.tabBar_.classList.contains(layout.CssClasses_.TAB_MANUAL_SWITCH)) { tab.addEventListener('click', function (e) { if (tab.getAttribute('href').charAt(0) === '#') { e.preventDefault(); selectTab(); } }); } tab.show = selectTab; } window['MaterialLayoutTab'] = MaterialLayoutTab; // The component registers itself. It can assume componentHandler is available // in the global scope. componentHandler.register({ constructor: MaterialLayout, classAsString: 'MaterialLayout', cssClass: 'mdl-js-layout' }); /** * @license * Copyright 2015 Google Inc. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * Class constructor for Data Table Card MDL component. * Implements MDL component design pattern defined at: * https://github.com/jasonmayes/mdl-component-design-pattern * * @constructor * @param {Element} element The element that will be upgraded. */ var MaterialDataTable = function MaterialDataTable(element) { this.element_ = element; // Initialize instance. this.init(); }; window['MaterialDataTable'] = MaterialDataTable; /** * Store constants in one place so they can be updated easily. * * @enum {string | number} * @private */ MaterialDataTable.prototype.Constant_ = {}; /** * Store strings for class names defined by this component that are used in * JavaScript. This allows us to simply change it in one place should we * decide to modify at a later date. * * @enum {string} * @private */ MaterialDataTable.prototype.CssClasses_ = { DATA_TABLE: 'mdl-data-table', SELECTABLE: 'mdl-data-table--selectable', SELECT_ELEMENT: 'mdl-data-table__select', IS_SELECTED: 'is-selected', IS_UPGRADED: 'is-upgraded' }; /** * Generates and returns a function that toggles the selection state of a * single row (or multiple rows). * * @param {Element} checkbox Checkbox that toggles the selection state. * @param {Element} row Row to toggle when checkbox changes. * @param {(Array|NodeList)=} opt_rows Rows to toggle when checkbox changes. * @private */ MaterialDataTable.prototype.selectRow_ = function (checkbox, row, opt_rows) { if (row) { return function () { if (checkbox.checked) { row.classList.add(this.CssClasses_.IS_SELECTED); } else { row.classList.remove(this.CssClasses_.IS_SELECTED); } }.bind(this); } if (opt_rows) { return function () { var i; var el; if (checkbox.checked) { for (i = 0; i < opt_rows.length; i++) { el = opt_rows[i].querySelector('td').querySelector('.mdl-checkbox'); el['MaterialCheckbox'].check(); opt_rows[i].classList.add(this.CssClasses_.IS_SELECTED); } } else { for (i = 0; i < opt_rows.length; i++) { el = opt_rows[i].querySelector('td').querySelector('.mdl-checkbox'); el['MaterialCheckbox'].uncheck(); opt_rows[i].classList.remove(this.CssClasses_.IS_SELECTED); } } }.bind(this); } }; /** * Creates a checkbox for a single or or multiple rows and hooks up the * event handling. * * @param {Element} row Row to toggle when checkbox changes. * @param {(Array|NodeList)=} opt_rows Rows to toggle when checkbox changes. * @private */ MaterialDataTable.prototype.createCheckbox_ = function (row, opt_rows) { var label = document.createElement('label'); var labelClasses = [ 'mdl-checkbox', 'mdl-js-checkbox', 'mdl-js-ripple-effect', this.CssClasses_.SELECT_ELEMENT ]; label.className = labelClasses.join(' '); var checkbox = document.createElement('input'); checkbox.type = 'checkbox'; checkbox.classList.add('mdl-checkbox__input'); if (row) { checkbox.checked = row.classList.contains(this.CssClasses_.IS_SELECTED); checkbox.addEventListener('change', this.selectRow_(checkbox, row)); } else if (opt_rows) { checkbox.addEventListener('change', this.selectRow_(checkbox, null, opt_rows)); } label.appendChild(checkbox); componentHandler.upgradeElement(label, 'MaterialCheckbox'); return label; }; /** * Initialize element. */ MaterialDataTable.prototype.init = function () { if (this.element_) { var firstHeader = this.element_.querySelector('th'); var bodyRows = Array.prototype.slice.call(this.element_.querySelectorAll('tbody tr')); var footRows = Array.prototype.slice.call(this.element_.querySelectorAll('tfoot tr')); var rows = bodyRows.concat(footRows); if (this.element_.classList.contains(this.CssClasses_.SELECTABLE)) { var th = document.createElement('th'); var headerCheckbox = this.createCheckbox_(null, rows); th.appendChild(headerCheckbox); firstHeader.parentElement.insertBefore(th, firstHeader); for (var i = 0; i < rows.length; i++) { var firstCell = rows[i].querySelector('td'); if (firstCell) { var td = document.createElement('td'); if (rows[i].parentNode.nodeName.toUpperCase() === 'TBODY') { var rowCheckbox = this.createCheckbox_(rows[i]); td.appendChild(rowCheckbox); } rows[i].insertBefore(td, firstCell); } } this.element_.classList.add(this.CssClasses_.IS_UPGRADED); } } }; // The component registers itself. It can assume componentHandler is available // in the global scope. componentHandler.register({ constructor: MaterialDataTable, classAsString: 'MaterialDataTable', cssClass: 'mdl-js-data-table' }); /** * @license * Copyright 2015 Google Inc. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * Class constructor for Ripple MDL component. * Implements MDL component design pattern defined at: * https://github.com/jasonmayes/mdl-component-design-pattern * * @constructor * @param {HTMLElement} element The element that will be upgraded. */ var MaterialRipple = function MaterialRipple(element) { this.element_ = element; // Initialize instance. this.init(); }; window['MaterialRipple'] = MaterialRipple; /** * Store constants in one place so they can be updated easily. * * @enum {string | number} * @private */ MaterialRipple.prototype.Constant_ = { INITIAL_SCALE: 'scale(0.0001, 0.0001)', INITIAL_SIZE: '1px', INITIAL_OPACITY: '0.4', FINAL_OPACITY: '0', FINAL_SCALE: '' }; /** * Store strings for class names defined by this component that are used in * JavaScript. This allows us to simply change it in one place should we * decide to modify at a later date. * * @enum {string} * @private */ MaterialRipple.prototype.CssClasses_ = { RIPPLE_CENTER: 'mdl-ripple--center', RIPPLE_EFFECT_IGNORE_EVENTS: 'mdl-js-ripple-effect--ignore-events', RIPPLE: 'mdl-ripple', IS_ANIMATING: 'is-animating', IS_VISIBLE: 'is-visible' }; /** * Handle mouse / finger down on element. * * @param {Event} event The event that fired. * @private */ MaterialRipple.prototype.downHandler_ = function (event) { if (!this.rippleElement_.style.width && !this.rippleElement_.style.height) { var rect = this.element_.getBoundingClientRect(); this.boundHeight = rect.height; this.boundWidth = rect.width; this.rippleSize_ = Math.sqrt(rect.width * rect.width + rect.height * rect.height) * 2 + 2; this.rippleElement_.style.width = this.rippleSize_ + 'px'; this.rippleElement_.style.height = this.rippleSize_ + 'px'; } this.rippleElement_.classList.add(this.CssClasses_.IS_VISIBLE); if (event.type === 'mousedown' && this.ignoringMouseDown_) { this.ignoringMouseDown_ = false; } else { if (event.type === 'touchstart') { this.ignoringMouseDown_ = true; } var frameCount = this.getFrameCount(); if (frameCount > 0) { return; } this.setFrameCount(1); var bound = event.currentTarget.getBoundingClientRect(); var x; var y; // Check if we are handling a keyboard click. if (event.clientX === 0 && event.clientY === 0) { x = Math.round(bound.width / 2); y = Math.round(bound.height / 2); } else { var clientX = event.clientX !== undefined ? event.clientX : event.touches[0].clientX; var clientY = event.clientY !== undefined ? event.clientY : event.touches[0].clientY; x = Math.round(clientX - bound.left); y = Math.round(clientY - bound.top); } this.setRippleXY(x, y); this.setRippleStyles(true); window.requestAnimationFrame(this.animFrameHandler.bind(this)); } }; /** * Handle mouse / finger up on element. * * @param {Event} event The event that fired. * @private */ MaterialRipple.prototype.upHandler_ = function (event) { // Don't fire for the artificial "mouseup" generated by a double-click. if (event && event.detail !== 2) { // Allow a repaint to occur before removing this class, so the animation // shows for tap events, which seem to trigger a mouseup too soon after // mousedown. window.setTimeout(function () { this.rippleElement_.classList.remove(this.CssClasses_.IS_VISIBLE); }.bind(this), 0); } }; /** * Initialize element. */ MaterialRipple.prototype.init = function () { if (this.element_) { var recentering = this.element_.classList.contains(this.CssClasses_.RIPPLE_CENTER); if (!this.element_.classList.contains(this.CssClasses_.RIPPLE_EFFECT_IGNORE_EVENTS)) { this.rippleElement_ = this.element_.querySelector('.' + this.CssClasses_.RIPPLE); this.frameCount_ = 0; this.rippleSize_ = 0; this.x_ = 0; this.y_ = 0; // Touch start produces a compat mouse down event, which would cause a // second ripples. To avoid that, we use this property to ignore the first // mouse down after a touch start. this.ignoringMouseDown_ = false; this.boundDownHandler = this.downHandler_.bind(this); this.element_.addEventListener('mousedown', this.boundDownHandler); this.element_.addEventListener('touchstart', this.boundDownHandler); this.boundUpHandler = this.upHandler_.bind(this); this.element_.addEventListener('mouseup', this.boundUpHandler); this.element_.addEventListener('mouseleave', this.boundUpHandler); this.element_.addEventListener('touchend', this.boundUpHandler); this.element_.addEventListener('blur', this.boundUpHandler); /** * Getter for frameCount_. * @return {number} the frame count. */ this.getFrameCount = function () { return this.frameCount_; }; /** * Setter for frameCount_. * @param {number} fC the frame count. */ this.setFrameCount = function (fC) { this.frameCount_ = fC; }; /** * Getter for rippleElement_. * @return {Element} the ripple element. */ this.getRippleElement = function () { return this.rippleElement_; }; /** * Sets the ripple X and Y coordinates. * @param {number} newX the new X coordinate * @param {number} newY the new Y coordinate */ this.setRippleXY = function (newX, newY) { this.x_ = newX; this.y_ = newY; }; /** * Sets the ripple styles. * @param {boolean} start whether or not this is the start frame. */ this.setRippleStyles = function (start) { if (this.rippleElement_ !== null) { var transformString; var scale; var size; var offset = 'translate(' + this.x_ + 'px, ' + this.y_ + 'px)'; if (start) { scale = this.Constant_.INITIAL_SCALE; size = this.Constant_.INITIAL_SIZE; } else { scale = this.Constant_.FINAL_SCALE; size = this.rippleSize_ + 'px'; if (recentering) { offset = 'translate(' + this.boundWidth / 2 + 'px, ' + this.boundHeight / 2 + 'px)'; } } transformString = 'translate(-50%, -50%) ' + offset + scale; this.rippleElement_.style.webkitTransform = transformString; this.rippleElement_.style.msTransform = transformString; this.rippleElement_.style.transform = transformString; if (start) { this.rippleElement_.classList.remove(this.CssClasses_.IS_ANIMATING); } else { this.rippleElement_.classList.add(this.CssClasses_.IS_ANIMATING); } } }; /** * Handles an animation frame. */ this.animFrameHandler = function () { if (this.frameCount_-- > 0) { window.requestAnimationFrame(this.animFrameHandler.bind(this)); } else { this.setRippleStyles(false); } }; } } }; // The component registers itself. It can assume componentHandler is available // in the global scope. componentHandler.register({ constructor: MaterialRipple, classAsString: 'MaterialRipple', cssClass: 'mdl-js-ripple-effect', widget: false }); }());