6 * Copyright 2015 Google Inc. All Rights Reserved.
8 * Licensed under the Apache License, Version 2.0 (the "License");
9 * you may not use this file except in compliance with the License.
10 * You may obtain a copy of the License at
12 * http://www.apache.org/licenses/LICENSE-2.0
14 * Unless required by applicable law or agreed to in writing, software
15 * distributed under the License is distributed on an "AS IS" BASIS,
16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 * See the License for the specific language governing permissions and
18 * limitations under the License.
22 * A component handler interface using the revealing module design pattern.
23 * More details on this design pattern here:
24 * https://github.com/jasonmayes/mdl-component-design-pattern
26 * @author Jason Mayes.
28 /* exported componentHandler */
30 // Pre-defining the componentHandler interface, for closure documentation and
31 // static verification.
32 var componentHandler = {
34 * Searches existing DOM for elements of our component type and upgrades them
35 * if they have not already been upgraded.
37 * @param {string=} optJsClass the programatic name of the element class we
38 * need to create a new instance of.
39 * @param {string=} optCssClass the name of the CSS class elements of this
42 upgradeDom: function (optJsClass, optCssClass) {
45 * Upgrades a specific element rather than all in the DOM.
47 * @param {!Element} element The element we wish to upgrade.
48 * @param {string=} optJsClass Optional name of the class we want to upgrade
51 upgradeElement: function (element, optJsClass) {
54 * Upgrades a specific list of elements rather than all in the DOM.
56 * @param {!Element|!Array<!Element>|!NodeList|!HTMLCollection} elements
57 * The elements we wish to upgrade.
59 upgradeElements: function (elements) {
62 * Upgrades all registered components found in the current DOM. This is
63 * automatically called on window load.
65 upgradeAllRegistered: function () {
68 * Allows user to be alerted to any upgrades that are performed for a given
71 * @param {string} jsClass The class name of the MDL component we wish
72 * to hook into for any upgrades performed.
73 * @param {function(!HTMLElement)} callback The function to call upon an
74 * upgrade. This function should expect 1 parameter - the HTMLElement which
77 registerUpgradedCallback: function (jsClass, callback) {
80 * Registers a class for future use and attempts to upgrade existing DOM.
82 * @param {componentHandler.ComponentConfigPublic} config the registration configuration
84 register: function (config) {
87 * Downgrade either a given node, an array of nodes, or a NodeList.
89 * @param {!Node|!Array<!Node>|!NodeList} nodes
91 downgradeElements: function (nodes) {
95 componentHandler = (function () {
98 /** @type {!Array<componentHandler.ComponentConfig>} */
99 var registeredComponents_ = [];
101 /** @type {!Array<componentHandler.Component>} */
102 var createdComponents_ = [];
104 var componentConfigProperty_ = 'mdlComponentConfigInternal_';
107 * Searches registered components for a class we are interested in using.
108 * Optionally replaces a match with passed object if specified.
110 * @param {string} name The name of a class we want to use.
111 * @param {componentHandler.ComponentConfig=} optReplace Optional object to replace match with.
112 * @return {!Object|boolean}
115 function findRegisteredClass_(name, optReplace) {
116 for (var i = 0; i < registeredComponents_.length; i++) {
117 if (registeredComponents_[i].className === name) {
118 if (typeof optReplace !== 'undefined') {
119 registeredComponents_[i] = optReplace;
121 return registeredComponents_[i];
128 * Returns an array of the classNames of the upgraded classes on the element.
130 * @param {!Element} element The element to fetch data from.
131 * @return {!Array<string>}
134 function getUpgradedListOfElement_(element) {
135 var dataUpgraded = element.getAttribute('data-upgraded');
136 // Use `['']` as default value to conform the `,name,name...` style.
137 return dataUpgraded === null ? [''] : dataUpgraded.split(',');
141 * Returns true if the given element has already been upgraded for the given
144 * @param {!Element} element The element we want to check.
145 * @param {string} jsClass The class to check for.
149 function isElementUpgraded_(element, jsClass) {
150 var upgradedList = getUpgradedListOfElement_(element);
151 return upgradedList.indexOf(jsClass) !== -1;
155 * Create an event object.
157 * @param {string} eventType The type name of the event.
158 * @param {boolean} bubbles Whether the event should bubble up the DOM.
159 * @param {boolean} cancelable Whether the event can be canceled.
162 function createEvent_(eventType, bubbles, cancelable) {
163 if ('CustomEvent' in window && typeof window.CustomEvent === 'function') {
164 return new CustomEvent(eventType, {
166 cancelable: cancelable
169 var ev = document.createEvent('Events');
170 ev.initEvent(eventType, bubbles, cancelable);
176 * Searches existing DOM for elements of our component type and upgrades them
177 * if they have not already been upgraded.
179 * @param {string=} optJsClass the programatic name of the element class we
180 * need to create a new instance of.
181 * @param {string=} optCssClass the name of the CSS class elements of this
184 function upgradeDomInternal(optJsClass, optCssClass) {
185 if (typeof optJsClass === 'undefined' &&
186 typeof optCssClass === 'undefined') {
187 for (var i = 0; i < registeredComponents_.length; i++) {
188 upgradeDomInternal(registeredComponents_[i].className,
189 registeredComponents_[i].cssClass);
192 var jsClass = /** @type {string} */ (optJsClass);
193 if (typeof optCssClass === 'undefined') {
194 var registeredClass = findRegisteredClass_(jsClass);
195 if (registeredClass) {
196 optCssClass = registeredClass.cssClass;
200 var elements = document.querySelectorAll('.' + optCssClass);
201 for (var n = 0; n < elements.length; n++) {
202 upgradeElementInternal(elements[n], jsClass);
208 * Upgrades a specific element rather than all in the DOM.
210 * @param {!Element} element The element we wish to upgrade.
211 * @param {string=} optJsClass Optional name of the class we want to upgrade
214 function upgradeElementInternal(element, optJsClass) {
215 // Verify argument type.
216 if (!(typeof element === 'object' && element instanceof Element)) {
217 throw new Error('Invalid argument provided to upgrade MDL element.');
219 // Allow upgrade to be canceled by canceling emitted event.
220 var upgradingEv = createEvent_('mdl-componentupgrading', true, true);
221 element.dispatchEvent(upgradingEv);
222 if (upgradingEv.defaultPrevented) {
226 var upgradedList = getUpgradedListOfElement_(element);
227 var classesToUpgrade = [];
228 // If jsClass is not provided scan the registered components to find the
229 // ones matching the element's CSS classList.
231 var classList = element.classList;
232 registeredComponents_.forEach(function (component) {
233 // Match CSS & Not to be upgraded & Not upgraded.
234 if (classList.contains(component.cssClass) &&
235 classesToUpgrade.indexOf(component) === -1 &&
236 !isElementUpgraded_(element, component.className)) {
237 classesToUpgrade.push(component);
240 } else if (!isElementUpgraded_(element, optJsClass)) {
241 classesToUpgrade.push(findRegisteredClass_(optJsClass));
244 // Upgrade the element for each classes.
245 for (var i = 0, n = classesToUpgrade.length, registeredClass; i < n; i++) {
246 registeredClass = classesToUpgrade[i];
247 if (registeredClass) {
248 // Mark element as upgraded.
249 upgradedList.push(registeredClass.className);
250 element.setAttribute('data-upgraded', upgradedList.join(','));
251 var instance = new registeredClass.classConstructor(element);
252 instance[componentConfigProperty_] = registeredClass;
253 createdComponents_.push(instance);
254 // Call any callbacks the user has registered with this component type.
255 for (var j = 0, m = registeredClass.callbacks.length; j < m; j++) {
256 registeredClass.callbacks[j](element);
259 if (registeredClass.widget) {
260 // Assign per element instance for control over API
261 element[registeredClass.className] = instance;
265 'Unable to find a registered component for the given class.');
268 var upgradedEv = createEvent_('mdl-componentupgraded', true, false);
269 element.dispatchEvent(upgradedEv);
274 * Upgrades a specific list of elements rather than all in the DOM.
276 * @param {!Element|!Array<!Element>|!NodeList|!HTMLCollection} elements
277 * The elements we wish to upgrade.
279 function upgradeElementsInternal(elements) {
280 if (!Array.isArray(elements)) {
281 if (elements instanceof Element) {
282 elements = [elements];
284 elements = Array.prototype.slice.call(elements);
287 for (var i = 0, n = elements.length, element; i < n; i++) {
288 element = elements[i];
289 if (element instanceof HTMLElement) {
290 upgradeElementInternal(element);
291 if (element.children.length > 0) {
292 upgradeElementsInternal(element.children);
299 * Registers a class for future use and attempts to upgrade existing DOM.
301 * @param {componentHandler.ComponentConfigPublic} config
303 function registerInternal(config) {
304 // In order to support both Closure-compiled and uncompiled code accessing
305 // this method, we need to allow for both the dot and array syntax for
306 // property access. You'll therefore see the `foo.bar || foo['bar']`
307 // pattern repeated across this method.
308 var widgetMissing = (typeof config.widget === 'undefined' &&
309 typeof config['widget'] === 'undefined');
312 if (!widgetMissing) {
313 widget = config.widget || config['widget'];
316 var newConfig = /** @type {componentHandler.ComponentConfig} */ ({
317 classConstructor: config.constructor || config['constructor'],
318 className: config.classAsString || config['classAsString'],
319 cssClass: config.cssClass || config['cssClass'],
324 registeredComponents_.forEach(function (item) {
325 if (item.cssClass === newConfig.cssClass) {
326 throw new Error('The provided cssClass has already been registered: ' + item.cssClass);
328 if (item.className === newConfig.className) {
329 throw new Error('The provided className has already been registered');
333 if (config.constructor.prototype
334 .hasOwnProperty(componentConfigProperty_)) {
336 'MDL component classes must not have ' + componentConfigProperty_ +
337 ' defined as a property.');
340 var found = findRegisteredClass_(config.classAsString, newConfig);
343 registeredComponents_.push(newConfig);
348 * Allows user to be alerted to any upgrades that are performed for a given
351 * @param {string} jsClass The class name of the MDL component we wish
352 * to hook into for any upgrades performed.
353 * @param {function(!HTMLElement)} callback The function to call upon an
354 * upgrade. This function should expect 1 parameter - the HTMLElement which
357 function registerUpgradedCallbackInternal(jsClass, callback) {
358 var regClass = findRegisteredClass_(jsClass);
360 regClass.callbacks.push(callback);
365 * Upgrades all registered components found in the current DOM. This is
366 * automatically called on window load.
368 function upgradeAllRegisteredInternal() {
369 for (var n = 0; n < registeredComponents_.length; n++) {
370 upgradeDomInternal(registeredComponents_[n].className);
375 * Check the component for the downgrade method.
377 * Remove component from createdComponents list.
379 * @param {?componentHandler.Component} component
381 function deconstructComponentInternal(component) {
383 var componentIndex = createdComponents_.indexOf(component);
384 createdComponents_.splice(componentIndex, 1);
386 var upgrades = component.element_.getAttribute('data-upgraded').split(',');
387 var componentPlace = upgrades.indexOf(component[componentConfigProperty_].classAsString);
388 upgrades.splice(componentPlace, 1);
389 component.element_.setAttribute('data-upgraded', upgrades.join(','));
391 var ev = createEvent_('mdl-componentdowngraded', true, false);
392 component.element_.dispatchEvent(ev);
397 * Downgrade either a given node, an array of nodes, or a NodeList.
399 * @param {!Node|!Array<!Node>|!NodeList} nodes
401 function downgradeNodesInternal(nodes) {
403 * Auxiliary function to downgrade a single node.
404 * @param {!Node} node the node to be downgraded
406 var downgradeNode = function (node) {
407 createdComponents_.filter(function (item) {
408 return item.element_ === node;
409 }).forEach(deconstructComponentInternal);
411 if (nodes instanceof Array || nodes instanceof NodeList) {
412 for (var n = 0; n < nodes.length; n++) {
413 downgradeNode(nodes[n]);
415 } else if (nodes instanceof Node) {
416 downgradeNode(nodes);
418 throw new Error('Invalid argument provided to downgrade MDL nodes.');
422 // Now return the functions that should be made public with their publicly
425 upgradeDom: upgradeDomInternal,
426 upgradeElement: upgradeElementInternal,
427 upgradeElements: upgradeElementsInternal,
428 upgradeAllRegistered: upgradeAllRegisteredInternal,
429 registerUpgradedCallback: registerUpgradedCallbackInternal,
430 register: registerInternal,
431 downgradeElements: downgradeNodesInternal
436 * Describes the type of a registered component type managed by
437 * componentHandler. Provided for benefit of the Closure compiler.
440 * constructor: Function,
441 * classAsString: string,
443 * widget: (string|boolean|undefined)
446 componentHandler.ComponentConfigPublic; // jshint ignore:line
449 * Describes the type of a registered component type managed by
450 * componentHandler. Provided for benefit of the Closure compiler.
453 * constructor: !Function,
456 * widget: (string|boolean),
457 * callbacks: !Array<function(!HTMLElement)>
460 componentHandler.ComponentConfig; // jshint ignore:line
463 * Created component (i.e., upgraded element) type as managed by
464 * componentHandler. Provided for benefit of the Closure compiler.
467 * element_: !HTMLElement,
469 * classAsString: string,
474 componentHandler.Component; // jshint ignore:line
476 // Export all symbols, for the benefit of Closure compiler.
477 // No effect on uncompiled code.
478 componentHandler['upgradeDom'] = componentHandler.upgradeDom;
479 componentHandler['upgradeElement'] = componentHandler.upgradeElement;
480 componentHandler['upgradeElements'] = componentHandler.upgradeElements;
481 componentHandler['upgradeAllRegistered'] =
482 componentHandler.upgradeAllRegistered;
483 componentHandler['registerUpgradedCallback'] =
484 componentHandler.registerUpgradedCallback;
485 componentHandler['register'] = componentHandler.register;
486 componentHandler['downgradeElements'] = componentHandler.downgradeElements;
487 window.componentHandler = componentHandler;
488 window['componentHandler'] = componentHandler;
490 window.addEventListener('load', function () {
494 * Performs a "Cutting the mustard" test. If the browser supports the features
495 * tested, adds a mdl-js class to the <html> element. It then upgrades all MDL
496 * components requiring JavaScript.
498 if ('classList' in document.createElement('div') &&
499 'querySelector' in document &&
500 'addEventListener' in window && Array.prototype.forEach) {
501 document.documentElement.classList.add('mdl-js');
502 componentHandler.upgradeAllRegistered();
505 * Dummy function to avoid JS errors.
507 componentHandler.upgradeElement = function () {
510 * Dummy function to avoid JS errors.
512 componentHandler.register = function () {
517 // Source: https://github.com/darius/requestAnimationFrame/blob/master/requestAnimationFrame.js
518 // Adapted from https://gist.github.com/paulirish/1579671 which derived from
519 // http://paulirish.com/2011/requestanimationframe-for-smart-animating/
520 // http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating
521 // requestAnimationFrame polyfill by Erik Möller.
522 // Fixes from Paul Irish, Tino Zijdel, Andrew Mao, Klemen Slavič, Darius Bacon
527 * @return {number} the current Date
529 Date.now = function () {
530 return new Date().getTime();
532 Date['now'] = Date.now;
538 for (var i = 0; i < vendors.length && !window.requestAnimationFrame; ++i) {
540 window.requestAnimationFrame = window[vp + 'RequestAnimationFrame'];
541 window.cancelAnimationFrame = window[vp + 'CancelAnimationFrame'] || window[vp + 'CancelRequestAnimationFrame'];
542 window['requestAnimationFrame'] = window.requestAnimationFrame;
543 window['cancelAnimationFrame'] = window.cancelAnimationFrame;
545 if (/iP(ad|hone|od).*OS 6/.test(window.navigator.userAgent) || !window.requestAnimationFrame || !window.cancelAnimationFrame) {
548 * requestAnimationFrame polyfill.
549 * @param {!Function} callback the callback function.
551 window.requestAnimationFrame = function (callback) {
552 var now = Date.now();
553 var nextTime = Math.max(lastTime + 16, now);
554 return setTimeout(function () {
555 callback(lastTime = nextTime);
558 window.cancelAnimationFrame = clearTimeout;
559 window['requestAnimationFrame'] = window.requestAnimationFrame;
560 window['cancelAnimationFrame'] = window.cancelAnimationFrame;
564 * Copyright 2015 Google Inc. All Rights Reserved.
566 * Licensed under the Apache License, Version 2.0 (the "License");
567 * you may not use this file except in compliance with the License.
568 * You may obtain a copy of the License at
570 * http://www.apache.org/licenses/LICENSE-2.0
572 * Unless required by applicable law or agreed to in writing, software
573 * distributed under the License is distributed on an "AS IS" BASIS,
574 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
575 * See the License for the specific language governing permissions and
576 * limitations under the License.
579 * Class constructor for Button MDL component.
580 * Implements MDL component design pattern defined at:
581 * https://github.com/jasonmayes/mdl-component-design-pattern
583 * @param {HTMLElement} element The element that will be upgraded.
585 var MaterialButton = function MaterialButton(element) {
586 this.element_ = element;
587 // Initialize instance.
590 window['MaterialButton'] = MaterialButton;
592 * Store constants in one place so they can be updated easily.
594 * @enum {string | number}
597 MaterialButton.prototype.Constant_ = {};
599 * Store strings for class names defined by this component that are used in
600 * JavaScript. This allows us to simply change it in one place should we
601 * decide to modify at a later date.
606 MaterialButton.prototype.CssClasses_ = {
607 RIPPLE_EFFECT: 'mdl-js-ripple-effect',
608 RIPPLE_CONTAINER: 'mdl-button__ripple-container',
612 * Handle blur of element.
614 * @param {Event} event The event that fired.
617 MaterialButton.prototype.blurHandler_ = function (event) {
619 this.element_.blur();
628 MaterialButton.prototype.disable = function () {
629 this.element_.disabled = true;
631 MaterialButton.prototype['disable'] = MaterialButton.prototype.disable;
637 MaterialButton.prototype.enable = function () {
638 this.element_.disabled = false;
640 MaterialButton.prototype['enable'] = MaterialButton.prototype.enable;
642 * Initialize element.
644 MaterialButton.prototype.init = function () {
646 if (this.element_.classList.contains(this.CssClasses_.RIPPLE_EFFECT)) {
647 var rippleContainer = document.createElement('span');
648 rippleContainer.classList.add(this.CssClasses_.RIPPLE_CONTAINER);
649 this.rippleElement_ = document.createElement('span');
650 this.rippleElement_.classList.add(this.CssClasses_.RIPPLE);
651 rippleContainer.appendChild(this.rippleElement_);
652 this.boundRippleBlurHandler = this.blurHandler_.bind(this);
653 this.rippleElement_.addEventListener('mouseup', this.boundRippleBlurHandler);
654 this.element_.appendChild(rippleContainer);
656 this.boundButtonBlurHandler = this.blurHandler_.bind(this);
657 this.element_.addEventListener('mouseup', this.boundButtonBlurHandler);
658 this.element_.addEventListener('mouseleave', this.boundButtonBlurHandler);
661 // The component registers itself. It can assume componentHandler is available
662 // in the global scope.
663 componentHandler.register({
664 constructor: MaterialButton,
665 classAsString: 'MaterialButton',
666 cssClass: 'mdl-js-button',
671 * Copyright 2015 Google Inc. All Rights Reserved.
673 * Licensed under the Apache License, Version 2.0 (the "License");
674 * you may not use this file except in compliance with the License.
675 * You may obtain a copy of the License at
677 * http://www.apache.org/licenses/LICENSE-2.0
679 * Unless required by applicable law or agreed to in writing, software
680 * distributed under the License is distributed on an "AS IS" BASIS,
681 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
682 * See the License for the specific language governing permissions and
683 * limitations under the License.
686 * Class constructor for Checkbox MDL component.
687 * Implements MDL component design pattern defined at:
688 * https://github.com/jasonmayes/mdl-component-design-pattern
691 * @param {HTMLElement} element The element that will be upgraded.
693 var MaterialCheckbox = function MaterialCheckbox(element) {
694 this.element_ = element;
695 // Initialize instance.
698 window['MaterialCheckbox'] = MaterialCheckbox;
700 * Store constants in one place so they can be updated easily.
702 * @enum {string | number}
705 MaterialCheckbox.prototype.Constant_ = {TINY_TIMEOUT: 0.001};
707 * Store strings for class names defined by this component that are used in
708 * JavaScript. This allows us to simply change it in one place should we
709 * decide to modify at a later date.
714 MaterialCheckbox.prototype.CssClasses_ = {
715 INPUT: 'mdl-checkbox__input',
716 BOX_OUTLINE: 'mdl-checkbox__box-outline',
717 FOCUS_HELPER: 'mdl-checkbox__focus-helper',
718 TICK_OUTLINE: 'mdl-checkbox__tick-outline',
719 RIPPLE_EFFECT: 'mdl-js-ripple-effect',
720 RIPPLE_IGNORE_EVENTS: 'mdl-js-ripple-effect--ignore-events',
721 RIPPLE_CONTAINER: 'mdl-checkbox__ripple-container',
722 RIPPLE_CENTER: 'mdl-ripple--center',
723 RIPPLE: 'mdl-ripple',
724 IS_FOCUSED: 'is-focused',
725 IS_DISABLED: 'is-disabled',
726 IS_CHECKED: 'is-checked',
727 IS_UPGRADED: 'is-upgraded'
730 * Handle change of state.
732 * @param {Event} event The event that fired.
735 MaterialCheckbox.prototype.onChange_ = function (event) {
736 this.updateClasses_();
739 * Handle focus of element.
741 * @param {Event} event The event that fired.
744 MaterialCheckbox.prototype.onFocus_ = function (event) {
745 this.element_.classList.add(this.CssClasses_.IS_FOCUSED);
748 * Handle lost focus of element.
750 * @param {Event} event The event that fired.
753 MaterialCheckbox.prototype.onBlur_ = function (event) {
754 this.element_.classList.remove(this.CssClasses_.IS_FOCUSED);
759 * @param {Event} event The event that fired.
762 MaterialCheckbox.prototype.onMouseUp_ = function (event) {
766 * Handle class updates.
770 MaterialCheckbox.prototype.updateClasses_ = function () {
771 this.checkDisabled();
772 this.checkToggleState();
779 MaterialCheckbox.prototype.blur_ = function () {
780 // TODO: figure out why there's a focus event being fired after our blur,
781 // so that we can avoid this hack.
782 window.setTimeout(function () {
783 this.inputElement_.blur();
784 }.bind(this), this.Constant_.TINY_TIMEOUT);
788 * Check the inputs toggle state and update display.
792 MaterialCheckbox.prototype.checkToggleState = function () {
793 if (this.inputElement_.checked) {
794 this.element_.classList.add(this.CssClasses_.IS_CHECKED);
796 this.element_.classList.remove(this.CssClasses_.IS_CHECKED);
799 MaterialCheckbox.prototype['checkToggleState'] = MaterialCheckbox.prototype.checkToggleState;
801 * Check the inputs disabled state and update display.
805 MaterialCheckbox.prototype.checkDisabled = function () {
806 if (this.inputElement_.disabled) {
807 this.element_.classList.add(this.CssClasses_.IS_DISABLED);
809 this.element_.classList.remove(this.CssClasses_.IS_DISABLED);
812 MaterialCheckbox.prototype['checkDisabled'] = MaterialCheckbox.prototype.checkDisabled;
818 MaterialCheckbox.prototype.disable = function () {
819 this.inputElement_.disabled = true;
820 this.updateClasses_();
822 MaterialCheckbox.prototype['disable'] = MaterialCheckbox.prototype.disable;
828 MaterialCheckbox.prototype.enable = function () {
829 this.inputElement_.disabled = false;
830 this.updateClasses_();
832 MaterialCheckbox.prototype['enable'] = MaterialCheckbox.prototype.enable;
838 MaterialCheckbox.prototype.check = function () {
839 this.inputElement_.checked = true;
840 this.updateClasses_();
842 MaterialCheckbox.prototype['check'] = MaterialCheckbox.prototype.check;
848 MaterialCheckbox.prototype.uncheck = function () {
849 this.inputElement_.checked = false;
850 this.updateClasses_();
852 MaterialCheckbox.prototype['uncheck'] = MaterialCheckbox.prototype.uncheck;
854 * Initialize element.
856 MaterialCheckbox.prototype.init = function () {
858 this.inputElement_ = this.element_.querySelector('.' + this.CssClasses_.INPUT);
859 var boxOutline = document.createElement('span');
860 boxOutline.classList.add(this.CssClasses_.BOX_OUTLINE);
861 var tickContainer = document.createElement('span');
862 tickContainer.classList.add(this.CssClasses_.FOCUS_HELPER);
863 var tickOutline = document.createElement('span');
864 tickOutline.classList.add(this.CssClasses_.TICK_OUTLINE);
865 boxOutline.appendChild(tickOutline);
866 this.element_.appendChild(tickContainer);
867 this.element_.appendChild(boxOutline);
868 if (this.element_.classList.contains(this.CssClasses_.RIPPLE_EFFECT)) {
869 this.element_.classList.add(this.CssClasses_.RIPPLE_IGNORE_EVENTS);
870 this.rippleContainerElement_ = document.createElement('span');
871 this.rippleContainerElement_.classList.add(this.CssClasses_.RIPPLE_CONTAINER);
872 this.rippleContainerElement_.classList.add(this.CssClasses_.RIPPLE_EFFECT);
873 this.rippleContainerElement_.classList.add(this.CssClasses_.RIPPLE_CENTER);
874 this.boundRippleMouseUp = this.onMouseUp_.bind(this);
875 this.rippleContainerElement_.addEventListener('mouseup', this.boundRippleMouseUp);
876 var ripple = document.createElement('span');
877 ripple.classList.add(this.CssClasses_.RIPPLE);
878 this.rippleContainerElement_.appendChild(ripple);
879 this.element_.appendChild(this.rippleContainerElement_);
881 this.boundInputOnChange = this.onChange_.bind(this);
882 this.boundInputOnFocus = this.onFocus_.bind(this);
883 this.boundInputOnBlur = this.onBlur_.bind(this);
884 this.boundElementMouseUp = this.onMouseUp_.bind(this);
885 this.inputElement_.addEventListener('change', this.boundInputOnChange);
886 this.inputElement_.addEventListener('focus', this.boundInputOnFocus);
887 this.inputElement_.addEventListener('blur', this.boundInputOnBlur);
888 this.element_.addEventListener('mouseup', this.boundElementMouseUp);
889 this.updateClasses_();
890 this.element_.classList.add(this.CssClasses_.IS_UPGRADED);
893 // The component registers itself. It can assume componentHandler is available
894 // in the global scope.
895 componentHandler.register({
896 constructor: MaterialCheckbox,
897 classAsString: 'MaterialCheckbox',
898 cssClass: 'mdl-js-checkbox',
903 * Copyright 2015 Google Inc. All Rights Reserved.
905 * Licensed under the Apache License, Version 2.0 (the "License");
906 * you may not use this file except in compliance with the License.
907 * You may obtain a copy of the License at
909 * http://www.apache.org/licenses/LICENSE-2.0
911 * Unless required by applicable law or agreed to in writing, software
912 * distributed under the License is distributed on an "AS IS" BASIS,
913 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
914 * See the License for the specific language governing permissions and
915 * limitations under the License.
918 * Class constructor for icon toggle MDL component.
919 * Implements MDL component design pattern defined at:
920 * https://github.com/jasonmayes/mdl-component-design-pattern
923 * @param {HTMLElement} element The element that will be upgraded.
925 var MaterialIconToggle = function MaterialIconToggle(element) {
926 this.element_ = element;
927 // Initialize instance.
930 window['MaterialIconToggle'] = MaterialIconToggle;
932 * Store constants in one place so they can be updated easily.
934 * @enum {string | number}
937 MaterialIconToggle.prototype.Constant_ = {TINY_TIMEOUT: 0.001};
939 * Store strings for class names defined by this component that are used in
940 * JavaScript. This allows us to simply change it in one place should we
941 * decide to modify at a later date.
946 MaterialIconToggle.prototype.CssClasses_ = {
947 INPUT: 'mdl-icon-toggle__input',
948 JS_RIPPLE_EFFECT: 'mdl-js-ripple-effect',
949 RIPPLE_IGNORE_EVENTS: 'mdl-js-ripple-effect--ignore-events',
950 RIPPLE_CONTAINER: 'mdl-icon-toggle__ripple-container',
951 RIPPLE_CENTER: 'mdl-ripple--center',
952 RIPPLE: 'mdl-ripple',
953 IS_FOCUSED: 'is-focused',
954 IS_DISABLED: 'is-disabled',
955 IS_CHECKED: 'is-checked'
958 * Handle change of state.
960 * @param {Event} event The event that fired.
963 MaterialIconToggle.prototype.onChange_ = function (event) {
964 this.updateClasses_();
967 * Handle focus of element.
969 * @param {Event} event The event that fired.
972 MaterialIconToggle.prototype.onFocus_ = function (event) {
973 this.element_.classList.add(this.CssClasses_.IS_FOCUSED);
976 * Handle lost focus of element.
978 * @param {Event} event The event that fired.
981 MaterialIconToggle.prototype.onBlur_ = function (event) {
982 this.element_.classList.remove(this.CssClasses_.IS_FOCUSED);
987 * @param {Event} event The event that fired.
990 MaterialIconToggle.prototype.onMouseUp_ = function (event) {
994 * Handle class updates.
998 MaterialIconToggle.prototype.updateClasses_ = function () {
999 this.checkDisabled();
1000 this.checkToggleState();
1007 MaterialIconToggle.prototype.blur_ = function () {
1008 // TODO: figure out why there's a focus event being fired after our blur,
1009 // so that we can avoid this hack.
1010 window.setTimeout(function () {
1011 this.inputElement_.blur();
1012 }.bind(this), this.Constant_.TINY_TIMEOUT);
1016 * Check the inputs toggle state and update display.
1020 MaterialIconToggle.prototype.checkToggleState = function () {
1021 if (this.inputElement_.checked) {
1022 this.element_.classList.add(this.CssClasses_.IS_CHECKED);
1024 this.element_.classList.remove(this.CssClasses_.IS_CHECKED);
1027 MaterialIconToggle.prototype['checkToggleState'] = MaterialIconToggle.prototype.checkToggleState;
1029 * Check the inputs disabled state and update display.
1033 MaterialIconToggle.prototype.checkDisabled = function () {
1034 if (this.inputElement_.disabled) {
1035 this.element_.classList.add(this.CssClasses_.IS_DISABLED);
1037 this.element_.classList.remove(this.CssClasses_.IS_DISABLED);
1040 MaterialIconToggle.prototype['checkDisabled'] = MaterialIconToggle.prototype.checkDisabled;
1042 * Disable icon toggle.
1046 MaterialIconToggle.prototype.disable = function () {
1047 this.inputElement_.disabled = true;
1048 this.updateClasses_();
1050 MaterialIconToggle.prototype['disable'] = MaterialIconToggle.prototype.disable;
1052 * Enable icon toggle.
1056 MaterialIconToggle.prototype.enable = function () {
1057 this.inputElement_.disabled = false;
1058 this.updateClasses_();
1060 MaterialIconToggle.prototype['enable'] = MaterialIconToggle.prototype.enable;
1062 * Check icon toggle.
1066 MaterialIconToggle.prototype.check = function () {
1067 this.inputElement_.checked = true;
1068 this.updateClasses_();
1070 MaterialIconToggle.prototype['check'] = MaterialIconToggle.prototype.check;
1072 * Uncheck icon toggle.
1076 MaterialIconToggle.prototype.uncheck = function () {
1077 this.inputElement_.checked = false;
1078 this.updateClasses_();
1080 MaterialIconToggle.prototype['uncheck'] = MaterialIconToggle.prototype.uncheck;
1082 * Initialize element.
1084 MaterialIconToggle.prototype.init = function () {
1085 if (this.element_) {
1086 this.inputElement_ = this.element_.querySelector('.' + this.CssClasses_.INPUT);
1087 if (this.element_.classList.contains(this.CssClasses_.JS_RIPPLE_EFFECT)) {
1088 this.element_.classList.add(this.CssClasses_.RIPPLE_IGNORE_EVENTS);
1089 this.rippleContainerElement_ = document.createElement('span');
1090 this.rippleContainerElement_.classList.add(this.CssClasses_.RIPPLE_CONTAINER);
1091 this.rippleContainerElement_.classList.add(this.CssClasses_.JS_RIPPLE_EFFECT);
1092 this.rippleContainerElement_.classList.add(this.CssClasses_.RIPPLE_CENTER);
1093 this.boundRippleMouseUp = this.onMouseUp_.bind(this);
1094 this.rippleContainerElement_.addEventListener('mouseup', this.boundRippleMouseUp);
1095 var ripple = document.createElement('span');
1096 ripple.classList.add(this.CssClasses_.RIPPLE);
1097 this.rippleContainerElement_.appendChild(ripple);
1098 this.element_.appendChild(this.rippleContainerElement_);
1100 this.boundInputOnChange = this.onChange_.bind(this);
1101 this.boundInputOnFocus = this.onFocus_.bind(this);
1102 this.boundInputOnBlur = this.onBlur_.bind(this);
1103 this.boundElementOnMouseUp = this.onMouseUp_.bind(this);
1104 this.inputElement_.addEventListener('change', this.boundInputOnChange);
1105 this.inputElement_.addEventListener('focus', this.boundInputOnFocus);
1106 this.inputElement_.addEventListener('blur', this.boundInputOnBlur);
1107 this.element_.addEventListener('mouseup', this.boundElementOnMouseUp);
1108 this.updateClasses_();
1109 this.element_.classList.add('is-upgraded');
1112 // The component registers itself. It can assume componentHandler is available
1113 // in the global scope.
1114 componentHandler.register({
1115 constructor: MaterialIconToggle,
1116 classAsString: 'MaterialIconToggle',
1117 cssClass: 'mdl-js-icon-toggle',
1122 * Copyright 2015 Google Inc. All Rights Reserved.
1124 * Licensed under the Apache License, Version 2.0 (the "License");
1125 * you may not use this file except in compliance with the License.
1126 * You may obtain a copy of the License at
1128 * http://www.apache.org/licenses/LICENSE-2.0
1130 * Unless required by applicable law or agreed to in writing, software
1131 * distributed under the License is distributed on an "AS IS" BASIS,
1132 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1133 * See the License for the specific language governing permissions and
1134 * limitations under the License.
1137 * Class constructor for dropdown MDL component.
1138 * Implements MDL component design pattern defined at:
1139 * https://github.com/jasonmayes/mdl-component-design-pattern
1142 * @param {HTMLElement} element The element that will be upgraded.
1144 var MaterialMenu = function MaterialMenu(element) {
1145 this.element_ = element;
1146 // Initialize instance.
1149 window['MaterialMenu'] = MaterialMenu;
1151 * Store constants in one place so they can be updated easily.
1153 * @enum {string | number}
1156 MaterialMenu.prototype.Constant_ = {
1157 // Total duration of the menu animation.
1158 TRANSITION_DURATION_SECONDS: 0.3,
1159 // The fraction of the total duration we want to use for menu item animations.
1160 TRANSITION_DURATION_FRACTION: 0.8,
1161 // How long the menu stays open after choosing an option (so the user can see
1166 * Keycodes, for code readability.
1171 MaterialMenu.prototype.Keycodes_ = {
1179 * Store strings for class names defined by this component that are used in
1180 * JavaScript. This allows us to simply change it in one place should we
1181 * decide to modify at a later date.
1186 MaterialMenu.prototype.CssClasses_ = {
1187 CONTAINER: 'mdl-menu__container',
1188 OUTLINE: 'mdl-menu__outline',
1189 ITEM: 'mdl-menu__item',
1190 ITEM_RIPPLE_CONTAINER: 'mdl-menu__item-ripple-container',
1191 RIPPLE_EFFECT: 'mdl-js-ripple-effect',
1192 RIPPLE_IGNORE_EVENTS: 'mdl-js-ripple-effect--ignore-events',
1193 RIPPLE: 'mdl-ripple',
1195 IS_UPGRADED: 'is-upgraded',
1196 IS_VISIBLE: 'is-visible',
1197 IS_ANIMATING: 'is-animating',
1198 // Alignment options
1199 BOTTOM_LEFT: 'mdl-menu--bottom-left',
1200 // This is the default.
1201 BOTTOM_RIGHT: 'mdl-menu--bottom-right',
1202 TOP_LEFT: 'mdl-menu--top-left',
1203 TOP_RIGHT: 'mdl-menu--top-right',
1204 UNALIGNED: 'mdl-menu--unaligned'
1207 * Initialize element.
1209 MaterialMenu.prototype.init = function () {
1210 if (this.element_) {
1211 // Create container for the menu.
1212 var container = document.createElement('div');
1213 container.classList.add(this.CssClasses_.CONTAINER);
1214 this.element_.parentElement.insertBefore(container, this.element_);
1215 this.element_.parentElement.removeChild(this.element_);
1216 container.appendChild(this.element_);
1217 this.container_ = container;
1218 // Create outline for the menu (shadow and background).
1219 var outline = document.createElement('div');
1220 outline.classList.add(this.CssClasses_.OUTLINE);
1221 this.outline_ = outline;
1222 container.insertBefore(outline, this.element_);
1223 // Find the "for" element and bind events to it.
1224 var forElId = this.element_.getAttribute('for') || this.element_.getAttribute('data-mdl-for');
1227 forEl = document.getElementById(forElId);
1229 this.forElement_ = forEl;
1230 forEl.addEventListener('click', this.handleForClick_.bind(this));
1231 forEl.addEventListener('keydown', this.handleForKeyboardEvent_.bind(this));
1234 var items = this.element_.querySelectorAll('.' + this.CssClasses_.ITEM);
1235 this.boundItemKeydown_ = this.handleItemKeyboardEvent_.bind(this);
1236 this.boundItemClick_ = this.handleItemClick_.bind(this);
1237 for (var i = 0; i < items.length; i++) {
1238 // Add a listener to each menu item.
1239 items[i].addEventListener('click', this.boundItemClick_);
1240 // Add a tab index to each menu item.
1241 items[i].tabIndex = '-1';
1242 // Add a keyboard listener to each menu item.
1243 items[i].addEventListener('keydown', this.boundItemKeydown_);
1245 // Add ripple classes to each item, if the user has enabled ripples.
1246 if (this.element_.classList.contains(this.CssClasses_.RIPPLE_EFFECT)) {
1247 this.element_.classList.add(this.CssClasses_.RIPPLE_IGNORE_EVENTS);
1248 for (i = 0; i < items.length; i++) {
1249 var item = items[i];
1250 var rippleContainer = document.createElement('span');
1251 rippleContainer.classList.add(this.CssClasses_.ITEM_RIPPLE_CONTAINER);
1252 var ripple = document.createElement('span');
1253 ripple.classList.add(this.CssClasses_.RIPPLE);
1254 rippleContainer.appendChild(ripple);
1255 item.appendChild(rippleContainer);
1256 item.classList.add(this.CssClasses_.RIPPLE_EFFECT);
1259 // Copy alignment classes to the container, so the outline can use them.
1260 if (this.element_.classList.contains(this.CssClasses_.BOTTOM_LEFT)) {
1261 this.outline_.classList.add(this.CssClasses_.BOTTOM_LEFT);
1263 if (this.element_.classList.contains(this.CssClasses_.BOTTOM_RIGHT)) {
1264 this.outline_.classList.add(this.CssClasses_.BOTTOM_RIGHT);
1266 if (this.element_.classList.contains(this.CssClasses_.TOP_LEFT)) {
1267 this.outline_.classList.add(this.CssClasses_.TOP_LEFT);
1269 if (this.element_.classList.contains(this.CssClasses_.TOP_RIGHT)) {
1270 this.outline_.classList.add(this.CssClasses_.TOP_RIGHT);
1272 if (this.element_.classList.contains(this.CssClasses_.UNALIGNED)) {
1273 this.outline_.classList.add(this.CssClasses_.UNALIGNED);
1275 container.classList.add(this.CssClasses_.IS_UPGRADED);
1279 * Handles a click on the "for" element, by positioning the menu and then
1282 * @param {Event} evt The event that fired.
1285 MaterialMenu.prototype.handleForClick_ = function (evt) {
1286 if (this.element_ && this.forElement_) {
1287 var rect = this.forElement_.getBoundingClientRect();
1288 var forRect = this.forElement_.parentElement.getBoundingClientRect();
1289 if (this.element_.classList.contains(this.CssClasses_.UNALIGNED)) {
1290 } else if (this.element_.classList.contains(this.CssClasses_.BOTTOM_RIGHT)) {
1291 // Position below the "for" element, aligned to its right.
1292 this.container_.style.right = forRect.right - rect.right + 'px';
1293 this.container_.style.top = this.forElement_.offsetTop + this.forElement_.offsetHeight + 'px';
1294 } else if (this.element_.classList.contains(this.CssClasses_.TOP_LEFT)) {
1295 // Position above the "for" element, aligned to its left.
1296 this.container_.style.left = this.forElement_.offsetLeft + 'px';
1297 this.container_.style.bottom = forRect.bottom - rect.top + 'px';
1298 } else if (this.element_.classList.contains(this.CssClasses_.TOP_RIGHT)) {
1299 // Position above the "for" element, aligned to its right.
1300 this.container_.style.right = forRect.right - rect.right + 'px';
1301 this.container_.style.bottom = forRect.bottom - rect.top + 'px';
1303 // Default: position below the "for" element, aligned to its left.
1304 this.container_.style.left = this.forElement_.offsetLeft + 'px';
1305 this.container_.style.top = this.forElement_.offsetTop + this.forElement_.offsetHeight + 'px';
1311 * Handles a keyboard event on the "for" element.
1313 * @param {Event} evt The event that fired.
1316 MaterialMenu.prototype.handleForKeyboardEvent_ = function (evt) {
1317 if (this.element_ && this.container_ && this.forElement_) {
1318 var items = this.element_.querySelectorAll('.' + this.CssClasses_.ITEM + ':not([disabled])');
1319 if (items && items.length > 0 && this.container_.classList.contains(this.CssClasses_.IS_VISIBLE)) {
1320 if (evt.keyCode === this.Keycodes_.UP_ARROW) {
1321 evt.preventDefault();
1322 items[items.length - 1].focus();
1323 } else if (evt.keyCode === this.Keycodes_.DOWN_ARROW) {
1324 evt.preventDefault();
1331 * Handles a keyboard event on an item.
1333 * @param {Event} evt The event that fired.
1336 MaterialMenu.prototype.handleItemKeyboardEvent_ = function (evt) {
1337 if (this.element_ && this.container_) {
1338 var items = this.element_.querySelectorAll('.' + this.CssClasses_.ITEM + ':not([disabled])');
1339 if (items && items.length > 0 && this.container_.classList.contains(this.CssClasses_.IS_VISIBLE)) {
1340 var currentIndex = Array.prototype.slice.call(items).indexOf(evt.target);
1341 if (evt.keyCode === this.Keycodes_.UP_ARROW) {
1342 evt.preventDefault();
1343 if (currentIndex > 0) {
1344 items[currentIndex - 1].focus();
1346 items[items.length - 1].focus();
1348 } else if (evt.keyCode === this.Keycodes_.DOWN_ARROW) {
1349 evt.preventDefault();
1350 if (items.length > currentIndex + 1) {
1351 items[currentIndex + 1].focus();
1355 } else if (evt.keyCode === this.Keycodes_.SPACE || evt.keyCode === this.Keycodes_.ENTER) {
1356 evt.preventDefault();
1357 // Send mousedown and mouseup to trigger ripple.
1358 var e = new MouseEvent('mousedown');
1359 evt.target.dispatchEvent(e);
1360 e = new MouseEvent('mouseup');
1361 evt.target.dispatchEvent(e);
1364 } else if (evt.keyCode === this.Keycodes_.ESCAPE) {
1365 evt.preventDefault();
1372 * Handles a click event on an item.
1374 * @param {Event} evt The event that fired.
1377 MaterialMenu.prototype.handleItemClick_ = function (evt) {
1378 if (evt.target.hasAttribute('disabled')) {
1379 evt.stopPropagation();
1381 // Wait some time before closing menu, so the user can see the ripple.
1382 this.closing_ = true;
1383 window.setTimeout(function (evt) {
1385 this.closing_ = false;
1386 }.bind(this), this.Constant_.CLOSE_TIMEOUT);
1390 * Calculates the initial clip (for opening the menu) or final clip (for closing
1391 * it), and applies it. This allows us to animate from or to the correct point,
1392 * that is, the point it's aligned to in the "for" element.
1394 * @param {number} height Height of the clip rectangle
1395 * @param {number} width Width of the clip rectangle
1398 MaterialMenu.prototype.applyClip_ = function (height, width) {
1399 if (this.element_.classList.contains(this.CssClasses_.UNALIGNED)) {
1401 this.element_.style.clip = '';
1402 } else if (this.element_.classList.contains(this.CssClasses_.BOTTOM_RIGHT)) {
1403 // Clip to the top right corner of the menu.
1404 this.element_.style.clip = 'rect(0 ' + width + 'px ' + '0 ' + width + 'px)';
1405 } else if (this.element_.classList.contains(this.CssClasses_.TOP_LEFT)) {
1406 // Clip to the bottom left corner of the menu.
1407 this.element_.style.clip = 'rect(' + height + 'px 0 ' + height + 'px 0)';
1408 } else if (this.element_.classList.contains(this.CssClasses_.TOP_RIGHT)) {
1409 // Clip to the bottom right corner of the menu.
1410 this.element_.style.clip = 'rect(' + height + 'px ' + width + 'px ' + height + 'px ' + width + 'px)';
1412 // Default: do not clip (same as clipping to the top left corner).
1413 this.element_.style.clip = '';
1417 * Cleanup function to remove animation listeners.
1419 * @param {Event} evt
1422 MaterialMenu.prototype.removeAnimationEndListener_ = function (evt) {
1423 evt.target.classList.remove(MaterialMenu.prototype.CssClasses_.IS_ANIMATING);
1426 * Adds an event listener to clean up after the animation ends.
1430 MaterialMenu.prototype.addAnimationEndListener_ = function () {
1431 this.element_.addEventListener('transitionend', this.removeAnimationEndListener_);
1432 this.element_.addEventListener('webkitTransitionEnd', this.removeAnimationEndListener_);
1435 * Displays the menu.
1439 MaterialMenu.prototype.show = function (evt) {
1440 if (this.element_ && this.container_ && this.outline_) {
1441 // Measure the inner element.
1442 var height = this.element_.getBoundingClientRect().height;
1443 var width = this.element_.getBoundingClientRect().width;
1444 // Apply the inner element's size to the container and outline.
1445 this.container_.style.width = width + 'px';
1446 this.container_.style.height = height + 'px';
1447 this.outline_.style.width = width + 'px';
1448 this.outline_.style.height = height + 'px';
1449 var transitionDuration = this.Constant_.TRANSITION_DURATION_SECONDS * this.Constant_.TRANSITION_DURATION_FRACTION;
1450 // Calculate transition delays for individual menu items, so that they fade
1451 // in one at a time.
1452 var items = this.element_.querySelectorAll('.' + this.CssClasses_.ITEM);
1453 for (var i = 0; i < items.length; i++) {
1454 var itemDelay = null;
1455 if (this.element_.classList.contains(this.CssClasses_.TOP_LEFT) || this.element_.classList.contains(this.CssClasses_.TOP_RIGHT)) {
1456 itemDelay = (height - items[i].offsetTop - items[i].offsetHeight) / height * transitionDuration + 's';
1458 itemDelay = items[i].offsetTop / height * transitionDuration + 's';
1460 items[i].style.transitionDelay = itemDelay;
1462 // Apply the initial clip to the text before we start animating.
1463 this.applyClip_(height, width);
1464 // Wait for the next frame, turn on animation, and apply the final clip.
1465 // Also make it visible. This triggers the transitions.
1466 window.requestAnimationFrame(function () {
1467 this.element_.classList.add(this.CssClasses_.IS_ANIMATING);
1468 this.element_.style.clip = 'rect(0 ' + width + 'px ' + height + 'px 0)';
1469 this.container_.classList.add(this.CssClasses_.IS_VISIBLE);
1471 // Clean up after the animation is complete.
1472 this.addAnimationEndListener_();
1473 // Add a click listener to the document, to close the menu.
1474 var callback = function (e) {
1475 // Check to see if the document is processing the same event that
1476 // displayed the menu in the first place. If so, do nothing.
1477 // Also check to see if the menu is in the process of closing itself, and
1478 // do nothing in that case.
1479 // Also check if the clicked element is a menu item
1480 // if so, do nothing.
1481 if (e !== evt && !this.closing_ && e.target.parentNode !== this.element_) {
1482 document.removeEventListener('click', callback);
1486 document.addEventListener('click', callback);
1489 MaterialMenu.prototype['show'] = MaterialMenu.prototype.show;
1495 MaterialMenu.prototype.hide = function () {
1496 if (this.element_ && this.container_ && this.outline_) {
1497 var items = this.element_.querySelectorAll('.' + this.CssClasses_.ITEM);
1498 // Remove all transition delays; menu items fade out concurrently.
1499 for (var i = 0; i < items.length; i++) {
1500 items[i].style.removeProperty('transition-delay');
1502 // Measure the inner element.
1503 var rect = this.element_.getBoundingClientRect();
1504 var height = rect.height;
1505 var width = rect.width;
1506 // Turn on animation, and apply the final clip. Also make invisible.
1507 // This triggers the transitions.
1508 this.element_.classList.add(this.CssClasses_.IS_ANIMATING);
1509 this.applyClip_(height, width);
1510 this.container_.classList.remove(this.CssClasses_.IS_VISIBLE);
1511 // Clean up after the animation is complete.
1512 this.addAnimationEndListener_();
1515 MaterialMenu.prototype['hide'] = MaterialMenu.prototype.hide;
1517 * Displays or hides the menu, depending on current state.
1521 MaterialMenu.prototype.toggle = function (evt) {
1522 if (this.container_.classList.contains(this.CssClasses_.IS_VISIBLE)) {
1528 MaterialMenu.prototype['toggle'] = MaterialMenu.prototype.toggle;
1529 // The component registers itself. It can assume componentHandler is available
1530 // in the global scope.
1531 componentHandler.register({
1532 constructor: MaterialMenu,
1533 classAsString: 'MaterialMenu',
1534 cssClass: 'mdl-js-menu',
1539 * Copyright 2015 Google Inc. All Rights Reserved.
1541 * Licensed under the Apache License, Version 2.0 (the "License");
1542 * you may not use this file except in compliance with the License.
1543 * You may obtain a copy of the License at
1545 * http://www.apache.org/licenses/LICENSE-2.0
1547 * Unless required by applicable law or agreed to in writing, software
1548 * distributed under the License is distributed on an "AS IS" BASIS,
1549 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1550 * See the License for the specific language governing permissions and
1551 * limitations under the License.
1554 * Class constructor for Progress MDL component.
1555 * Implements MDL component design pattern defined at:
1556 * https://github.com/jasonmayes/mdl-component-design-pattern
1559 * @param {HTMLElement} element The element that will be upgraded.
1561 var MaterialProgress = function MaterialProgress(element) {
1562 this.element_ = element;
1563 // Initialize instance.
1566 window['MaterialProgress'] = MaterialProgress;
1568 * Store constants in one place so they can be updated easily.
1570 * @enum {string | number}
1573 MaterialProgress.prototype.Constant_ = {};
1575 * Store strings for class names defined by this component that are used in
1576 * JavaScript. This allows us to simply change it in one place should we
1577 * decide to modify at a later date.
1582 MaterialProgress.prototype.CssClasses_ = {INDETERMINATE_CLASS: 'mdl-progress__indeterminate'};
1584 * Set the current progress of the progressbar.
1586 * @param {number} p Percentage of the progress (0-100)
1589 MaterialProgress.prototype.setProgress = function (p) {
1590 if (this.element_.classList.contains(this.CssClasses_.INDETERMINATE_CLASS)) {
1593 this.progressbar_.style.width = p + '%';
1595 MaterialProgress.prototype['setProgress'] = MaterialProgress.prototype.setProgress;
1597 * Set the current progress of the buffer.
1599 * @param {number} p Percentage of the buffer (0-100)
1602 MaterialProgress.prototype.setBuffer = function (p) {
1603 this.bufferbar_.style.width = p + '%';
1604 this.auxbar_.style.width = 100 - p + '%';
1606 MaterialProgress.prototype['setBuffer'] = MaterialProgress.prototype.setBuffer;
1608 * Initialize element.
1610 MaterialProgress.prototype.init = function () {
1611 if (this.element_) {
1612 var el = document.createElement('div');
1613 el.className = 'progressbar bar bar1';
1614 this.element_.appendChild(el);
1615 this.progressbar_ = el;
1616 el = document.createElement('div');
1617 el.className = 'bufferbar bar bar2';
1618 this.element_.appendChild(el);
1619 this.bufferbar_ = el;
1620 el = document.createElement('div');
1621 el.className = 'auxbar bar bar3';
1622 this.element_.appendChild(el);
1624 this.progressbar_.style.width = '0%';
1625 this.bufferbar_.style.width = '100%';
1626 this.auxbar_.style.width = '0%';
1627 this.element_.classList.add('is-upgraded');
1630 // The component registers itself. It can assume componentHandler is available
1631 // in the global scope.
1632 componentHandler.register({
1633 constructor: MaterialProgress,
1634 classAsString: 'MaterialProgress',
1635 cssClass: 'mdl-js-progress',
1640 * Copyright 2015 Google Inc. All Rights Reserved.
1642 * Licensed under the Apache License, Version 2.0 (the "License");
1643 * you may not use this file except in compliance with the License.
1644 * You may obtain a copy of the License at
1646 * http://www.apache.org/licenses/LICENSE-2.0
1648 * Unless required by applicable law or agreed to in writing, software
1649 * distributed under the License is distributed on an "AS IS" BASIS,
1650 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1651 * See the License for the specific language governing permissions and
1652 * limitations under the License.
1655 * Class constructor for Radio MDL component.
1656 * Implements MDL component design pattern defined at:
1657 * https://github.com/jasonmayes/mdl-component-design-pattern
1660 * @param {HTMLElement} element The element that will be upgraded.
1662 var MaterialRadio = function MaterialRadio(element) {
1663 this.element_ = element;
1664 // Initialize instance.
1667 window['MaterialRadio'] = MaterialRadio;
1669 * Store constants in one place so they can be updated easily.
1671 * @enum {string | number}
1674 MaterialRadio.prototype.Constant_ = {TINY_TIMEOUT: 0.001};
1676 * Store strings for class names defined by this component that are used in
1677 * JavaScript. This allows us to simply change it in one place should we
1678 * decide to modify at a later date.
1683 MaterialRadio.prototype.CssClasses_ = {
1684 IS_FOCUSED: 'is-focused',
1685 IS_DISABLED: 'is-disabled',
1686 IS_CHECKED: 'is-checked',
1687 IS_UPGRADED: 'is-upgraded',
1688 JS_RADIO: 'mdl-js-radio',
1689 RADIO_BTN: 'mdl-radio__button',
1690 RADIO_OUTER_CIRCLE: 'mdl-radio__outer-circle',
1691 RADIO_INNER_CIRCLE: 'mdl-radio__inner-circle',
1692 RIPPLE_EFFECT: 'mdl-js-ripple-effect',
1693 RIPPLE_IGNORE_EVENTS: 'mdl-js-ripple-effect--ignore-events',
1694 RIPPLE_CONTAINER: 'mdl-radio__ripple-container',
1695 RIPPLE_CENTER: 'mdl-ripple--center',
1696 RIPPLE: 'mdl-ripple'
1699 * Handle change of state.
1701 * @param {Event} event The event that fired.
1704 MaterialRadio.prototype.onChange_ = function (event) {
1705 // Since other radio buttons don't get change events, we need to look for
1706 // them to update their classes.
1707 var radios = document.getElementsByClassName(this.CssClasses_.JS_RADIO);
1708 for (var i = 0; i < radios.length; i++) {
1709 var button = radios[i].querySelector('.' + this.CssClasses_.RADIO_BTN);
1710 // Different name == different group, so no point updating those.
1711 if (button.getAttribute('name') === this.btnElement_.getAttribute('name')) {
1712 if (typeof radios[i]['MaterialRadio'] !== 'undefined') {
1713 radios[i]['MaterialRadio'].updateClasses_();
1721 * @param {Event} event The event that fired.
1724 MaterialRadio.prototype.onFocus_ = function (event) {
1725 this.element_.classList.add(this.CssClasses_.IS_FOCUSED);
1728 * Handle lost focus.
1730 * @param {Event} event The event that fired.
1733 MaterialRadio.prototype.onBlur_ = function (event) {
1734 this.element_.classList.remove(this.CssClasses_.IS_FOCUSED);
1739 * @param {Event} event The event that fired.
1742 MaterialRadio.prototype.onMouseup_ = function (event) {
1750 MaterialRadio.prototype.updateClasses_ = function () {
1751 this.checkDisabled();
1752 this.checkToggleState();
1759 MaterialRadio.prototype.blur_ = function () {
1760 // TODO: figure out why there's a focus event being fired after our blur,
1761 // so that we can avoid this hack.
1762 window.setTimeout(function () {
1763 this.btnElement_.blur();
1764 }.bind(this), this.Constant_.TINY_TIMEOUT);
1768 * Check the components disabled state.
1772 MaterialRadio.prototype.checkDisabled = function () {
1773 if (this.btnElement_.disabled) {
1774 this.element_.classList.add(this.CssClasses_.IS_DISABLED);
1776 this.element_.classList.remove(this.CssClasses_.IS_DISABLED);
1779 MaterialRadio.prototype['checkDisabled'] = MaterialRadio.prototype.checkDisabled;
1781 * Check the components toggled state.
1785 MaterialRadio.prototype.checkToggleState = function () {
1786 if (this.btnElement_.checked) {
1787 this.element_.classList.add(this.CssClasses_.IS_CHECKED);
1789 this.element_.classList.remove(this.CssClasses_.IS_CHECKED);
1792 MaterialRadio.prototype['checkToggleState'] = MaterialRadio.prototype.checkToggleState;
1798 MaterialRadio.prototype.disable = function () {
1799 this.btnElement_.disabled = true;
1800 this.updateClasses_();
1802 MaterialRadio.prototype['disable'] = MaterialRadio.prototype.disable;
1808 MaterialRadio.prototype.enable = function () {
1809 this.btnElement_.disabled = false;
1810 this.updateClasses_();
1812 MaterialRadio.prototype['enable'] = MaterialRadio.prototype.enable;
1818 MaterialRadio.prototype.check = function () {
1819 this.btnElement_.checked = true;
1820 this.onChange_(null);
1822 MaterialRadio.prototype['check'] = MaterialRadio.prototype.check;
1828 MaterialRadio.prototype.uncheck = function () {
1829 this.btnElement_.checked = false;
1830 this.onChange_(null);
1832 MaterialRadio.prototype['uncheck'] = MaterialRadio.prototype.uncheck;
1834 * Initialize element.
1836 MaterialRadio.prototype.init = function () {
1837 if (this.element_) {
1838 this.btnElement_ = this.element_.querySelector('.' + this.CssClasses_.RADIO_BTN);
1839 this.boundChangeHandler_ = this.onChange_.bind(this);
1840 this.boundFocusHandler_ = this.onChange_.bind(this);
1841 this.boundBlurHandler_ = this.onBlur_.bind(this);
1842 this.boundMouseUpHandler_ = this.onMouseup_.bind(this);
1843 var outerCircle = document.createElement('span');
1844 outerCircle.classList.add(this.CssClasses_.RADIO_OUTER_CIRCLE);
1845 var innerCircle = document.createElement('span');
1846 innerCircle.classList.add(this.CssClasses_.RADIO_INNER_CIRCLE);
1847 this.element_.appendChild(outerCircle);
1848 this.element_.appendChild(innerCircle);
1849 var rippleContainer;
1850 if (this.element_.classList.contains(this.CssClasses_.RIPPLE_EFFECT)) {
1851 this.element_.classList.add(this.CssClasses_.RIPPLE_IGNORE_EVENTS);
1852 rippleContainer = document.createElement('span');
1853 rippleContainer.classList.add(this.CssClasses_.RIPPLE_CONTAINER);
1854 rippleContainer.classList.add(this.CssClasses_.RIPPLE_EFFECT);
1855 rippleContainer.classList.add(this.CssClasses_.RIPPLE_CENTER);
1856 rippleContainer.addEventListener('mouseup', this.boundMouseUpHandler_);
1857 var ripple = document.createElement('span');
1858 ripple.classList.add(this.CssClasses_.RIPPLE);
1859 rippleContainer.appendChild(ripple);
1860 this.element_.appendChild(rippleContainer);
1862 this.btnElement_.addEventListener('change', this.boundChangeHandler_);
1863 this.btnElement_.addEventListener('focus', this.boundFocusHandler_);
1864 this.btnElement_.addEventListener('blur', this.boundBlurHandler_);
1865 this.element_.addEventListener('mouseup', this.boundMouseUpHandler_);
1866 this.updateClasses_();
1867 this.element_.classList.add(this.CssClasses_.IS_UPGRADED);
1870 // The component registers itself. It can assume componentHandler is available
1871 // in the global scope.
1872 componentHandler.register({
1873 constructor: MaterialRadio,
1874 classAsString: 'MaterialRadio',
1875 cssClass: 'mdl-js-radio',
1880 * Copyright 2015 Google Inc. All Rights Reserved.
1882 * Licensed under the Apache License, Version 2.0 (the "License");
1883 * you may not use this file except in compliance with the License.
1884 * You may obtain a copy of the License at
1886 * http://www.apache.org/licenses/LICENSE-2.0
1888 * Unless required by applicable law or agreed to in writing, software
1889 * distributed under the License is distributed on an "AS IS" BASIS,
1890 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1891 * See the License for the specific language governing permissions and
1892 * limitations under the License.
1895 * Class constructor for Slider MDL component.
1896 * Implements MDL component design pattern defined at:
1897 * https://github.com/jasonmayes/mdl-component-design-pattern
1900 * @param {HTMLElement} element The element that will be upgraded.
1902 var MaterialSlider = function MaterialSlider(element) {
1903 this.element_ = element;
1904 // Browser feature detection.
1905 this.isIE_ = window.navigator.msPointerEnabled;
1906 // Initialize instance.
1909 window['MaterialSlider'] = MaterialSlider;
1911 * Store constants in one place so they can be updated easily.
1913 * @enum {string | number}
1916 MaterialSlider.prototype.Constant_ = {};
1918 * Store strings for class names defined by this component that are used in
1919 * JavaScript. This allows us to simply change it in one place should we
1920 * decide to modify at a later date.
1925 MaterialSlider.prototype.CssClasses_ = {
1926 IE_CONTAINER: 'mdl-slider__ie-container',
1927 SLIDER_CONTAINER: 'mdl-slider__container',
1928 BACKGROUND_FLEX: 'mdl-slider__background-flex',
1929 BACKGROUND_LOWER: 'mdl-slider__background-lower',
1930 BACKGROUND_UPPER: 'mdl-slider__background-upper',
1931 IS_LOWEST_VALUE: 'is-lowest-value',
1932 IS_UPGRADED: 'is-upgraded'
1935 * Handle input on element.
1937 * @param {Event} event The event that fired.
1940 MaterialSlider.prototype.onInput_ = function (event) {
1941 this.updateValueStyles_();
1944 * Handle change on element.
1946 * @param {Event} event The event that fired.
1949 MaterialSlider.prototype.onChange_ = function (event) {
1950 this.updateValueStyles_();
1953 * Handle mouseup on element.
1955 * @param {Event} event The event that fired.
1958 MaterialSlider.prototype.onMouseUp_ = function (event) {
1959 event.target.blur();
1962 * Handle mousedown on container element.
1963 * This handler is purpose is to not require the use to click
1964 * exactly on the 2px slider element, as FireFox seems to be very
1965 * strict about this.
1967 * @param {Event} event The event that fired.
1969 * @suppress {missingProperties}
1971 MaterialSlider.prototype.onContainerMouseDown_ = function (event) {
1972 // If this click is not on the parent element (but rather some child)
1973 // ignore. It may still bubble up.
1974 if (event.target !== this.element_.parentElement) {
1977 // Discard the original event and create a new event that
1978 // is on the slider element.
1979 event.preventDefault();
1980 var newEvent = new MouseEvent('mousedown', {
1981 target: event.target,
1982 buttons: event.buttons,
1983 clientX: event.clientX,
1984 clientY: this.element_.getBoundingClientRect().y
1986 this.element_.dispatchEvent(newEvent);
1989 * Handle updating of values.
1993 MaterialSlider.prototype.updateValueStyles_ = function () {
1994 // Calculate and apply percentages to div structure behind slider.
1995 var fraction = (this.element_.value - this.element_.min) / (this.element_.max - this.element_.min);
1996 if (fraction === 0) {
1997 this.element_.classList.add(this.CssClasses_.IS_LOWEST_VALUE);
1999 this.element_.classList.remove(this.CssClasses_.IS_LOWEST_VALUE);
2002 this.backgroundLower_.style.flex = fraction;
2003 this.backgroundLower_.style.webkitFlex = fraction;
2004 this.backgroundUpper_.style.flex = 1 - fraction;
2005 this.backgroundUpper_.style.webkitFlex = 1 - fraction;
2014 MaterialSlider.prototype.disable = function () {
2015 this.element_.disabled = true;
2017 MaterialSlider.prototype['disable'] = MaterialSlider.prototype.disable;
2023 MaterialSlider.prototype.enable = function () {
2024 this.element_.disabled = false;
2026 MaterialSlider.prototype['enable'] = MaterialSlider.prototype.enable;
2028 * Update slider value.
2030 * @param {number} value The value to which to set the control (optional).
2033 MaterialSlider.prototype.change = function (value) {
2034 if (typeof value !== 'undefined') {
2035 this.element_.value = value;
2037 this.updateValueStyles_();
2039 MaterialSlider.prototype['change'] = MaterialSlider.prototype.change;
2041 * Initialize element.
2043 MaterialSlider.prototype.init = function () {
2044 if (this.element_) {
2046 // Since we need to specify a very large height in IE due to
2047 // implementation limitations, we add a parent here that trims it down to
2048 // a reasonable size.
2049 var containerIE = document.createElement('div');
2050 containerIE.classList.add(this.CssClasses_.IE_CONTAINER);
2051 this.element_.parentElement.insertBefore(containerIE, this.element_);
2052 this.element_.parentElement.removeChild(this.element_);
2053 containerIE.appendChild(this.element_);
2055 // For non-IE browsers, we need a div structure that sits behind the
2056 // slider and allows us to style the left and right sides of it with
2057 // different colors.
2058 var container = document.createElement('div');
2059 container.classList.add(this.CssClasses_.SLIDER_CONTAINER);
2060 this.element_.parentElement.insertBefore(container, this.element_);
2061 this.element_.parentElement.removeChild(this.element_);
2062 container.appendChild(this.element_);
2063 var backgroundFlex = document.createElement('div');
2064 backgroundFlex.classList.add(this.CssClasses_.BACKGROUND_FLEX);
2065 container.appendChild(backgroundFlex);
2066 this.backgroundLower_ = document.createElement('div');
2067 this.backgroundLower_.classList.add(this.CssClasses_.BACKGROUND_LOWER);
2068 backgroundFlex.appendChild(this.backgroundLower_);
2069 this.backgroundUpper_ = document.createElement('div');
2070 this.backgroundUpper_.classList.add(this.CssClasses_.BACKGROUND_UPPER);
2071 backgroundFlex.appendChild(this.backgroundUpper_);
2073 this.boundInputHandler = this.onInput_.bind(this);
2074 this.boundChangeHandler = this.onChange_.bind(this);
2075 this.boundMouseUpHandler = this.onMouseUp_.bind(this);
2076 this.boundContainerMouseDownHandler = this.onContainerMouseDown_.bind(this);
2077 this.element_.addEventListener('input', this.boundInputHandler);
2078 this.element_.addEventListener('change', this.boundChangeHandler);
2079 this.element_.addEventListener('mouseup', this.boundMouseUpHandler);
2080 this.element_.parentElement.addEventListener('mousedown', this.boundContainerMouseDownHandler);
2081 this.updateValueStyles_();
2082 this.element_.classList.add(this.CssClasses_.IS_UPGRADED);
2085 // The component registers itself. It can assume componentHandler is available
2086 // in the global scope.
2087 componentHandler.register({
2088 constructor: MaterialSlider,
2089 classAsString: 'MaterialSlider',
2090 cssClass: 'mdl-js-slider',
2094 * Copyright 2015 Google Inc. All Rights Reserved.
2096 * Licensed under the Apache License, Version 2.0 (the "License");
2097 * you may not use this file except in compliance with the License.
2098 * You may obtain a copy of the License at
2100 * http://www.apache.org/licenses/LICENSE-2.0
2102 * Unless required by applicable law or agreed to in writing, software
2103 * distributed under the License is distributed on an "AS IS" BASIS,
2104 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2105 * See the License for the specific language governing permissions and
2106 * limitations under the License.
2109 * Class constructor for Snackbar MDL component.
2110 * Implements MDL component design pattern defined at:
2111 * https://github.com/jasonmayes/mdl-component-design-pattern
2114 * @param {HTMLElement} element The element that will be upgraded.
2116 var MaterialSnackbar = function MaterialSnackbar(element) {
2117 this.element_ = element;
2118 this.textElement_ = this.element_.querySelector('.' + this.cssClasses_.MESSAGE);
2119 this.actionElement_ = this.element_.querySelector('.' + this.cssClasses_.ACTION);
2120 if (!this.textElement_) {
2121 throw new Error('There must be a message element for a snackbar.');
2123 if (!this.actionElement_) {
2124 throw new Error('There must be an action element for a snackbar.');
2126 this.active = false;
2127 this.actionHandler_ = undefined;
2128 this.message_ = undefined;
2129 this.actionText_ = undefined;
2130 this.queuedNotifications_ = [];
2131 this.setActionHidden_(true);
2133 window['MaterialSnackbar'] = MaterialSnackbar;
2135 * Store constants in one place so they can be updated easily.
2137 * @enum {string | number}
2140 MaterialSnackbar.prototype.Constant_ = {
2141 // The duration of the snackbar show/hide animation, in ms.
2142 ANIMATION_LENGTH: 250
2145 * Store strings for class names defined by this component that are used in
2146 * JavaScript. This allows us to simply change it in one place should we
2147 * decide to modify at a later date.
2152 MaterialSnackbar.prototype.cssClasses_ = {
2153 SNACKBAR: 'mdl-snackbar',
2154 MESSAGE: 'mdl-snackbar__text',
2155 ACTION: 'mdl-snackbar__action',
2156 ACTIVE: 'mdl-snackbar--active'
2159 * Display the snackbar.
2163 MaterialSnackbar.prototype.displaySnackbar_ = function () {
2164 this.element_.setAttribute('aria-hidden', 'true');
2165 if (this.actionHandler_) {
2166 this.actionElement_.textContent = this.actionText_;
2167 this.actionElement_.addEventListener('click', this.actionHandler_);
2168 this.setActionHidden_(false);
2170 this.textElement_.textContent = this.message_;
2171 this.element_.classList.add(this.cssClasses_.ACTIVE);
2172 this.element_.setAttribute('aria-hidden', 'false');
2173 setTimeout(this.cleanup_.bind(this), this.timeout_);
2176 * Show the snackbar.
2178 * @param {Object} data The data for the notification.
2181 MaterialSnackbar.prototype.showSnackbar = function (data) {
2182 if (data === undefined) {
2183 throw new Error('Please provide a data object with at least a message to display.');
2185 if (data['message'] === undefined) {
2186 throw new Error('Please provide a message to be displayed.');
2188 if (data['actionHandler'] && !data['actionText']) {
2189 throw new Error('Please provide action text with the handler.');
2192 this.queuedNotifications_.push(data);
2195 this.message_ = data['message'];
2196 if (data['timeout']) {
2197 this.timeout_ = data['timeout'];
2199 this.timeout_ = 2750;
2201 if (data['actionHandler']) {
2202 this.actionHandler_ = data['actionHandler'];
2204 if (data['actionText']) {
2205 this.actionText_ = data['actionText'];
2207 this.displaySnackbar_();
2210 MaterialSnackbar.prototype['showSnackbar'] = MaterialSnackbar.prototype.showSnackbar;
2212 * Check if the queue has items within it.
2213 * If it does, display the next entry.
2217 MaterialSnackbar.prototype.checkQueue_ = function () {
2218 if (this.queuedNotifications_.length > 0) {
2219 this.showSnackbar(this.queuedNotifications_.shift());
2223 * Cleanup the snackbar event listeners and accessiblity attributes.
2227 MaterialSnackbar.prototype.cleanup_ = function () {
2228 this.element_.classList.remove(this.cssClasses_.ACTIVE);
2229 setTimeout(function () {
2230 this.element_.setAttribute('aria-hidden', 'true');
2231 this.textElement_.textContent = '';
2232 if (!Boolean(this.actionElement_.getAttribute('aria-hidden'))) {
2233 this.setActionHidden_(true);
2234 this.actionElement_.textContent = '';
2235 this.actionElement_.removeEventListener('click', this.actionHandler_);
2237 this.actionHandler_ = undefined;
2238 this.message_ = undefined;
2239 this.actionText_ = undefined;
2240 this.active = false;
2242 }.bind(this), this.Constant_.ANIMATION_LENGTH);
2245 * Set the action handler hidden state.
2247 * @param {boolean} value
2250 MaterialSnackbar.prototype.setActionHidden_ = function (value) {
2252 this.actionElement_.setAttribute('aria-hidden', 'true');
2254 this.actionElement_.removeAttribute('aria-hidden');
2257 // The component registers itself. It can assume componentHandler is available
2258 // in the global scope.
2259 componentHandler.register({
2260 constructor: MaterialSnackbar,
2261 classAsString: 'MaterialSnackbar',
2262 cssClass: 'mdl-js-snackbar',
2267 * Copyright 2015 Google Inc. All Rights Reserved.
2269 * Licensed under the Apache License, Version 2.0 (the "License");
2270 * you may not use this file except in compliance with the License.
2271 * You may obtain a copy of the License at
2273 * http://www.apache.org/licenses/LICENSE-2.0
2275 * Unless required by applicable law or agreed to in writing, software
2276 * distributed under the License is distributed on an "AS IS" BASIS,
2277 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2278 * See the License for the specific language governing permissions and
2279 * limitations under the License.
2282 * Class constructor for Spinner MDL component.
2283 * Implements MDL component design pattern defined at:
2284 * https://github.com/jasonmayes/mdl-component-design-pattern
2286 * @param {HTMLElement} element The element that will be upgraded.
2289 var MaterialSpinner = function MaterialSpinner(element) {
2290 this.element_ = element;
2291 // Initialize instance.
2294 window['MaterialSpinner'] = MaterialSpinner;
2296 * Store constants in one place so they can be updated easily.
2298 * @enum {string | number}
2301 MaterialSpinner.prototype.Constant_ = {MDL_SPINNER_LAYER_COUNT: 4};
2303 * Store strings for class names defined by this component that are used in
2304 * JavaScript. This allows us to simply change it in one place should we
2305 * decide to modify at a later date.
2310 MaterialSpinner.prototype.CssClasses_ = {
2311 MDL_SPINNER_LAYER: 'mdl-spinner__layer',
2312 MDL_SPINNER_CIRCLE_CLIPPER: 'mdl-spinner__circle-clipper',
2313 MDL_SPINNER_CIRCLE: 'mdl-spinner__circle',
2314 MDL_SPINNER_GAP_PATCH: 'mdl-spinner__gap-patch',
2315 MDL_SPINNER_LEFT: 'mdl-spinner__left',
2316 MDL_SPINNER_RIGHT: 'mdl-spinner__right'
2319 * Auxiliary method to create a spinner layer.
2321 * @param {number} index Index of the layer to be created.
2324 MaterialSpinner.prototype.createLayer = function (index) {
2325 var layer = document.createElement('div');
2326 layer.classList.add(this.CssClasses_.MDL_SPINNER_LAYER);
2327 layer.classList.add(this.CssClasses_.MDL_SPINNER_LAYER + '-' + index);
2328 var leftClipper = document.createElement('div');
2329 leftClipper.classList.add(this.CssClasses_.MDL_SPINNER_CIRCLE_CLIPPER);
2330 leftClipper.classList.add(this.CssClasses_.MDL_SPINNER_LEFT);
2331 var gapPatch = document.createElement('div');
2332 gapPatch.classList.add(this.CssClasses_.MDL_SPINNER_GAP_PATCH);
2333 var rightClipper = document.createElement('div');
2334 rightClipper.classList.add(this.CssClasses_.MDL_SPINNER_CIRCLE_CLIPPER);
2335 rightClipper.classList.add(this.CssClasses_.MDL_SPINNER_RIGHT);
2336 var circleOwners = [
2341 for (var i = 0; i < circleOwners.length; i++) {
2342 var circle = document.createElement('div');
2343 circle.classList.add(this.CssClasses_.MDL_SPINNER_CIRCLE);
2344 circleOwners[i].appendChild(circle);
2346 layer.appendChild(leftClipper);
2347 layer.appendChild(gapPatch);
2348 layer.appendChild(rightClipper);
2349 this.element_.appendChild(layer);
2351 MaterialSpinner.prototype['createLayer'] = MaterialSpinner.prototype.createLayer;
2353 * Stops the spinner animation.
2354 * Public method for users who need to stop the spinner for any reason.
2358 MaterialSpinner.prototype.stop = function () {
2359 this.element_.classList.remove('is-active');
2361 MaterialSpinner.prototype['stop'] = MaterialSpinner.prototype.stop;
2363 * Starts the spinner animation.
2364 * Public method for users who need to manually start the spinner for any reason
2365 * (instead of just adding the 'is-active' class to their markup).
2369 MaterialSpinner.prototype.start = function () {
2370 this.element_.classList.add('is-active');
2372 MaterialSpinner.prototype['start'] = MaterialSpinner.prototype.start;
2374 * Initialize element.
2376 MaterialSpinner.prototype.init = function () {
2377 if (this.element_) {
2378 for (var i = 1; i <= this.Constant_.MDL_SPINNER_LAYER_COUNT; i++) {
2379 this.createLayer(i);
2381 this.element_.classList.add('is-upgraded');
2384 // The component registers itself. It can assume componentHandler is available
2385 // in the global scope.
2386 componentHandler.register({
2387 constructor: MaterialSpinner,
2388 classAsString: 'MaterialSpinner',
2389 cssClass: 'mdl-js-spinner',
2394 * Copyright 2015 Google Inc. All Rights Reserved.
2396 * Licensed under the Apache License, Version 2.0 (the "License");
2397 * you may not use this file except in compliance with the License.
2398 * You may obtain a copy of the License at
2400 * http://www.apache.org/licenses/LICENSE-2.0
2402 * Unless required by applicable law or agreed to in writing, software
2403 * distributed under the License is distributed on an "AS IS" BASIS,
2404 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2405 * See the License for the specific language governing permissions and
2406 * limitations under the License.
2409 * Class constructor for Checkbox MDL component.
2410 * Implements MDL component design pattern defined at:
2411 * https://github.com/jasonmayes/mdl-component-design-pattern
2414 * @param {HTMLElement} element The element that will be upgraded.
2416 var MaterialSwitch = function MaterialSwitch(element) {
2417 this.element_ = element;
2418 // Initialize instance.
2421 window['MaterialSwitch'] = MaterialSwitch;
2423 * Store constants in one place so they can be updated easily.
2425 * @enum {string | number}
2428 MaterialSwitch.prototype.Constant_ = {TINY_TIMEOUT: 0.001};
2430 * Store strings for class names defined by this component that are used in
2431 * JavaScript. This allows us to simply change it in one place should we
2432 * decide to modify at a later date.
2437 MaterialSwitch.prototype.CssClasses_ = {
2438 INPUT: 'mdl-switch__input',
2439 TRACK: 'mdl-switch__track',
2440 THUMB: 'mdl-switch__thumb',
2441 FOCUS_HELPER: 'mdl-switch__focus-helper',
2442 RIPPLE_EFFECT: 'mdl-js-ripple-effect',
2443 RIPPLE_IGNORE_EVENTS: 'mdl-js-ripple-effect--ignore-events',
2444 RIPPLE_CONTAINER: 'mdl-switch__ripple-container',
2445 RIPPLE_CENTER: 'mdl-ripple--center',
2446 RIPPLE: 'mdl-ripple',
2447 IS_FOCUSED: 'is-focused',
2448 IS_DISABLED: 'is-disabled',
2449 IS_CHECKED: 'is-checked'
2452 * Handle change of state.
2454 * @param {Event} event The event that fired.
2457 MaterialSwitch.prototype.onChange_ = function (event) {
2458 this.updateClasses_();
2461 * Handle focus of element.
2463 * @param {Event} event The event that fired.
2466 MaterialSwitch.prototype.onFocus_ = function (event) {
2467 this.element_.classList.add(this.CssClasses_.IS_FOCUSED);
2470 * Handle lost focus of element.
2472 * @param {Event} event The event that fired.
2475 MaterialSwitch.prototype.onBlur_ = function (event) {
2476 this.element_.classList.remove(this.CssClasses_.IS_FOCUSED);
2481 * @param {Event} event The event that fired.
2484 MaterialSwitch.prototype.onMouseUp_ = function (event) {
2488 * Handle class updates.
2492 MaterialSwitch.prototype.updateClasses_ = function () {
2493 this.checkDisabled();
2494 this.checkToggleState();
2501 MaterialSwitch.prototype.blur_ = function () {
2502 // TODO: figure out why there's a focus event being fired after our blur,
2503 // so that we can avoid this hack.
2504 window.setTimeout(function () {
2505 this.inputElement_.blur();
2506 }.bind(this), this.Constant_.TINY_TIMEOUT);
2510 * Check the components disabled state.
2514 MaterialSwitch.prototype.checkDisabled = function () {
2515 if (this.inputElement_.disabled) {
2516 this.element_.classList.add(this.CssClasses_.IS_DISABLED);
2518 this.element_.classList.remove(this.CssClasses_.IS_DISABLED);
2521 MaterialSwitch.prototype['checkDisabled'] = MaterialSwitch.prototype.checkDisabled;
2523 * Check the components toggled state.
2527 MaterialSwitch.prototype.checkToggleState = function () {
2528 if (this.inputElement_.checked) {
2529 this.element_.classList.add(this.CssClasses_.IS_CHECKED);
2531 this.element_.classList.remove(this.CssClasses_.IS_CHECKED);
2534 MaterialSwitch.prototype['checkToggleState'] = MaterialSwitch.prototype.checkToggleState;
2540 MaterialSwitch.prototype.disable = function () {
2541 this.inputElement_.disabled = true;
2542 this.updateClasses_();
2544 MaterialSwitch.prototype['disable'] = MaterialSwitch.prototype.disable;
2550 MaterialSwitch.prototype.enable = function () {
2551 this.inputElement_.disabled = false;
2552 this.updateClasses_();
2554 MaterialSwitch.prototype['enable'] = MaterialSwitch.prototype.enable;
2560 MaterialSwitch.prototype.on = function () {
2561 this.inputElement_.checked = true;
2562 this.updateClasses_();
2564 MaterialSwitch.prototype['on'] = MaterialSwitch.prototype.on;
2566 * Deactivate switch.
2570 MaterialSwitch.prototype.off = function () {
2571 this.inputElement_.checked = false;
2572 this.updateClasses_();
2574 MaterialSwitch.prototype['off'] = MaterialSwitch.prototype.off;
2576 * Initialize element.
2578 MaterialSwitch.prototype.init = function () {
2579 if (this.element_) {
2580 this.inputElement_ = this.element_.querySelector('.' + this.CssClasses_.INPUT);
2581 var track = document.createElement('div');
2582 track.classList.add(this.CssClasses_.TRACK);
2583 var thumb = document.createElement('div');
2584 thumb.classList.add(this.CssClasses_.THUMB);
2585 var focusHelper = document.createElement('span');
2586 focusHelper.classList.add(this.CssClasses_.FOCUS_HELPER);
2587 thumb.appendChild(focusHelper);
2588 this.element_.appendChild(track);
2589 this.element_.appendChild(thumb);
2590 this.boundMouseUpHandler = this.onMouseUp_.bind(this);
2591 if (this.element_.classList.contains(this.CssClasses_.RIPPLE_EFFECT)) {
2592 this.element_.classList.add(this.CssClasses_.RIPPLE_IGNORE_EVENTS);
2593 this.rippleContainerElement_ = document.createElement('span');
2594 this.rippleContainerElement_.classList.add(this.CssClasses_.RIPPLE_CONTAINER);
2595 this.rippleContainerElement_.classList.add(this.CssClasses_.RIPPLE_EFFECT);
2596 this.rippleContainerElement_.classList.add(this.CssClasses_.RIPPLE_CENTER);
2597 this.rippleContainerElement_.addEventListener('mouseup', this.boundMouseUpHandler);
2598 var ripple = document.createElement('span');
2599 ripple.classList.add(this.CssClasses_.RIPPLE);
2600 this.rippleContainerElement_.appendChild(ripple);
2601 this.element_.appendChild(this.rippleContainerElement_);
2603 this.boundChangeHandler = this.onChange_.bind(this);
2604 this.boundFocusHandler = this.onFocus_.bind(this);
2605 this.boundBlurHandler = this.onBlur_.bind(this);
2606 this.inputElement_.addEventListener('change', this.boundChangeHandler);
2607 this.inputElement_.addEventListener('focus', this.boundFocusHandler);
2608 this.inputElement_.addEventListener('blur', this.boundBlurHandler);
2609 this.element_.addEventListener('mouseup', this.boundMouseUpHandler);
2610 this.updateClasses_();
2611 this.element_.classList.add('is-upgraded');
2614 // The component registers itself. It can assume componentHandler is available
2615 // in the global scope.
2616 componentHandler.register({
2617 constructor: MaterialSwitch,
2618 classAsString: 'MaterialSwitch',
2619 cssClass: 'mdl-js-switch',
2624 * Copyright 2015 Google Inc. All Rights Reserved.
2626 * Licensed under the Apache License, Version 2.0 (the "License");
2627 * you may not use this file except in compliance with the License.
2628 * You may obtain a copy of the License at
2630 * http://www.apache.org/licenses/LICENSE-2.0
2632 * Unless required by applicable law or agreed to in writing, software
2633 * distributed under the License is distributed on an "AS IS" BASIS,
2634 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2635 * See the License for the specific language governing permissions and
2636 * limitations under the License.
2639 * Class constructor for Tabs MDL component.
2640 * Implements MDL component design pattern defined at:
2641 * https://github.com/jasonmayes/mdl-component-design-pattern
2644 * @param {Element} element The element that will be upgraded.
2646 var MaterialTabs = function MaterialTabs(element) {
2647 // Stores the HTML element.
2648 this.element_ = element;
2649 // Initialize instance.
2652 window['MaterialTabs'] = MaterialTabs;
2654 * Store constants in one place so they can be updated easily.
2659 MaterialTabs.prototype.Constant_ = {};
2661 * Store strings for class names defined by this component that are used in
2662 * JavaScript. This allows us to simply change it in one place should we
2663 * decide to modify at a later date.
2668 MaterialTabs.prototype.CssClasses_ = {
2669 TAB_CLASS: 'mdl-tabs__tab',
2670 PANEL_CLASS: 'mdl-tabs__panel',
2671 ACTIVE_CLASS: 'is-active',
2672 UPGRADED_CLASS: 'is-upgraded',
2673 MDL_JS_RIPPLE_EFFECT: 'mdl-js-ripple-effect',
2674 MDL_RIPPLE_CONTAINER: 'mdl-tabs__ripple-container',
2675 MDL_RIPPLE: 'mdl-ripple',
2676 MDL_JS_RIPPLE_EFFECT_IGNORE_EVENTS: 'mdl-js-ripple-effect--ignore-events'
2679 * Handle clicks to a tabs component
2683 MaterialTabs.prototype.initTabs_ = function () {
2684 if (this.element_.classList.contains(this.CssClasses_.MDL_JS_RIPPLE_EFFECT)) {
2685 this.element_.classList.add(this.CssClasses_.MDL_JS_RIPPLE_EFFECT_IGNORE_EVENTS);
2687 // Select element tabs, document panels
2688 this.tabs_ = this.element_.querySelectorAll('.' + this.CssClasses_.TAB_CLASS);
2689 this.panels_ = this.element_.querySelectorAll('.' + this.CssClasses_.PANEL_CLASS);
2690 // Create new tabs for each tab element
2691 for (var i = 0; i < this.tabs_.length; i++) {
2692 new MaterialTab(this.tabs_[i], this);
2694 this.element_.classList.add(this.CssClasses_.UPGRADED_CLASS);
2697 * Reset tab state, dropping active classes
2701 MaterialTabs.prototype.resetTabState_ = function () {
2702 for (var k = 0; k < this.tabs_.length; k++) {
2703 this.tabs_[k].classList.remove(this.CssClasses_.ACTIVE_CLASS);
2707 * Reset panel state, droping active classes
2711 MaterialTabs.prototype.resetPanelState_ = function () {
2712 for (var j = 0; j < this.panels_.length; j++) {
2713 this.panels_[j].classList.remove(this.CssClasses_.ACTIVE_CLASS);
2717 * Initialize element.
2719 MaterialTabs.prototype.init = function () {
2720 if (this.element_) {
2726 * Constructor for an individual tab.
2729 * @param {Element} tab The HTML element for the tab.
2730 * @param {MaterialTabs} ctx The MaterialTabs object that owns the tab.
2732 function MaterialTab(tab, ctx) {
2734 if (ctx.element_.classList.contains(ctx.CssClasses_.MDL_JS_RIPPLE_EFFECT)) {
2735 var rippleContainer = document.createElement('span');
2736 rippleContainer.classList.add(ctx.CssClasses_.MDL_RIPPLE_CONTAINER);
2737 rippleContainer.classList.add(ctx.CssClasses_.MDL_JS_RIPPLE_EFFECT);
2738 var ripple = document.createElement('span');
2739 ripple.classList.add(ctx.CssClasses_.MDL_RIPPLE);
2740 rippleContainer.appendChild(ripple);
2741 tab.appendChild(rippleContainer);
2743 tab.addEventListener('click', function (e) {
2744 if (tab.getAttribute('href').charAt(0) === '#') {
2746 var href = tab.href.split('#')[1];
2747 var panel = ctx.element_.querySelector('#' + href);
2748 ctx.resetTabState_();
2749 ctx.resetPanelState_();
2750 tab.classList.add(ctx.CssClasses_.ACTIVE_CLASS);
2751 panel.classList.add(ctx.CssClasses_.ACTIVE_CLASS);
2757 // The component registers itself. It can assume componentHandler is available
2758 // in the global scope.
2759 componentHandler.register({
2760 constructor: MaterialTabs,
2761 classAsString: 'MaterialTabs',
2762 cssClass: 'mdl-js-tabs'
2766 * Copyright 2015 Google Inc. All Rights Reserved.
2768 * Licensed under the Apache License, Version 2.0 (the "License");
2769 * you may not use this file except in compliance with the License.
2770 * You may obtain a copy of the License at
2772 * http://www.apache.org/licenses/LICENSE-2.0
2774 * Unless required by applicable law or agreed to in writing, software
2775 * distributed under the License is distributed on an "AS IS" BASIS,
2776 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2777 * See the License for the specific language governing permissions and
2778 * limitations under the License.
2781 * Class constructor for Textfield MDL component.
2782 * Implements MDL component design pattern defined at:
2783 * https://github.com/jasonmayes/mdl-component-design-pattern
2786 * @param {HTMLElement} element The element that will be upgraded.
2788 var MaterialTextfield = function MaterialTextfield(element) {
2789 this.element_ = element;
2790 this.maxRows = this.Constant_.NO_MAX_ROWS;
2791 // Initialize instance.
2794 window['MaterialTextfield'] = MaterialTextfield;
2796 * Store constants in one place so they can be updated easily.
2798 * @enum {string | number}
2801 MaterialTextfield.prototype.Constant_ = {
2803 MAX_ROWS_ATTRIBUTE: 'maxrows'
2806 * Store strings for class names defined by this component that are used in
2807 * JavaScript. This allows us to simply change it in one place should we
2808 * decide to modify at a later date.
2813 MaterialTextfield.prototype.CssClasses_ = {
2814 LABEL: 'mdl-textfield__label',
2815 INPUT: 'mdl-textfield__input',
2816 IS_DIRTY: 'is-dirty',
2817 IS_FOCUSED: 'is-focused',
2818 IS_DISABLED: 'is-disabled',
2819 IS_INVALID: 'is-invalid',
2820 IS_UPGRADED: 'is-upgraded',
2821 HAS_PLACEHOLDER: 'has-placeholder'
2824 * Handle input being entered.
2826 * @param {Event} event The event that fired.
2829 MaterialTextfield.prototype.onKeyDown_ = function (event) {
2830 var currentRowCount = event.target.value.split('\n').length;
2831 if (event.keyCode === 13) {
2832 if (currentRowCount >= this.maxRows) {
2833 event.preventDefault();
2840 * @param {Event} event The event that fired.
2843 MaterialTextfield.prototype.onFocus_ = function (event) {
2844 this.element_.classList.add(this.CssClasses_.IS_FOCUSED);
2847 * Handle lost focus.
2849 * @param {Event} event The event that fired.
2852 MaterialTextfield.prototype.onBlur_ = function (event) {
2853 this.element_.classList.remove(this.CssClasses_.IS_FOCUSED);
2856 * Handle reset event from out side.
2858 * @param {Event} event The event that fired.
2861 MaterialTextfield.prototype.onReset_ = function (event) {
2862 this.updateClasses_();
2865 * Handle class updates.
2869 MaterialTextfield.prototype.updateClasses_ = function () {
2870 this.checkDisabled();
2871 this.checkValidity();
2877 * Check the disabled state and update field accordingly.
2881 MaterialTextfield.prototype.checkDisabled = function () {
2882 if (this.input_.disabled) {
2883 this.element_.classList.add(this.CssClasses_.IS_DISABLED);
2885 this.element_.classList.remove(this.CssClasses_.IS_DISABLED);
2888 MaterialTextfield.prototype['checkDisabled'] = MaterialTextfield.prototype.checkDisabled;
2890 * Check the focus state and update field accordingly.
2894 MaterialTextfield.prototype.checkFocus = function () {
2895 if (Boolean(this.element_.querySelector(':focus'))) {
2896 this.element_.classList.add(this.CssClasses_.IS_FOCUSED);
2898 this.element_.classList.remove(this.CssClasses_.IS_FOCUSED);
2901 MaterialTextfield.prototype['checkFocus'] = MaterialTextfield.prototype.checkFocus;
2903 * Check the validity state and update field accordingly.
2907 MaterialTextfield.prototype.checkValidity = function () {
2908 if (this.input_.validity) {
2909 if (this.input_.validity.valid) {
2910 this.element_.classList.remove(this.CssClasses_.IS_INVALID);
2912 this.element_.classList.add(this.CssClasses_.IS_INVALID);
2916 MaterialTextfield.prototype['checkValidity'] = MaterialTextfield.prototype.checkValidity;
2918 * Check the dirty state and update field accordingly.
2922 MaterialTextfield.prototype.checkDirty = function () {
2923 if (this.input_.value && this.input_.value.length > 0) {
2924 this.element_.classList.add(this.CssClasses_.IS_DIRTY);
2926 this.element_.classList.remove(this.CssClasses_.IS_DIRTY);
2929 MaterialTextfield.prototype['checkDirty'] = MaterialTextfield.prototype.checkDirty;
2931 * Disable text field.
2935 MaterialTextfield.prototype.disable = function () {
2936 this.input_.disabled = true;
2937 this.updateClasses_();
2939 MaterialTextfield.prototype['disable'] = MaterialTextfield.prototype.disable;
2941 * Enable text field.
2945 MaterialTextfield.prototype.enable = function () {
2946 this.input_.disabled = false;
2947 this.updateClasses_();
2949 MaterialTextfield.prototype['enable'] = MaterialTextfield.prototype.enable;
2951 * Update text field value.
2953 * @param {string} value The value to which to set the control (optional).
2956 MaterialTextfield.prototype.change = function (value) {
2957 this.input_.value = value || '';
2958 this.updateClasses_();
2960 MaterialTextfield.prototype['change'] = MaterialTextfield.prototype.change;
2962 * Initialize element.
2964 MaterialTextfield.prototype.init = function () {
2965 if (this.element_) {
2966 this.label_ = this.element_.querySelector('.' + this.CssClasses_.LABEL);
2967 this.input_ = this.element_.querySelector('.' + this.CssClasses_.INPUT);
2969 if (this.input_.hasAttribute(this.Constant_.MAX_ROWS_ATTRIBUTE)) {
2970 this.maxRows = parseInt(this.input_.getAttribute(this.Constant_.MAX_ROWS_ATTRIBUTE), 10);
2971 if (isNaN(this.maxRows)) {
2972 this.maxRows = this.Constant_.NO_MAX_ROWS;
2975 if (this.input_.hasAttribute('placeholder')) {
2976 this.element_.classList.add(this.CssClasses_.HAS_PLACEHOLDER);
2978 this.boundUpdateClassesHandler = this.updateClasses_.bind(this);
2979 this.boundFocusHandler = this.onFocus_.bind(this);
2980 this.boundBlurHandler = this.onBlur_.bind(this);
2981 this.boundResetHandler = this.onReset_.bind(this);
2982 this.input_.addEventListener('input', this.boundUpdateClassesHandler);
2983 this.input_.addEventListener('focus', this.boundFocusHandler);
2984 this.input_.addEventListener('blur', this.boundBlurHandler);
2985 this.input_.addEventListener('reset', this.boundResetHandler);
2986 if (this.maxRows !== this.Constant_.NO_MAX_ROWS) {
2987 // TODO: This should handle pasting multi line text.
2988 // Currently doesn't.
2989 this.boundKeyDownHandler = this.onKeyDown_.bind(this);
2990 this.input_.addEventListener('keydown', this.boundKeyDownHandler);
2992 var invalid = this.element_.classList.contains(this.CssClasses_.IS_INVALID);
2993 this.updateClasses_();
2994 this.element_.classList.add(this.CssClasses_.IS_UPGRADED);
2996 this.element_.classList.add(this.CssClasses_.IS_INVALID);
2998 if (this.input_.hasAttribute('autofocus')) {
2999 this.element_.focus();
3005 // The component registers itself. It can assume componentHandler is available
3006 // in the global scope.
3007 componentHandler.register({
3008 constructor: MaterialTextfield,
3009 classAsString: 'MaterialTextfield',
3010 cssClass: 'mdl-js-textfield',
3015 * Copyright 2015 Google Inc. All Rights Reserved.
3017 * Licensed under the Apache License, Version 2.0 (the "License");
3018 * you may not use this file except in compliance with the License.
3019 * You may obtain a copy of the License at
3021 * http://www.apache.org/licenses/LICENSE-2.0
3023 * Unless required by applicable law or agreed to in writing, software
3024 * distributed under the License is distributed on an "AS IS" BASIS,
3025 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
3026 * See the License for the specific language governing permissions and
3027 * limitations under the License.
3030 * Class constructor for Tooltip MDL component.
3031 * Implements MDL component design pattern defined at:
3032 * https://github.com/jasonmayes/mdl-component-design-pattern
3035 * @param {HTMLElement} element The element that will be upgraded.
3037 var MaterialTooltip = function MaterialTooltip(element) {
3038 this.element_ = element;
3039 // Initialize instance.
3042 window['MaterialTooltip'] = MaterialTooltip;
3044 * Store constants in one place so they can be updated easily.
3046 * @enum {string | number}
3049 MaterialTooltip.prototype.Constant_ = {};
3051 * Store strings for class names defined by this component that are used in
3052 * JavaScript. This allows us to simply change it in one place should we
3053 * decide to modify at a later date.
3058 MaterialTooltip.prototype.CssClasses_ = {
3059 IS_ACTIVE: 'is-active',
3060 BOTTOM: 'mdl-tooltip--bottom',
3061 LEFT: 'mdl-tooltip--left',
3062 RIGHT: 'mdl-tooltip--right',
3063 TOP: 'mdl-tooltip--top'
3066 * Handle mouseenter for tooltip.
3068 * @param {Event} event The event that fired.
3071 MaterialTooltip.prototype.handleMouseEnter_ = function (event) {
3072 var props = event.target.getBoundingClientRect();
3073 var left = props.left + props.width / 2;
3074 var top = props.top + props.height / 2;
3075 var marginLeft = -1 * (this.element_.offsetWidth / 2);
3076 var marginTop = -1 * (this.element_.offsetHeight / 2);
3077 if (this.element_.classList.contains(this.CssClasses_.LEFT) || this.element_.classList.contains(this.CssClasses_.RIGHT)) {
3078 left = props.width / 2;
3079 if (top + marginTop < 0) {
3080 this.element_.style.top = '0';
3081 this.element_.style.marginTop = '0';
3083 this.element_.style.top = top + 'px';
3084 this.element_.style.marginTop = marginTop + 'px';
3087 if (left + marginLeft < 0) {
3088 this.element_.style.left = '0';
3089 this.element_.style.marginLeft = '0';
3091 this.element_.style.left = left + 'px';
3092 this.element_.style.marginLeft = marginLeft + 'px';
3095 if (this.element_.classList.contains(this.CssClasses_.TOP)) {
3096 this.element_.style.top = props.top - this.element_.offsetHeight - 10 + 'px';
3097 } else if (this.element_.classList.contains(this.CssClasses_.RIGHT)) {
3098 this.element_.style.left = props.left + props.width + 10 + 'px';
3099 } else if (this.element_.classList.contains(this.CssClasses_.LEFT)) {
3100 this.element_.style.left = props.left - this.element_.offsetWidth - 10 + 'px';
3102 this.element_.style.top = props.top + props.height + 10 + 'px';
3104 this.element_.classList.add(this.CssClasses_.IS_ACTIVE);
3107 * Hide tooltip on mouseleave or scroll
3111 MaterialTooltip.prototype.hideTooltip_ = function () {
3112 this.element_.classList.remove(this.CssClasses_.IS_ACTIVE);
3115 * Initialize element.
3117 MaterialTooltip.prototype.init = function () {
3118 if (this.element_) {
3119 var forElId = this.element_.getAttribute('for') || this.element_.getAttribute('data-mdl-for');
3121 this.forElement_ = document.getElementById(forElId);
3123 if (this.forElement_) {
3124 // It's left here because it prevents accidental text selection on Android
3125 if (!this.forElement_.hasAttribute('tabindex')) {
3126 this.forElement_.setAttribute('tabindex', '0');
3128 this.boundMouseEnterHandler = this.handleMouseEnter_.bind(this);
3129 this.boundMouseLeaveAndScrollHandler = this.hideTooltip_.bind(this);
3130 this.forElement_.addEventListener('mouseenter', this.boundMouseEnterHandler, false);
3131 this.forElement_.addEventListener('touchend', this.boundMouseEnterHandler, false);
3132 this.forElement_.addEventListener('mouseleave', this.boundMouseLeaveAndScrollHandler, false);
3133 window.addEventListener('scroll', this.boundMouseLeaveAndScrollHandler, true);
3134 window.addEventListener('touchstart', this.boundMouseLeaveAndScrollHandler);
3138 // The component registers itself. It can assume componentHandler is available
3139 // in the global scope.
3140 componentHandler.register({
3141 constructor: MaterialTooltip,
3142 classAsString: 'MaterialTooltip',
3143 cssClass: 'mdl-tooltip'
3147 * Copyright 2015 Google Inc. All Rights Reserved.
3149 * Licensed under the Apache License, Version 2.0 (the "License");
3150 * you may not use this file except in compliance with the License.
3151 * You may obtain a copy of the License at
3153 * http://www.apache.org/licenses/LICENSE-2.0
3155 * Unless required by applicable law or agreed to in writing, software
3156 * distributed under the License is distributed on an "AS IS" BASIS,
3157 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
3158 * See the License for the specific language governing permissions and
3159 * limitations under the License.
3162 * Class constructor for Layout MDL component.
3163 * Implements MDL component design pattern defined at:
3164 * https://github.com/jasonmayes/mdl-component-design-pattern
3167 * @param {HTMLElement} element The element that will be upgraded.
3169 var MaterialLayout = function MaterialLayout(element) {
3170 this.element_ = element;
3171 // Initialize instance.
3174 window['MaterialLayout'] = MaterialLayout;
3176 * Store constants in one place so they can be updated easily.
3178 * @enum {string | number}
3181 MaterialLayout.prototype.Constant_ = {
3182 MAX_WIDTH: '(max-width: 1024px)',
3183 TAB_SCROLL_PIXELS: 100,
3184 RESIZE_TIMEOUT: 100,
3185 MENU_ICON: '',
3186 CHEVRON_LEFT: 'chevron_left',
3187 CHEVRON_RIGHT: 'chevron_right'
3190 * Keycodes, for code readability.
3195 MaterialLayout.prototype.Keycodes_ = {
3206 MaterialLayout.prototype.Mode_ = {
3213 * Store strings for class names defined by this component that are used in
3214 * JavaScript. This allows us to simply change it in one place should we
3215 * decide to modify at a later date.
3220 MaterialLayout.prototype.CssClasses_ = {
3221 CONTAINER: 'mdl-layout__container',
3222 HEADER: 'mdl-layout__header',
3223 DRAWER: 'mdl-layout__drawer',
3224 CONTENT: 'mdl-layout__content',
3225 DRAWER_BTN: 'mdl-layout__drawer-button',
3226 ICON: 'material-icons',
3227 JS_RIPPLE_EFFECT: 'mdl-js-ripple-effect',
3228 RIPPLE_CONTAINER: 'mdl-layout__tab-ripple-container',
3229 RIPPLE: 'mdl-ripple',
3230 RIPPLE_IGNORE_EVENTS: 'mdl-js-ripple-effect--ignore-events',
3231 HEADER_SEAMED: 'mdl-layout__header--seamed',
3232 HEADER_WATERFALL: 'mdl-layout__header--waterfall',
3233 HEADER_SCROLL: 'mdl-layout__header--scroll',
3234 FIXED_HEADER: 'mdl-layout--fixed-header',
3235 OBFUSCATOR: 'mdl-layout__obfuscator',
3236 TAB_BAR: 'mdl-layout__tab-bar',
3237 TAB_CONTAINER: 'mdl-layout__tab-bar-container',
3238 TAB: 'mdl-layout__tab',
3239 TAB_BAR_BUTTON: 'mdl-layout__tab-bar-button',
3240 TAB_BAR_LEFT_BUTTON: 'mdl-layout__tab-bar-left-button',
3241 TAB_BAR_RIGHT_BUTTON: 'mdl-layout__tab-bar-right-button',
3242 TAB_MANUAL_SWITCH: 'mdl-layout__tab-manual-switch',
3243 PANEL: 'mdl-layout__tab-panel',
3244 HAS_DRAWER: 'has-drawer',
3245 HAS_TABS: 'has-tabs',
3246 HAS_SCROLLING_HEADER: 'has-scrolling-header',
3247 CASTING_SHADOW: 'is-casting-shadow',
3248 IS_COMPACT: 'is-compact',
3249 IS_SMALL_SCREEN: 'is-small-screen',
3250 IS_DRAWER_OPEN: 'is-visible',
3251 IS_ACTIVE: 'is-active',
3252 IS_UPGRADED: 'is-upgraded',
3253 IS_ANIMATING: 'is-animating',
3254 ON_LARGE_SCREEN: 'mdl-layout--large-screen-only',
3255 ON_SMALL_SCREEN: 'mdl-layout--small-screen-only'
3258 * Handles scrolling on the content.
3262 MaterialLayout.prototype.contentScrollHandler_ = function () {
3263 if (this.header_.classList.contains(this.CssClasses_.IS_ANIMATING)) {
3266 var headerVisible = !this.element_.classList.contains(this.CssClasses_.IS_SMALL_SCREEN) || this.element_.classList.contains(this.CssClasses_.FIXED_HEADER);
3267 if (this.content_.scrollTop > 0 && !this.header_.classList.contains(this.CssClasses_.IS_COMPACT)) {
3268 this.header_.classList.add(this.CssClasses_.CASTING_SHADOW);
3269 this.header_.classList.add(this.CssClasses_.IS_COMPACT);
3270 if (headerVisible) {
3271 this.header_.classList.add(this.CssClasses_.IS_ANIMATING);
3273 } else if (this.content_.scrollTop <= 0 && this.header_.classList.contains(this.CssClasses_.IS_COMPACT)) {
3274 this.header_.classList.remove(this.CssClasses_.CASTING_SHADOW);
3275 this.header_.classList.remove(this.CssClasses_.IS_COMPACT);
3276 if (headerVisible) {
3277 this.header_.classList.add(this.CssClasses_.IS_ANIMATING);
3282 * Handles a keyboard event on the drawer.
3284 * @param {Event} evt The event that fired.
3287 MaterialLayout.prototype.keyboardEventHandler_ = function (evt) {
3288 // Only react when the drawer is open.
3289 if (evt.keyCode === this.Keycodes_.ESCAPE && this.drawer_.classList.contains(this.CssClasses_.IS_DRAWER_OPEN)) {
3290 this.toggleDrawer();
3294 * Handles changes in screen size.
3298 MaterialLayout.prototype.screenSizeHandler_ = function () {
3299 if (this.screenSizeMediaQuery_.matches) {
3300 this.element_.classList.add(this.CssClasses_.IS_SMALL_SCREEN);
3302 this.element_.classList.remove(this.CssClasses_.IS_SMALL_SCREEN);
3303 // Collapse drawer (if any) when moving to a large screen size.
3305 this.drawer_.classList.remove(this.CssClasses_.IS_DRAWER_OPEN);
3306 this.obfuscator_.classList.remove(this.CssClasses_.IS_DRAWER_OPEN);
3311 * Handles events of drawer button.
3313 * @param {Event} evt The event that fired.
3316 MaterialLayout.prototype.drawerToggleHandler_ = function (evt) {
3317 if (evt && evt.type === 'keydown') {
3318 if (evt.keyCode === this.Keycodes_.SPACE || evt.keyCode === this.Keycodes_.ENTER) {
3319 // prevent scrolling in drawer nav
3320 evt.preventDefault();
3322 // prevent other keys
3326 this.toggleDrawer();
3329 * Handles (un)setting the `is-animating` class
3333 MaterialLayout.prototype.headerTransitionEndHandler_ = function () {
3334 this.header_.classList.remove(this.CssClasses_.IS_ANIMATING);
3337 * Handles expanding the header on click
3341 MaterialLayout.prototype.headerClickHandler_ = function () {
3342 if (this.header_.classList.contains(this.CssClasses_.IS_COMPACT)) {
3343 this.header_.classList.remove(this.CssClasses_.IS_COMPACT);
3344 this.header_.classList.add(this.CssClasses_.IS_ANIMATING);
3348 * Reset tab state, dropping active classes
3352 MaterialLayout.prototype.resetTabState_ = function (tabBar) {
3353 for (var k = 0; k < tabBar.length; k++) {
3354 tabBar[k].classList.remove(this.CssClasses_.IS_ACTIVE);
3358 * Reset panel state, droping active classes
3362 MaterialLayout.prototype.resetPanelState_ = function (panels) {
3363 for (var j = 0; j < panels.length; j++) {
3364 panels[j].classList.remove(this.CssClasses_.IS_ACTIVE);
3368 * Toggle drawer state
3372 MaterialLayout.prototype.toggleDrawer = function () {
3373 var drawerButton = this.element_.querySelector('.' + this.CssClasses_.DRAWER_BTN);
3374 this.drawer_.classList.toggle(this.CssClasses_.IS_DRAWER_OPEN);
3375 this.obfuscator_.classList.toggle(this.CssClasses_.IS_DRAWER_OPEN);
3376 // Set accessibility properties.
3377 if (this.drawer_.classList.contains(this.CssClasses_.IS_DRAWER_OPEN)) {
3378 this.drawer_.setAttribute('aria-hidden', 'false');
3379 drawerButton.setAttribute('aria-expanded', 'true');
3381 this.drawer_.setAttribute('aria-hidden', 'true');
3382 drawerButton.setAttribute('aria-expanded', 'false');
3385 MaterialLayout.prototype['toggleDrawer'] = MaterialLayout.prototype.toggleDrawer;
3387 * Initialize element.
3389 MaterialLayout.prototype.init = function () {
3390 if (this.element_) {
3391 var container = document.createElement('div');
3392 container.classList.add(this.CssClasses_.CONTAINER);
3393 var focusedElement = this.element_.querySelector(':focus');
3394 this.element_.parentElement.insertBefore(container, this.element_);
3395 this.element_.parentElement.removeChild(this.element_);
3396 container.appendChild(this.element_);
3397 if (focusedElement) {
3398 focusedElement.focus();
3400 var directChildren = this.element_.childNodes;
3401 var numChildren = directChildren.length;
3402 for (var c = 0; c < numChildren; c++) {
3403 var child = directChildren[c];
3404 if (child.classList && child.classList.contains(this.CssClasses_.HEADER)) {
3405 this.header_ = child;
3407 if (child.classList && child.classList.contains(this.CssClasses_.DRAWER)) {
3408 this.drawer_ = child;
3410 if (child.classList && child.classList.contains(this.CssClasses_.CONTENT)) {
3411 this.content_ = child;
3414 window.addEventListener('pageshow', function (e) {
3416 // when page is loaded from back/forward cache
3417 // trigger repaint to let layout scroll in safari
3418 this.element_.style.overflowY = 'hidden';
3419 requestAnimationFrame(function () {
3420 this.element_.style.overflowY = '';
3423 }.bind(this), false);
3425 this.tabBar_ = this.header_.querySelector('.' + this.CssClasses_.TAB_BAR);
3427 var mode = this.Mode_.STANDARD;
3429 if (this.header_.classList.contains(this.CssClasses_.HEADER_SEAMED)) {
3430 mode = this.Mode_.SEAMED;
3431 } else if (this.header_.classList.contains(this.CssClasses_.HEADER_WATERFALL)) {
3432 mode = this.Mode_.WATERFALL;
3433 this.header_.addEventListener('transitionend', this.headerTransitionEndHandler_.bind(this));
3434 this.header_.addEventListener('click', this.headerClickHandler_.bind(this));
3435 } else if (this.header_.classList.contains(this.CssClasses_.HEADER_SCROLL)) {
3436 mode = this.Mode_.SCROLL;
3437 container.classList.add(this.CssClasses_.HAS_SCROLLING_HEADER);
3439 if (mode === this.Mode_.STANDARD) {
3440 this.header_.classList.add(this.CssClasses_.CASTING_SHADOW);
3442 this.tabBar_.classList.add(this.CssClasses_.CASTING_SHADOW);
3444 } else if (mode === this.Mode_.SEAMED || mode === this.Mode_.SCROLL) {
3445 this.header_.classList.remove(this.CssClasses_.CASTING_SHADOW);
3447 this.tabBar_.classList.remove(this.CssClasses_.CASTING_SHADOW);
3449 } else if (mode === this.Mode_.WATERFALL) {
3450 // Add and remove shadows depending on scroll position.
3451 // Also add/remove auxiliary class for styling of the compact version of
3453 this.content_.addEventListener('scroll', this.contentScrollHandler_.bind(this));
3454 this.contentScrollHandler_();
3457 // Add drawer toggling button to our layout, if we have an openable drawer.
3459 var drawerButton = this.element_.querySelector('.' + this.CssClasses_.DRAWER_BTN);
3460 if (!drawerButton) {
3461 drawerButton = document.createElement('div');
3462 drawerButton.setAttribute('aria-expanded', 'false');
3463 drawerButton.setAttribute('role', 'button');
3464 drawerButton.setAttribute('tabindex', '0');
3465 drawerButton.classList.add(this.CssClasses_.DRAWER_BTN);
3466 var drawerButtonIcon = document.createElement('i');
3467 drawerButtonIcon.classList.add(this.CssClasses_.ICON);
3468 drawerButtonIcon.innerHTML = this.Constant_.MENU_ICON;
3469 drawerButton.appendChild(drawerButtonIcon);
3471 if (this.drawer_.classList.contains(this.CssClasses_.ON_LARGE_SCREEN)) {
3472 //If drawer has ON_LARGE_SCREEN class then add it to the drawer toggle button as well.
3473 drawerButton.classList.add(this.CssClasses_.ON_LARGE_SCREEN);
3474 } else if (this.drawer_.classList.contains(this.CssClasses_.ON_SMALL_SCREEN)) {
3475 //If drawer has ON_SMALL_SCREEN class then add it to the drawer toggle button as well.
3476 drawerButton.classList.add(this.CssClasses_.ON_SMALL_SCREEN);
3478 drawerButton.addEventListener('click', this.drawerToggleHandler_.bind(this));
3479 drawerButton.addEventListener('keydown', this.drawerToggleHandler_.bind(this));
3480 // Add a class if the layout has a drawer, for altering the left padding.
3481 // Adds the HAS_DRAWER to the elements since this.header_ may or may
3483 this.element_.classList.add(this.CssClasses_.HAS_DRAWER);
3484 // If we have a fixed header, add the button to the header rather than
3486 if (this.element_.classList.contains(this.CssClasses_.FIXED_HEADER)) {
3487 this.header_.insertBefore(drawerButton, this.header_.firstChild);
3489 this.element_.insertBefore(drawerButton, this.content_);
3491 var obfuscator = document.createElement('div');
3492 obfuscator.classList.add(this.CssClasses_.OBFUSCATOR);
3493 this.element_.appendChild(obfuscator);
3494 obfuscator.addEventListener('click', this.drawerToggleHandler_.bind(this));
3495 this.obfuscator_ = obfuscator;
3496 this.drawer_.addEventListener('keydown', this.keyboardEventHandler_.bind(this));
3497 this.drawer_.setAttribute('aria-hidden', 'true');
3499 // Keep an eye on screen size, and add/remove auxiliary class for styling
3500 // of small screens.
3501 this.screenSizeMediaQuery_ = window.matchMedia(this.Constant_.MAX_WIDTH);
3502 this.screenSizeMediaQuery_.addListener(this.screenSizeHandler_.bind(this));
3503 this.screenSizeHandler_();
3504 // Initialize tabs, if any.
3505 if (this.header_ && this.tabBar_) {
3506 this.element_.classList.add(this.CssClasses_.HAS_TABS);
3507 var tabContainer = document.createElement('div');
3508 tabContainer.classList.add(this.CssClasses_.TAB_CONTAINER);
3509 this.header_.insertBefore(tabContainer, this.tabBar_);
3510 this.header_.removeChild(this.tabBar_);
3511 var leftButton = document.createElement('div');
3512 leftButton.classList.add(this.CssClasses_.TAB_BAR_BUTTON);
3513 leftButton.classList.add(this.CssClasses_.TAB_BAR_LEFT_BUTTON);
3514 var leftButtonIcon = document.createElement('i');
3515 leftButtonIcon.classList.add(this.CssClasses_.ICON);
3516 leftButtonIcon.textContent = this.Constant_.CHEVRON_LEFT;
3517 leftButton.appendChild(leftButtonIcon);
3518 leftButton.addEventListener('click', function () {
3519 this.tabBar_.scrollLeft -= this.Constant_.TAB_SCROLL_PIXELS;
3521 var rightButton = document.createElement('div');
3522 rightButton.classList.add(this.CssClasses_.TAB_BAR_BUTTON);
3523 rightButton.classList.add(this.CssClasses_.TAB_BAR_RIGHT_BUTTON);
3524 var rightButtonIcon = document.createElement('i');
3525 rightButtonIcon.classList.add(this.CssClasses_.ICON);
3526 rightButtonIcon.textContent = this.Constant_.CHEVRON_RIGHT;
3527 rightButton.appendChild(rightButtonIcon);
3528 rightButton.addEventListener('click', function () {
3529 this.tabBar_.scrollLeft += this.Constant_.TAB_SCROLL_PIXELS;
3531 tabContainer.appendChild(leftButton);
3532 tabContainer.appendChild(this.tabBar_);
3533 tabContainer.appendChild(rightButton);
3534 // Add and remove tab buttons depending on scroll position and total
3536 var tabUpdateHandler = function () {
3537 if (this.tabBar_.scrollLeft > 0) {
3538 leftButton.classList.add(this.CssClasses_.IS_ACTIVE);
3540 leftButton.classList.remove(this.CssClasses_.IS_ACTIVE);
3542 if (this.tabBar_.scrollLeft < this.tabBar_.scrollWidth - this.tabBar_.offsetWidth) {
3543 rightButton.classList.add(this.CssClasses_.IS_ACTIVE);
3545 rightButton.classList.remove(this.CssClasses_.IS_ACTIVE);
3548 this.tabBar_.addEventListener('scroll', tabUpdateHandler);
3550 // Update tabs when the window resizes.
3551 var windowResizeHandler = function () {
3552 // Use timeouts to make sure it doesn't happen too often.
3553 if (this.resizeTimeoutId_) {
3554 clearTimeout(this.resizeTimeoutId_);
3556 this.resizeTimeoutId_ = setTimeout(function () {
3558 this.resizeTimeoutId_ = null;
3559 }.bind(this), this.Constant_.RESIZE_TIMEOUT);
3561 window.addEventListener('resize', windowResizeHandler);
3562 if (this.tabBar_.classList.contains(this.CssClasses_.JS_RIPPLE_EFFECT)) {
3563 this.tabBar_.classList.add(this.CssClasses_.RIPPLE_IGNORE_EVENTS);
3565 // Select element tabs, document panels
3566 var tabs = this.tabBar_.querySelectorAll('.' + this.CssClasses_.TAB);
3567 var panels = this.content_.querySelectorAll('.' + this.CssClasses_.PANEL);
3568 // Create new tabs for each tab element
3569 for (var i = 0; i < tabs.length; i++) {
3570 new MaterialLayoutTab(tabs[i], tabs, panels, this);
3573 this.element_.classList.add(this.CssClasses_.IS_UPGRADED);
3578 * Constructor for an individual tab.
3581 * @param {HTMLElement} tab The HTML element for the tab.
3582 * @param {!Array<HTMLElement>} tabs Array with HTML elements for all tabs.
3583 * @param {!Array<HTMLElement>} panels Array with HTML elements for all panels.
3584 * @param {MaterialLayout} layout The MaterialLayout object that owns the tab.
3586 function MaterialLayoutTab(tab, tabs, panels, layout) {
3588 * Auxiliary method to programmatically select a tab in the UI.
3590 function selectTab() {
3591 var href = tab.href.split('#')[1];
3592 var panel = layout.content_.querySelector('#' + href);
3593 layout.resetTabState_(tabs);
3594 layout.resetPanelState_(panels);
3595 tab.classList.add(layout.CssClasses_.IS_ACTIVE);
3596 panel.classList.add(layout.CssClasses_.IS_ACTIVE);
3599 if (layout.tabBar_.classList.contains(layout.CssClasses_.JS_RIPPLE_EFFECT)) {
3600 var rippleContainer = document.createElement('span');
3601 rippleContainer.classList.add(layout.CssClasses_.RIPPLE_CONTAINER);
3602 rippleContainer.classList.add(layout.CssClasses_.JS_RIPPLE_EFFECT);
3603 var ripple = document.createElement('span');
3604 ripple.classList.add(layout.CssClasses_.RIPPLE);
3605 rippleContainer.appendChild(ripple);
3606 tab.appendChild(rippleContainer);
3608 if (!layout.tabBar_.classList.contains(layout.CssClasses_.TAB_MANUAL_SWITCH)) {
3609 tab.addEventListener('click', function (e) {
3610 if (tab.getAttribute('href').charAt(0) === '#') {
3616 tab.show = selectTab;
3619 window['MaterialLayoutTab'] = MaterialLayoutTab;
3620 // The component registers itself. It can assume componentHandler is available
3621 // in the global scope.
3622 componentHandler.register({
3623 constructor: MaterialLayout,
3624 classAsString: 'MaterialLayout',
3625 cssClass: 'mdl-js-layout'
3629 * Copyright 2015 Google Inc. All Rights Reserved.
3631 * Licensed under the Apache License, Version 2.0 (the "License");
3632 * you may not use this file except in compliance with the License.
3633 * You may obtain a copy of the License at
3635 * http://www.apache.org/licenses/LICENSE-2.0
3637 * Unless required by applicable law or agreed to in writing, software
3638 * distributed under the License is distributed on an "AS IS" BASIS,
3639 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
3640 * See the License for the specific language governing permissions and
3641 * limitations under the License.
3644 * Class constructor for Data Table Card MDL component.
3645 * Implements MDL component design pattern defined at:
3646 * https://github.com/jasonmayes/mdl-component-design-pattern
3649 * @param {Element} element The element that will be upgraded.
3651 var MaterialDataTable = function MaterialDataTable(element) {
3652 this.element_ = element;
3653 // Initialize instance.
3656 window['MaterialDataTable'] = MaterialDataTable;
3658 * Store constants in one place so they can be updated easily.
3660 * @enum {string | number}
3663 MaterialDataTable.prototype.Constant_ = {};
3665 * Store strings for class names defined by this component that are used in
3666 * JavaScript. This allows us to simply change it in one place should we
3667 * decide to modify at a later date.
3672 MaterialDataTable.prototype.CssClasses_ = {
3673 DATA_TABLE: 'mdl-data-table',
3674 SELECTABLE: 'mdl-data-table--selectable',
3675 SELECT_ELEMENT: 'mdl-data-table__select',
3676 IS_SELECTED: 'is-selected',
3677 IS_UPGRADED: 'is-upgraded'
3680 * Generates and returns a function that toggles the selection state of a
3681 * single row (or multiple rows).
3683 * @param {Element} checkbox Checkbox that toggles the selection state.
3684 * @param {Element} row Row to toggle when checkbox changes.
3685 * @param {(Array<Object>|NodeList)=} opt_rows Rows to toggle when checkbox changes.
3688 MaterialDataTable.prototype.selectRow_ = function (checkbox, row, opt_rows) {
3690 return function () {
3691 if (checkbox.checked) {
3692 row.classList.add(this.CssClasses_.IS_SELECTED);
3694 row.classList.remove(this.CssClasses_.IS_SELECTED);
3699 return function () {
3702 if (checkbox.checked) {
3703 for (i = 0; i < opt_rows.length; i++) {
3704 el = opt_rows[i].querySelector('td').querySelector('.mdl-checkbox');
3705 el['MaterialCheckbox'].check();
3706 opt_rows[i].classList.add(this.CssClasses_.IS_SELECTED);
3709 for (i = 0; i < opt_rows.length; i++) {
3710 el = opt_rows[i].querySelector('td').querySelector('.mdl-checkbox');
3711 el['MaterialCheckbox'].uncheck();
3712 opt_rows[i].classList.remove(this.CssClasses_.IS_SELECTED);
3719 * Creates a checkbox for a single or or multiple rows and hooks up the
3722 * @param {Element} row Row to toggle when checkbox changes.
3723 * @param {(Array<Object>|NodeList)=} opt_rows Rows to toggle when checkbox changes.
3726 MaterialDataTable.prototype.createCheckbox_ = function (row, opt_rows) {
3727 var label = document.createElement('label');
3728 var labelClasses = [
3731 'mdl-js-ripple-effect',
3732 this.CssClasses_.SELECT_ELEMENT
3734 label.className = labelClasses.join(' ');
3735 var checkbox = document.createElement('input');
3736 checkbox.type = 'checkbox';
3737 checkbox.classList.add('mdl-checkbox__input');
3739 checkbox.checked = row.classList.contains(this.CssClasses_.IS_SELECTED);
3740 checkbox.addEventListener('change', this.selectRow_(checkbox, row));
3741 } else if (opt_rows) {
3742 checkbox.addEventListener('change', this.selectRow_(checkbox, null, opt_rows));
3744 label.appendChild(checkbox);
3745 componentHandler.upgradeElement(label, 'MaterialCheckbox');
3749 * Initialize element.
3751 MaterialDataTable.prototype.init = function () {
3752 if (this.element_) {
3753 var firstHeader = this.element_.querySelector('th');
3754 var bodyRows = Array.prototype.slice.call(this.element_.querySelectorAll('tbody tr'));
3755 var footRows = Array.prototype.slice.call(this.element_.querySelectorAll('tfoot tr'));
3756 var rows = bodyRows.concat(footRows);
3757 if (this.element_.classList.contains(this.CssClasses_.SELECTABLE)) {
3758 var th = document.createElement('th');
3759 var headerCheckbox = this.createCheckbox_(null, rows);
3760 th.appendChild(headerCheckbox);
3761 firstHeader.parentElement.insertBefore(th, firstHeader);
3762 for (var i = 0; i < rows.length; i++) {
3763 var firstCell = rows[i].querySelector('td');
3765 var td = document.createElement('td');
3766 if (rows[i].parentNode.nodeName.toUpperCase() === 'TBODY') {
3767 var rowCheckbox = this.createCheckbox_(rows[i]);
3768 td.appendChild(rowCheckbox);
3770 rows[i].insertBefore(td, firstCell);
3773 this.element_.classList.add(this.CssClasses_.IS_UPGRADED);
3777 // The component registers itself. It can assume componentHandler is available
3778 // in the global scope.
3779 componentHandler.register({
3780 constructor: MaterialDataTable,
3781 classAsString: 'MaterialDataTable',
3782 cssClass: 'mdl-js-data-table'
3786 * Copyright 2015 Google Inc. All Rights Reserved.
3788 * Licensed under the Apache License, Version 2.0 (the "License");
3789 * you may not use this file except in compliance with the License.
3790 * You may obtain a copy of the License at
3792 * http://www.apache.org/licenses/LICENSE-2.0
3794 * Unless required by applicable law or agreed to in writing, software
3795 * distributed under the License is distributed on an "AS IS" BASIS,
3796 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
3797 * See the License for the specific language governing permissions and
3798 * limitations under the License.
3801 * Class constructor for Ripple MDL component.
3802 * Implements MDL component design pattern defined at:
3803 * https://github.com/jasonmayes/mdl-component-design-pattern
3806 * @param {HTMLElement} element The element that will be upgraded.
3808 var MaterialRipple = function MaterialRipple(element) {
3809 this.element_ = element;
3810 // Initialize instance.
3813 window['MaterialRipple'] = MaterialRipple;
3815 * Store constants in one place so they can be updated easily.
3817 * @enum {string | number}
3820 MaterialRipple.prototype.Constant_ = {
3821 INITIAL_SCALE: 'scale(0.0001, 0.0001)',
3822 INITIAL_SIZE: '1px',
3823 INITIAL_OPACITY: '0.4',
3828 * Store strings for class names defined by this component that are used in
3829 * JavaScript. This allows us to simply change it in one place should we
3830 * decide to modify at a later date.
3835 MaterialRipple.prototype.CssClasses_ = {
3836 RIPPLE_CENTER: 'mdl-ripple--center',
3837 RIPPLE_EFFECT_IGNORE_EVENTS: 'mdl-js-ripple-effect--ignore-events',
3838 RIPPLE: 'mdl-ripple',
3839 IS_ANIMATING: 'is-animating',
3840 IS_VISIBLE: 'is-visible'
3843 * Handle mouse / finger down on element.
3845 * @param {Event} event The event that fired.
3848 MaterialRipple.prototype.downHandler_ = function (event) {
3849 if (!this.rippleElement_.style.width && !this.rippleElement_.style.height) {
3850 var rect = this.element_.getBoundingClientRect();
3851 this.boundHeight = rect.height;
3852 this.boundWidth = rect.width;
3853 this.rippleSize_ = Math.sqrt(rect.width * rect.width + rect.height * rect.height) * 2 + 2;
3854 this.rippleElement_.style.width = this.rippleSize_ + 'px';
3855 this.rippleElement_.style.height = this.rippleSize_ + 'px';
3857 this.rippleElement_.classList.add(this.CssClasses_.IS_VISIBLE);
3858 if (event.type === 'mousedown' && this.ignoringMouseDown_) {
3859 this.ignoringMouseDown_ = false;
3861 if (event.type === 'touchstart') {
3862 this.ignoringMouseDown_ = true;
3864 var frameCount = this.getFrameCount();
3865 if (frameCount > 0) {
3868 this.setFrameCount(1);
3869 var bound = event.currentTarget.getBoundingClientRect();
3872 // Check if we are handling a keyboard click.
3873 if (event.clientX === 0 && event.clientY === 0) {
3874 x = Math.round(bound.width / 2);
3875 y = Math.round(bound.height / 2);
3877 var clientX = event.clientX !== undefined ? event.clientX : event.touches[0].clientX;
3878 var clientY = event.clientY !== undefined ? event.clientY : event.touches[0].clientY;
3879 x = Math.round(clientX - bound.left);
3880 y = Math.round(clientY - bound.top);
3882 this.setRippleXY(x, y);
3883 this.setRippleStyles(true);
3884 window.requestAnimationFrame(this.animFrameHandler.bind(this));
3888 * Handle mouse / finger up on element.
3890 * @param {Event} event The event that fired.
3893 MaterialRipple.prototype.upHandler_ = function (event) {
3894 // Don't fire for the artificial "mouseup" generated by a double-click.
3895 if (event && event.detail !== 2) {
3896 // Allow a repaint to occur before removing this class, so the animation
3897 // shows for tap events, which seem to trigger a mouseup too soon after
3899 window.setTimeout(function () {
3900 this.rippleElement_.classList.remove(this.CssClasses_.IS_VISIBLE);
3905 * Initialize element.
3907 MaterialRipple.prototype.init = function () {
3908 if (this.element_) {
3909 var recentering = this.element_.classList.contains(this.CssClasses_.RIPPLE_CENTER);
3910 if (!this.element_.classList.contains(this.CssClasses_.RIPPLE_EFFECT_IGNORE_EVENTS)) {
3911 this.rippleElement_ = this.element_.querySelector('.' + this.CssClasses_.RIPPLE);
3912 this.frameCount_ = 0;
3913 this.rippleSize_ = 0;
3916 // Touch start produces a compat mouse down event, which would cause a
3917 // second ripples. To avoid that, we use this property to ignore the first
3918 // mouse down after a touch start.
3919 this.ignoringMouseDown_ = false;
3920 this.boundDownHandler = this.downHandler_.bind(this);
3921 this.element_.addEventListener('mousedown', this.boundDownHandler);
3922 this.element_.addEventListener('touchstart', this.boundDownHandler);
3923 this.boundUpHandler = this.upHandler_.bind(this);
3924 this.element_.addEventListener('mouseup', this.boundUpHandler);
3925 this.element_.addEventListener('mouseleave', this.boundUpHandler);
3926 this.element_.addEventListener('touchend', this.boundUpHandler);
3927 this.element_.addEventListener('blur', this.boundUpHandler);
3929 * Getter for frameCount_.
3930 * @return {number} the frame count.
3932 this.getFrameCount = function () {
3933 return this.frameCount_;
3936 * Setter for frameCount_.
3937 * @param {number} fC the frame count.
3939 this.setFrameCount = function (fC) {
3940 this.frameCount_ = fC;
3943 * Getter for rippleElement_.
3944 * @return {Element} the ripple element.
3946 this.getRippleElement = function () {
3947 return this.rippleElement_;
3950 * Sets the ripple X and Y coordinates.
3951 * @param {number} newX the new X coordinate
3952 * @param {number} newY the new Y coordinate
3954 this.setRippleXY = function (newX, newY) {
3959 * Sets the ripple styles.
3960 * @param {boolean} start whether or not this is the start frame.
3962 this.setRippleStyles = function (start) {
3963 if (this.rippleElement_ !== null) {
3964 var transformString;
3967 var offset = 'translate(' + this.x_ + 'px, ' + this.y_ + 'px)';
3969 scale = this.Constant_.INITIAL_SCALE;
3970 size = this.Constant_.INITIAL_SIZE;
3972 scale = this.Constant_.FINAL_SCALE;
3973 size = this.rippleSize_ + 'px';
3975 offset = 'translate(' + this.boundWidth / 2 + 'px, ' + this.boundHeight / 2 + 'px)';
3978 transformString = 'translate(-50%, -50%) ' + offset + scale;
3979 this.rippleElement_.style.webkitTransform = transformString;
3980 this.rippleElement_.style.msTransform = transformString;
3981 this.rippleElement_.style.transform = transformString;
3983 this.rippleElement_.classList.remove(this.CssClasses_.IS_ANIMATING);
3985 this.rippleElement_.classList.add(this.CssClasses_.IS_ANIMATING);
3990 * Handles an animation frame.
3992 this.animFrameHandler = function () {
3993 if (this.frameCount_-- > 0) {
3994 window.requestAnimationFrame(this.animFrameHandler.bind(this));
3996 this.setRippleStyles(false);
4002 // The component registers itself. It can assume componentHandler is available
4003 // in the global scope.
4004 componentHandler.register({
4005 constructor: MaterialRipple,
4006 classAsString: 'MaterialRipple',
4007 cssClass: 'mdl-js-ripple-effect',