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) {},
44 * Upgrades a specific element rather than all in the DOM.
46 * @param {!Element} element The element we wish to upgrade.
47 * @param {string=} optJsClass Optional name of the class we want to upgrade
50 upgradeElement: function(element, optJsClass) {},
52 * Upgrades a specific list of elements rather than all in the DOM.
54 * @param {!Element|!Array<!Element>|!NodeList|!HTMLCollection} elements
55 * The elements we wish to upgrade.
57 upgradeElements: function(elements) {},
59 * Upgrades all registered components found in the current DOM. This is
60 * automatically called on window load.
62 upgradeAllRegistered: function() {},
64 * Allows user to be alerted to any upgrades that are performed for a given
67 * @param {string} jsClass The class name of the MDL component we wish
68 * to hook into for any upgrades performed.
69 * @param {function(!HTMLElement)} callback The function to call upon an
70 * upgrade. This function should expect 1 parameter - the HTMLElement which
73 registerUpgradedCallback: function(jsClass, callback) {},
75 * Registers a class for future use and attempts to upgrade existing DOM.
77 * @param {componentHandler.ComponentConfigPublic} config the registration configuration
79 register: function(config) {},
81 * Downgrade either a given node, an array of nodes, or a NodeList.
83 * @param {!Node|!Array<!Node>|!NodeList} nodes
85 downgradeElements: function(nodes) {}
88 componentHandler = (function() {
91 /** @type {!Array<componentHandler.ComponentConfig>} */
92 var registeredComponents_ = [];
94 /** @type {!Array<componentHandler.Component>} */
95 var createdComponents_ = [];
97 var componentConfigProperty_ = 'mdlComponentConfigInternal_';
100 * Searches registered components for a class we are interested in using.
101 * Optionally replaces a match with passed object if specified.
103 * @param {string} name The name of a class we want to use.
104 * @param {componentHandler.ComponentConfig=} optReplace Optional object to replace match with.
105 * @return {!Object|boolean}
108 function findRegisteredClass_(name, optReplace) {
109 for (var i = 0; i < registeredComponents_.length; i++) {
110 if (registeredComponents_[i].className === name) {
111 if (typeof optReplace !== 'undefined') {
112 registeredComponents_[i] = optReplace;
114 return registeredComponents_[i];
121 * Returns an array of the classNames of the upgraded classes on the element.
123 * @param {!Element} element The element to fetch data from.
124 * @return {!Array<string>}
127 function getUpgradedListOfElement_(element) {
128 var dataUpgraded = element.getAttribute('data-upgraded');
129 // Use `['']` as default value to conform the `,name,name...` style.
130 return dataUpgraded === null ? [''] : dataUpgraded.split(',');
134 * Returns true if the given element has already been upgraded for the given
137 * @param {!Element} element The element we want to check.
138 * @param {string} jsClass The class to check for.
142 function isElementUpgraded_(element, jsClass) {
143 var upgradedList = getUpgradedListOfElement_(element);
144 return upgradedList.indexOf(jsClass) !== -1;
148 * Create an event object.
150 * @param {string} eventType The type name of the event.
151 * @param {boolean} bubbles Whether the event should bubble up the DOM.
152 * @param {boolean} cancelable Whether the event can be canceled.
155 function createEvent_(eventType, bubbles, cancelable) {
156 if ('CustomEvent' in window && typeof window.CustomEvent === 'function') {
157 return new CustomEvent(eventType, {
159 cancelable: cancelable
162 var ev = document.createEvent('Events');
163 ev.initEvent(eventType, bubbles, cancelable);
169 * Searches existing DOM for elements of our component type and upgrades them
170 * if they have not already been upgraded.
172 * @param {string=} optJsClass the programatic name of the element class we
173 * need to create a new instance of.
174 * @param {string=} optCssClass the name of the CSS class elements of this
177 function upgradeDomInternal(optJsClass, optCssClass) {
178 if (typeof optJsClass === 'undefined' &&
179 typeof optCssClass === 'undefined') {
180 for (var i = 0; i < registeredComponents_.length; i++) {
181 upgradeDomInternal(registeredComponents_[i].className,
182 registeredComponents_[i].cssClass);
185 var jsClass = /** @type {string} */ (optJsClass);
186 if (typeof optCssClass === 'undefined') {
187 var registeredClass = findRegisteredClass_(jsClass);
188 if (registeredClass) {
189 optCssClass = registeredClass.cssClass;
193 var elements = document.querySelectorAll('.' + optCssClass);
194 for (var n = 0; n < elements.length; n++) {
195 upgradeElementInternal(elements[n], jsClass);
201 * Upgrades a specific element rather than all in the DOM.
203 * @param {!Element} element The element we wish to upgrade.
204 * @param {string=} optJsClass Optional name of the class we want to upgrade
207 function upgradeElementInternal(element, optJsClass) {
208 // Verify argument type.
209 if (!(typeof element === 'object' && element instanceof Element)) {
210 throw new Error('Invalid argument provided to upgrade MDL element.');
212 // Allow upgrade to be canceled by canceling emitted event.
213 var upgradingEv = createEvent_('mdl-componentupgrading', true, true);
214 element.dispatchEvent(upgradingEv);
215 if (upgradingEv.defaultPrevented) {
219 var upgradedList = getUpgradedListOfElement_(element);
220 var classesToUpgrade = [];
221 // If jsClass is not provided scan the registered components to find the
222 // ones matching the element's CSS classList.
224 var classList = element.classList;
225 registeredComponents_.forEach(function(component) {
226 // Match CSS & Not to be upgraded & Not upgraded.
227 if (classList.contains(component.cssClass) &&
228 classesToUpgrade.indexOf(component) === -1 &&
229 !isElementUpgraded_(element, component.className)) {
230 classesToUpgrade.push(component);
233 } else if (!isElementUpgraded_(element, optJsClass)) {
234 classesToUpgrade.push(findRegisteredClass_(optJsClass));
237 // Upgrade the element for each classes.
238 for (var i = 0, n = classesToUpgrade.length, registeredClass; i < n; i++) {
239 registeredClass = classesToUpgrade[i];
240 if (registeredClass) {
241 // Mark element as upgraded.
242 upgradedList.push(registeredClass.className);
243 element.setAttribute('data-upgraded', upgradedList.join(','));
244 var instance = new registeredClass.classConstructor(element);
245 instance[componentConfigProperty_] = registeredClass;
246 createdComponents_.push(instance);
247 // Call any callbacks the user has registered with this component type.
248 for (var j = 0, m = registeredClass.callbacks.length; j < m; j++) {
249 registeredClass.callbacks[j](element);
252 if (registeredClass.widget) {
253 // Assign per element instance for control over API
254 element[registeredClass.className] = instance;
258 'Unable to find a registered component for the given class.');
261 var upgradedEv = createEvent_('mdl-componentupgraded', true, false);
262 element.dispatchEvent(upgradedEv);
267 * Upgrades a specific list of elements rather than all in the DOM.
269 * @param {!Element|!Array<!Element>|!NodeList|!HTMLCollection} elements
270 * The elements we wish to upgrade.
272 function upgradeElementsInternal(elements) {
273 if (!Array.isArray(elements)) {
274 if (elements instanceof Element) {
275 elements = [elements];
277 elements = Array.prototype.slice.call(elements);
280 for (var i = 0, n = elements.length, element; i < n; i++) {
281 element = elements[i];
282 if (element instanceof HTMLElement) {
283 upgradeElementInternal(element);
284 if (element.children.length > 0) {
285 upgradeElementsInternal(element.children);
292 * Registers a class for future use and attempts to upgrade existing DOM.
294 * @param {componentHandler.ComponentConfigPublic} config
296 function registerInternal(config) {
297 // In order to support both Closure-compiled and uncompiled code accessing
298 // this method, we need to allow for both the dot and array syntax for
299 // property access. You'll therefore see the `foo.bar || foo['bar']`
300 // pattern repeated across this method.
301 var widgetMissing = (typeof config.widget === 'undefined' &&
302 typeof config['widget'] === 'undefined');
305 if (!widgetMissing) {
306 widget = config.widget || config['widget'];
309 var newConfig = /** @type {componentHandler.ComponentConfig} */ ({
310 classConstructor: config.constructor || config['constructor'],
311 className: config.classAsString || config['classAsString'],
312 cssClass: config.cssClass || config['cssClass'],
317 registeredComponents_.forEach(function(item) {
318 if (item.cssClass === newConfig.cssClass) {
319 throw new Error('The provided cssClass has already been registered: ' + item.cssClass);
321 if (item.className === newConfig.className) {
322 throw new Error('The provided className has already been registered');
326 if (config.constructor.prototype
327 .hasOwnProperty(componentConfigProperty_)) {
329 'MDL component classes must not have ' + componentConfigProperty_ +
330 ' defined as a property.');
333 var found = findRegisteredClass_(config.classAsString, newConfig);
336 registeredComponents_.push(newConfig);
341 * Allows user to be alerted to any upgrades that are performed for a given
344 * @param {string} jsClass The class name of the MDL component we wish
345 * to hook into for any upgrades performed.
346 * @param {function(!HTMLElement)} callback The function to call upon an
347 * upgrade. This function should expect 1 parameter - the HTMLElement which
350 function registerUpgradedCallbackInternal(jsClass, callback) {
351 var regClass = findRegisteredClass_(jsClass);
353 regClass.callbacks.push(callback);
358 * Upgrades all registered components found in the current DOM. This is
359 * automatically called on window load.
361 function upgradeAllRegisteredInternal() {
362 for (var n = 0; n < registeredComponents_.length; n++) {
363 upgradeDomInternal(registeredComponents_[n].className);
368 * Check the component for the downgrade method.
370 * Remove component from createdComponents list.
372 * @param {?componentHandler.Component} component
374 function deconstructComponentInternal(component) {
376 var componentIndex = createdComponents_.indexOf(component);
377 createdComponents_.splice(componentIndex, 1);
379 var upgrades = component.element_.getAttribute('data-upgraded').split(',');
380 var componentPlace = upgrades.indexOf(component[componentConfigProperty_].classAsString);
381 upgrades.splice(componentPlace, 1);
382 component.element_.setAttribute('data-upgraded', upgrades.join(','));
384 var ev = createEvent_('mdl-componentdowngraded', true, false);
385 component.element_.dispatchEvent(ev);
390 * Downgrade either a given node, an array of nodes, or a NodeList.
392 * @param {!Node|!Array<!Node>|!NodeList} nodes
394 function downgradeNodesInternal(nodes) {
396 * Auxiliary function to downgrade a single node.
397 * @param {!Node} node the node to be downgraded
399 var downgradeNode = function(node) {
400 createdComponents_.filter(function(item) {
401 return item.element_ === node;
402 }).forEach(deconstructComponentInternal);
404 if (nodes instanceof Array || nodes instanceof NodeList) {
405 for (var n = 0; n < nodes.length; n++) {
406 downgradeNode(nodes[n]);
408 } else if (nodes instanceof Node) {
409 downgradeNode(nodes);
411 throw new Error('Invalid argument provided to downgrade MDL nodes.');
415 // Now return the functions that should be made public with their publicly
418 upgradeDom: upgradeDomInternal,
419 upgradeElement: upgradeElementInternal,
420 upgradeElements: upgradeElementsInternal,
421 upgradeAllRegistered: upgradeAllRegisteredInternal,
422 registerUpgradedCallback: registerUpgradedCallbackInternal,
423 register: registerInternal,
424 downgradeElements: downgradeNodesInternal
429 * Describes the type of a registered component type managed by
430 * componentHandler. Provided for benefit of the Closure compiler.
433 * constructor: Function,
434 * classAsString: string,
436 * widget: (string|boolean|undefined)
439 componentHandler.ComponentConfigPublic; // jshint ignore:line
442 * Describes the type of a registered component type managed by
443 * componentHandler. Provided for benefit of the Closure compiler.
446 * constructor: !Function,
449 * widget: (string|boolean),
450 * callbacks: !Array<function(!HTMLElement)>
453 componentHandler.ComponentConfig; // jshint ignore:line
456 * Created component (i.e., upgraded element) type as managed by
457 * componentHandler. Provided for benefit of the Closure compiler.
460 * element_: !HTMLElement,
462 * classAsString: string,
467 componentHandler.Component; // jshint ignore:line
469 // Export all symbols, for the benefit of Closure compiler.
470 // No effect on uncompiled code.
471 componentHandler['upgradeDom'] = componentHandler.upgradeDom;
472 componentHandler['upgradeElement'] = componentHandler.upgradeElement;
473 componentHandler['upgradeElements'] = componentHandler.upgradeElements;
474 componentHandler['upgradeAllRegistered'] =
475 componentHandler.upgradeAllRegistered;
476 componentHandler['registerUpgradedCallback'] =
477 componentHandler.registerUpgradedCallback;
478 componentHandler['register'] = componentHandler.register;
479 componentHandler['downgradeElements'] = componentHandler.downgradeElements;
480 window.componentHandler = componentHandler;
481 window['componentHandler'] = componentHandler;
483 window.addEventListener('load', function() {
487 * Performs a "Cutting the mustard" test. If the browser supports the features
488 * tested, adds a mdl-js class to the <html> element. It then upgrades all MDL
489 * components requiring JavaScript.
491 if ('classList' in document.createElement('div') &&
492 'querySelector' in document &&
493 'addEventListener' in window && Array.prototype.forEach) {
494 document.documentElement.classList.add('mdl-js');
495 componentHandler.upgradeAllRegistered();
498 * Dummy function to avoid JS errors.
500 componentHandler.upgradeElement = function() {};
502 * Dummy function to avoid JS errors.
504 componentHandler.register = function() {};
508 // Source: https://github.com/darius/requestAnimationFrame/blob/master/requestAnimationFrame.js
509 // Adapted from https://gist.github.com/paulirish/1579671 which derived from
510 // http://paulirish.com/2011/requestanimationframe-for-smart-animating/
511 // http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating
512 // requestAnimationFrame polyfill by Erik Möller.
513 // Fixes from Paul Irish, Tino Zijdel, Andrew Mao, Klemen Slavič, Darius Bacon
518 * @return {number} the current Date
520 Date.now = function () {
521 return new Date().getTime();
523 Date['now'] = Date.now;
529 for (var i = 0; i < vendors.length && !window.requestAnimationFrame; ++i) {
531 window.requestAnimationFrame = window[vp + 'RequestAnimationFrame'];
532 window.cancelAnimationFrame = window[vp + 'CancelAnimationFrame'] || window[vp + 'CancelRequestAnimationFrame'];
533 window['requestAnimationFrame'] = window.requestAnimationFrame;
534 window['cancelAnimationFrame'] = window.cancelAnimationFrame;
536 if (/iP(ad|hone|od).*OS 6/.test(window.navigator.userAgent) || !window.requestAnimationFrame || !window.cancelAnimationFrame) {
539 * requestAnimationFrame polyfill.
540 * @param {!Function} callback the callback function.
542 window.requestAnimationFrame = function (callback) {
543 var now = Date.now();
544 var nextTime = Math.max(lastTime + 16, now);
545 return setTimeout(function () {
546 callback(lastTime = nextTime);
549 window.cancelAnimationFrame = clearTimeout;
550 window['requestAnimationFrame'] = window.requestAnimationFrame;
551 window['cancelAnimationFrame'] = window.cancelAnimationFrame;
555 * Copyright 2015 Google Inc. All Rights Reserved.
557 * Licensed under the Apache License, Version 2.0 (the "License");
558 * you may not use this file except in compliance with the License.
559 * You may obtain a copy of the License at
561 * http://www.apache.org/licenses/LICENSE-2.0
563 * Unless required by applicable law or agreed to in writing, software
564 * distributed under the License is distributed on an "AS IS" BASIS,
565 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
566 * See the License for the specific language governing permissions and
567 * limitations under the License.
570 * Class constructor for Button MDL component.
571 * Implements MDL component design pattern defined at:
572 * https://github.com/jasonmayes/mdl-component-design-pattern
574 * @param {HTMLElement} element The element that will be upgraded.
576 var MaterialButton = function MaterialButton(element) {
577 this.element_ = element;
578 // Initialize instance.
581 window['MaterialButton'] = MaterialButton;
583 * Store constants in one place so they can be updated easily.
585 * @enum {string | number}
588 MaterialButton.prototype.Constant_ = {};
590 * Store strings for class names defined by this component that are used in
591 * JavaScript. This allows us to simply change it in one place should we
592 * decide to modify at a later date.
597 MaterialButton.prototype.CssClasses_ = {
598 RIPPLE_EFFECT: 'mdl-js-ripple-effect',
599 RIPPLE_CONTAINER: 'mdl-button__ripple-container',
603 * Handle blur of element.
605 * @param {Event} event The event that fired.
608 MaterialButton.prototype.blurHandler_ = function (event) {
610 this.element_.blur();
619 MaterialButton.prototype.disable = function () {
620 this.element_.disabled = true;
622 MaterialButton.prototype['disable'] = MaterialButton.prototype.disable;
628 MaterialButton.prototype.enable = function () {
629 this.element_.disabled = false;
631 MaterialButton.prototype['enable'] = MaterialButton.prototype.enable;
633 * Initialize element.
635 MaterialButton.prototype.init = function () {
637 if (this.element_.classList.contains(this.CssClasses_.RIPPLE_EFFECT)) {
638 var rippleContainer = document.createElement('span');
639 rippleContainer.classList.add(this.CssClasses_.RIPPLE_CONTAINER);
640 this.rippleElement_ = document.createElement('span');
641 this.rippleElement_.classList.add(this.CssClasses_.RIPPLE);
642 rippleContainer.appendChild(this.rippleElement_);
643 this.boundRippleBlurHandler = this.blurHandler_.bind(this);
644 this.rippleElement_.addEventListener('mouseup', this.boundRippleBlurHandler);
645 this.element_.appendChild(rippleContainer);
647 this.boundButtonBlurHandler = this.blurHandler_.bind(this);
648 this.element_.addEventListener('mouseup', this.boundButtonBlurHandler);
649 this.element_.addEventListener('mouseleave', this.boundButtonBlurHandler);
652 // The component registers itself. It can assume componentHandler is available
653 // in the global scope.
654 componentHandler.register({
655 constructor: MaterialButton,
656 classAsString: 'MaterialButton',
657 cssClass: 'mdl-js-button',
662 * Copyright 2015 Google Inc. All Rights Reserved.
664 * Licensed under the Apache License, Version 2.0 (the "License");
665 * you may not use this file except in compliance with the License.
666 * You may obtain a copy of the License at
668 * http://www.apache.org/licenses/LICENSE-2.0
670 * Unless required by applicable law or agreed to in writing, software
671 * distributed under the License is distributed on an "AS IS" BASIS,
672 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
673 * See the License for the specific language governing permissions and
674 * limitations under the License.
677 * Class constructor for Checkbox MDL component.
678 * Implements MDL component design pattern defined at:
679 * https://github.com/jasonmayes/mdl-component-design-pattern
682 * @param {HTMLElement} element The element that will be upgraded.
684 var MaterialCheckbox = function MaterialCheckbox(element) {
685 this.element_ = element;
686 // Initialize instance.
689 window['MaterialCheckbox'] = MaterialCheckbox;
691 * Store constants in one place so they can be updated easily.
693 * @enum {string | number}
696 MaterialCheckbox.prototype.Constant_ = { TINY_TIMEOUT: 0.001 };
698 * Store strings for class names defined by this component that are used in
699 * JavaScript. This allows us to simply change it in one place should we
700 * decide to modify at a later date.
705 MaterialCheckbox.prototype.CssClasses_ = {
706 INPUT: 'mdl-checkbox__input',
707 BOX_OUTLINE: 'mdl-checkbox__box-outline',
708 FOCUS_HELPER: 'mdl-checkbox__focus-helper',
709 TICK_OUTLINE: 'mdl-checkbox__tick-outline',
710 RIPPLE_EFFECT: 'mdl-js-ripple-effect',
711 RIPPLE_IGNORE_EVENTS: 'mdl-js-ripple-effect--ignore-events',
712 RIPPLE_CONTAINER: 'mdl-checkbox__ripple-container',
713 RIPPLE_CENTER: 'mdl-ripple--center',
714 RIPPLE: 'mdl-ripple',
715 IS_FOCUSED: 'is-focused',
716 IS_DISABLED: 'is-disabled',
717 IS_CHECKED: 'is-checked',
718 IS_UPGRADED: 'is-upgraded'
721 * Handle change of state.
723 * @param {Event} event The event that fired.
726 MaterialCheckbox.prototype.onChange_ = function (event) {
727 this.updateClasses_();
730 * Handle focus of element.
732 * @param {Event} event The event that fired.
735 MaterialCheckbox.prototype.onFocus_ = function (event) {
736 this.element_.classList.add(this.CssClasses_.IS_FOCUSED);
739 * Handle lost focus of element.
741 * @param {Event} event The event that fired.
744 MaterialCheckbox.prototype.onBlur_ = function (event) {
745 this.element_.classList.remove(this.CssClasses_.IS_FOCUSED);
750 * @param {Event} event The event that fired.
753 MaterialCheckbox.prototype.onMouseUp_ = function (event) {
757 * Handle class updates.
761 MaterialCheckbox.prototype.updateClasses_ = function () {
762 this.checkDisabled();
763 this.checkToggleState();
770 MaterialCheckbox.prototype.blur_ = function () {
771 // TODO: figure out why there's a focus event being fired after our blur,
772 // so that we can avoid this hack.
773 window.setTimeout(function () {
774 this.inputElement_.blur();
775 }.bind(this), this.Constant_.TINY_TIMEOUT);
779 * Check the inputs toggle state and update display.
783 MaterialCheckbox.prototype.checkToggleState = function () {
784 if (this.inputElement_.checked) {
785 this.element_.classList.add(this.CssClasses_.IS_CHECKED);
787 this.element_.classList.remove(this.CssClasses_.IS_CHECKED);
790 MaterialCheckbox.prototype['checkToggleState'] = MaterialCheckbox.prototype.checkToggleState;
792 * Check the inputs disabled state and update display.
796 MaterialCheckbox.prototype.checkDisabled = function () {
797 if (this.inputElement_.disabled) {
798 this.element_.classList.add(this.CssClasses_.IS_DISABLED);
800 this.element_.classList.remove(this.CssClasses_.IS_DISABLED);
803 MaterialCheckbox.prototype['checkDisabled'] = MaterialCheckbox.prototype.checkDisabled;
809 MaterialCheckbox.prototype.disable = function () {
810 this.inputElement_.disabled = true;
811 this.updateClasses_();
813 MaterialCheckbox.prototype['disable'] = MaterialCheckbox.prototype.disable;
819 MaterialCheckbox.prototype.enable = function () {
820 this.inputElement_.disabled = false;
821 this.updateClasses_();
823 MaterialCheckbox.prototype['enable'] = MaterialCheckbox.prototype.enable;
829 MaterialCheckbox.prototype.check = function () {
830 this.inputElement_.checked = true;
831 this.updateClasses_();
833 MaterialCheckbox.prototype['check'] = MaterialCheckbox.prototype.check;
839 MaterialCheckbox.prototype.uncheck = function () {
840 this.inputElement_.checked = false;
841 this.updateClasses_();
843 MaterialCheckbox.prototype['uncheck'] = MaterialCheckbox.prototype.uncheck;
845 * Initialize element.
847 MaterialCheckbox.prototype.init = function () {
849 this.inputElement_ = this.element_.querySelector('.' + this.CssClasses_.INPUT);
850 var boxOutline = document.createElement('span');
851 boxOutline.classList.add(this.CssClasses_.BOX_OUTLINE);
852 var tickContainer = document.createElement('span');
853 tickContainer.classList.add(this.CssClasses_.FOCUS_HELPER);
854 var tickOutline = document.createElement('span');
855 tickOutline.classList.add(this.CssClasses_.TICK_OUTLINE);
856 boxOutline.appendChild(tickOutline);
857 this.element_.appendChild(tickContainer);
858 this.element_.appendChild(boxOutline);
859 if (this.element_.classList.contains(this.CssClasses_.RIPPLE_EFFECT)) {
860 this.element_.classList.add(this.CssClasses_.RIPPLE_IGNORE_EVENTS);
861 this.rippleContainerElement_ = document.createElement('span');
862 this.rippleContainerElement_.classList.add(this.CssClasses_.RIPPLE_CONTAINER);
863 this.rippleContainerElement_.classList.add(this.CssClasses_.RIPPLE_EFFECT);
864 this.rippleContainerElement_.classList.add(this.CssClasses_.RIPPLE_CENTER);
865 this.boundRippleMouseUp = this.onMouseUp_.bind(this);
866 this.rippleContainerElement_.addEventListener('mouseup', this.boundRippleMouseUp);
867 var ripple = document.createElement('span');
868 ripple.classList.add(this.CssClasses_.RIPPLE);
869 this.rippleContainerElement_.appendChild(ripple);
870 this.element_.appendChild(this.rippleContainerElement_);
872 this.boundInputOnChange = this.onChange_.bind(this);
873 this.boundInputOnFocus = this.onFocus_.bind(this);
874 this.boundInputOnBlur = this.onBlur_.bind(this);
875 this.boundElementMouseUp = this.onMouseUp_.bind(this);
876 this.inputElement_.addEventListener('change', this.boundInputOnChange);
877 this.inputElement_.addEventListener('focus', this.boundInputOnFocus);
878 this.inputElement_.addEventListener('blur', this.boundInputOnBlur);
879 this.element_.addEventListener('mouseup', this.boundElementMouseUp);
880 this.updateClasses_();
881 this.element_.classList.add(this.CssClasses_.IS_UPGRADED);
884 // The component registers itself. It can assume componentHandler is available
885 // in the global scope.
886 componentHandler.register({
887 constructor: MaterialCheckbox,
888 classAsString: 'MaterialCheckbox',
889 cssClass: 'mdl-js-checkbox',
894 * Copyright 2015 Google Inc. All Rights Reserved.
896 * Licensed under the Apache License, Version 2.0 (the "License");
897 * you may not use this file except in compliance with the License.
898 * You may obtain a copy of the License at
900 * http://www.apache.org/licenses/LICENSE-2.0
902 * Unless required by applicable law or agreed to in writing, software
903 * distributed under the License is distributed on an "AS IS" BASIS,
904 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
905 * See the License for the specific language governing permissions and
906 * limitations under the License.
909 * Class constructor for icon toggle MDL component.
910 * Implements MDL component design pattern defined at:
911 * https://github.com/jasonmayes/mdl-component-design-pattern
914 * @param {HTMLElement} element The element that will be upgraded.
916 var MaterialIconToggle = function MaterialIconToggle(element) {
917 this.element_ = element;
918 // Initialize instance.
921 window['MaterialIconToggle'] = MaterialIconToggle;
923 * Store constants in one place so they can be updated easily.
925 * @enum {string | number}
928 MaterialIconToggle.prototype.Constant_ = { TINY_TIMEOUT: 0.001 };
930 * Store strings for class names defined by this component that are used in
931 * JavaScript. This allows us to simply change it in one place should we
932 * decide to modify at a later date.
937 MaterialIconToggle.prototype.CssClasses_ = {
938 INPUT: 'mdl-icon-toggle__input',
939 JS_RIPPLE_EFFECT: 'mdl-js-ripple-effect',
940 RIPPLE_IGNORE_EVENTS: 'mdl-js-ripple-effect--ignore-events',
941 RIPPLE_CONTAINER: 'mdl-icon-toggle__ripple-container',
942 RIPPLE_CENTER: 'mdl-ripple--center',
943 RIPPLE: 'mdl-ripple',
944 IS_FOCUSED: 'is-focused',
945 IS_DISABLED: 'is-disabled',
946 IS_CHECKED: 'is-checked'
949 * Handle change of state.
951 * @param {Event} event The event that fired.
954 MaterialIconToggle.prototype.onChange_ = function (event) {
955 this.updateClasses_();
958 * Handle focus of element.
960 * @param {Event} event The event that fired.
963 MaterialIconToggle.prototype.onFocus_ = function (event) {
964 this.element_.classList.add(this.CssClasses_.IS_FOCUSED);
967 * Handle lost focus of element.
969 * @param {Event} event The event that fired.
972 MaterialIconToggle.prototype.onBlur_ = function (event) {
973 this.element_.classList.remove(this.CssClasses_.IS_FOCUSED);
978 * @param {Event} event The event that fired.
981 MaterialIconToggle.prototype.onMouseUp_ = function (event) {
985 * Handle class updates.
989 MaterialIconToggle.prototype.updateClasses_ = function () {
990 this.checkDisabled();
991 this.checkToggleState();
998 MaterialIconToggle.prototype.blur_ = function () {
999 // TODO: figure out why there's a focus event being fired after our blur,
1000 // so that we can avoid this hack.
1001 window.setTimeout(function () {
1002 this.inputElement_.blur();
1003 }.bind(this), this.Constant_.TINY_TIMEOUT);
1007 * Check the inputs toggle state and update display.
1011 MaterialIconToggle.prototype.checkToggleState = function () {
1012 if (this.inputElement_.checked) {
1013 this.element_.classList.add(this.CssClasses_.IS_CHECKED);
1015 this.element_.classList.remove(this.CssClasses_.IS_CHECKED);
1018 MaterialIconToggle.prototype['checkToggleState'] = MaterialIconToggle.prototype.checkToggleState;
1020 * Check the inputs disabled state and update display.
1024 MaterialIconToggle.prototype.checkDisabled = function () {
1025 if (this.inputElement_.disabled) {
1026 this.element_.classList.add(this.CssClasses_.IS_DISABLED);
1028 this.element_.classList.remove(this.CssClasses_.IS_DISABLED);
1031 MaterialIconToggle.prototype['checkDisabled'] = MaterialIconToggle.prototype.checkDisabled;
1033 * Disable icon toggle.
1037 MaterialIconToggle.prototype.disable = function () {
1038 this.inputElement_.disabled = true;
1039 this.updateClasses_();
1041 MaterialIconToggle.prototype['disable'] = MaterialIconToggle.prototype.disable;
1043 * Enable icon toggle.
1047 MaterialIconToggle.prototype.enable = function () {
1048 this.inputElement_.disabled = false;
1049 this.updateClasses_();
1051 MaterialIconToggle.prototype['enable'] = MaterialIconToggle.prototype.enable;
1053 * Check icon toggle.
1057 MaterialIconToggle.prototype.check = function () {
1058 this.inputElement_.checked = true;
1059 this.updateClasses_();
1061 MaterialIconToggle.prototype['check'] = MaterialIconToggle.prototype.check;
1063 * Uncheck icon toggle.
1067 MaterialIconToggle.prototype.uncheck = function () {
1068 this.inputElement_.checked = false;
1069 this.updateClasses_();
1071 MaterialIconToggle.prototype['uncheck'] = MaterialIconToggle.prototype.uncheck;
1073 * Initialize element.
1075 MaterialIconToggle.prototype.init = function () {
1076 if (this.element_) {
1077 this.inputElement_ = this.element_.querySelector('.' + this.CssClasses_.INPUT);
1078 if (this.element_.classList.contains(this.CssClasses_.JS_RIPPLE_EFFECT)) {
1079 this.element_.classList.add(this.CssClasses_.RIPPLE_IGNORE_EVENTS);
1080 this.rippleContainerElement_ = document.createElement('span');
1081 this.rippleContainerElement_.classList.add(this.CssClasses_.RIPPLE_CONTAINER);
1082 this.rippleContainerElement_.classList.add(this.CssClasses_.JS_RIPPLE_EFFECT);
1083 this.rippleContainerElement_.classList.add(this.CssClasses_.RIPPLE_CENTER);
1084 this.boundRippleMouseUp = this.onMouseUp_.bind(this);
1085 this.rippleContainerElement_.addEventListener('mouseup', this.boundRippleMouseUp);
1086 var ripple = document.createElement('span');
1087 ripple.classList.add(this.CssClasses_.RIPPLE);
1088 this.rippleContainerElement_.appendChild(ripple);
1089 this.element_.appendChild(this.rippleContainerElement_);
1091 this.boundInputOnChange = this.onChange_.bind(this);
1092 this.boundInputOnFocus = this.onFocus_.bind(this);
1093 this.boundInputOnBlur = this.onBlur_.bind(this);
1094 this.boundElementOnMouseUp = this.onMouseUp_.bind(this);
1095 this.inputElement_.addEventListener('change', this.boundInputOnChange);
1096 this.inputElement_.addEventListener('focus', this.boundInputOnFocus);
1097 this.inputElement_.addEventListener('blur', this.boundInputOnBlur);
1098 this.element_.addEventListener('mouseup', this.boundElementOnMouseUp);
1099 this.updateClasses_();
1100 this.element_.classList.add('is-upgraded');
1103 // The component registers itself. It can assume componentHandler is available
1104 // in the global scope.
1105 componentHandler.register({
1106 constructor: MaterialIconToggle,
1107 classAsString: 'MaterialIconToggle',
1108 cssClass: 'mdl-js-icon-toggle',
1113 * Copyright 2015 Google Inc. All Rights Reserved.
1115 * Licensed under the Apache License, Version 2.0 (the "License");
1116 * you may not use this file except in compliance with the License.
1117 * You may obtain a copy of the License at
1119 * http://www.apache.org/licenses/LICENSE-2.0
1121 * Unless required by applicable law or agreed to in writing, software
1122 * distributed under the License is distributed on an "AS IS" BASIS,
1123 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1124 * See the License for the specific language governing permissions and
1125 * limitations under the License.
1128 * Class constructor for dropdown MDL component.
1129 * Implements MDL component design pattern defined at:
1130 * https://github.com/jasonmayes/mdl-component-design-pattern
1133 * @param {HTMLElement} element The element that will be upgraded.
1135 var MaterialMenu = function MaterialMenu(element) {
1136 this.element_ = element;
1137 // Initialize instance.
1140 window['MaterialMenu'] = MaterialMenu;
1142 * Store constants in one place so they can be updated easily.
1144 * @enum {string | number}
1147 MaterialMenu.prototype.Constant_ = {
1148 // Total duration of the menu animation.
1149 TRANSITION_DURATION_SECONDS: 0.3,
1150 // The fraction of the total duration we want to use for menu item animations.
1151 TRANSITION_DURATION_FRACTION: 0.8,
1152 // How long the menu stays open after choosing an option (so the user can see
1157 * Keycodes, for code readability.
1162 MaterialMenu.prototype.Keycodes_ = {
1170 * Store strings for class names defined by this component that are used in
1171 * JavaScript. This allows us to simply change it in one place should we
1172 * decide to modify at a later date.
1177 MaterialMenu.prototype.CssClasses_ = {
1178 CONTAINER: 'mdl-menu__container',
1179 OUTLINE: 'mdl-menu__outline',
1180 ITEM: 'mdl-menu__item',
1181 ITEM_RIPPLE_CONTAINER: 'mdl-menu__item-ripple-container',
1182 RIPPLE_EFFECT: 'mdl-js-ripple-effect',
1183 RIPPLE_IGNORE_EVENTS: 'mdl-js-ripple-effect--ignore-events',
1184 RIPPLE: 'mdl-ripple',
1186 IS_UPGRADED: 'is-upgraded',
1187 IS_VISIBLE: 'is-visible',
1188 IS_ANIMATING: 'is-animating',
1189 // Alignment options
1190 BOTTOM_LEFT: 'mdl-menu--bottom-left',
1191 // This is the default.
1192 BOTTOM_RIGHT: 'mdl-menu--bottom-right',
1193 TOP_LEFT: 'mdl-menu--top-left',
1194 TOP_RIGHT: 'mdl-menu--top-right',
1195 UNALIGNED: 'mdl-menu--unaligned'
1198 * Initialize element.
1200 MaterialMenu.prototype.init = function () {
1201 if (this.element_) {
1202 // Create container for the menu.
1203 var container = document.createElement('div');
1204 container.classList.add(this.CssClasses_.CONTAINER);
1205 this.element_.parentElement.insertBefore(container, this.element_);
1206 this.element_.parentElement.removeChild(this.element_);
1207 container.appendChild(this.element_);
1208 this.container_ = container;
1209 // Create outline for the menu (shadow and background).
1210 var outline = document.createElement('div');
1211 outline.classList.add(this.CssClasses_.OUTLINE);
1212 this.outline_ = outline;
1213 container.insertBefore(outline, this.element_);
1214 // Find the "for" element and bind events to it.
1215 var forElId = this.element_.getAttribute('for') || this.element_.getAttribute('data-mdl-for');
1218 forEl = document.getElementById(forElId);
1220 this.forElement_ = forEl;
1221 forEl.addEventListener('click', this.handleForClick_.bind(this));
1222 forEl.addEventListener('keydown', this.handleForKeyboardEvent_.bind(this));
1225 var items = this.element_.querySelectorAll('.' + this.CssClasses_.ITEM);
1226 this.boundItemKeydown_ = this.handleItemKeyboardEvent_.bind(this);
1227 this.boundItemClick_ = this.handleItemClick_.bind(this);
1228 for (var i = 0; i < items.length; i++) {
1229 // Add a listener to each menu item.
1230 items[i].addEventListener('click', this.boundItemClick_);
1231 // Add a tab index to each menu item.
1232 items[i].tabIndex = '-1';
1233 // Add a keyboard listener to each menu item.
1234 items[i].addEventListener('keydown', this.boundItemKeydown_);
1236 // Add ripple classes to each item, if the user has enabled ripples.
1237 if (this.element_.classList.contains(this.CssClasses_.RIPPLE_EFFECT)) {
1238 this.element_.classList.add(this.CssClasses_.RIPPLE_IGNORE_EVENTS);
1239 for (i = 0; i < items.length; i++) {
1240 var item = items[i];
1241 var rippleContainer = document.createElement('span');
1242 rippleContainer.classList.add(this.CssClasses_.ITEM_RIPPLE_CONTAINER);
1243 var ripple = document.createElement('span');
1244 ripple.classList.add(this.CssClasses_.RIPPLE);
1245 rippleContainer.appendChild(ripple);
1246 item.appendChild(rippleContainer);
1247 item.classList.add(this.CssClasses_.RIPPLE_EFFECT);
1250 // Copy alignment classes to the container, so the outline can use them.
1251 if (this.element_.classList.contains(this.CssClasses_.BOTTOM_LEFT)) {
1252 this.outline_.classList.add(this.CssClasses_.BOTTOM_LEFT);
1254 if (this.element_.classList.contains(this.CssClasses_.BOTTOM_RIGHT)) {
1255 this.outline_.classList.add(this.CssClasses_.BOTTOM_RIGHT);
1257 if (this.element_.classList.contains(this.CssClasses_.TOP_LEFT)) {
1258 this.outline_.classList.add(this.CssClasses_.TOP_LEFT);
1260 if (this.element_.classList.contains(this.CssClasses_.TOP_RIGHT)) {
1261 this.outline_.classList.add(this.CssClasses_.TOP_RIGHT);
1263 if (this.element_.classList.contains(this.CssClasses_.UNALIGNED)) {
1264 this.outline_.classList.add(this.CssClasses_.UNALIGNED);
1266 container.classList.add(this.CssClasses_.IS_UPGRADED);
1270 * Handles a click on the "for" element, by positioning the menu and then
1273 * @param {Event} evt The event that fired.
1276 MaterialMenu.prototype.handleForClick_ = function (evt) {
1277 if (this.element_ && this.forElement_) {
1278 var rect = this.forElement_.getBoundingClientRect();
1279 var forRect = this.forElement_.parentElement.getBoundingClientRect();
1280 if (this.element_.classList.contains(this.CssClasses_.UNALIGNED)) {
1281 } else if (this.element_.classList.contains(this.CssClasses_.BOTTOM_RIGHT)) {
1282 // Position below the "for" element, aligned to its right.
1283 this.container_.style.right = forRect.right - rect.right + 'px';
1284 this.container_.style.top = this.forElement_.offsetTop + this.forElement_.offsetHeight + 'px';
1285 } else if (this.element_.classList.contains(this.CssClasses_.TOP_LEFT)) {
1286 // Position above the "for" element, aligned to its left.
1287 this.container_.style.left = this.forElement_.offsetLeft + 'px';
1288 this.container_.style.bottom = forRect.bottom - rect.top + 'px';
1289 } else if (this.element_.classList.contains(this.CssClasses_.TOP_RIGHT)) {
1290 // Position above the "for" element, aligned to its right.
1291 this.container_.style.right = forRect.right - rect.right + 'px';
1292 this.container_.style.bottom = forRect.bottom - rect.top + 'px';
1294 // Default: position below the "for" element, aligned to its left.
1295 this.container_.style.left = this.forElement_.offsetLeft + 'px';
1296 this.container_.style.top = this.forElement_.offsetTop + this.forElement_.offsetHeight + 'px';
1302 * Handles a keyboard event on the "for" element.
1304 * @param {Event} evt The event that fired.
1307 MaterialMenu.prototype.handleForKeyboardEvent_ = function (evt) {
1308 if (this.element_ && this.container_ && this.forElement_) {
1309 var items = this.element_.querySelectorAll('.' + this.CssClasses_.ITEM + ':not([disabled])');
1310 if (items && items.length > 0 && this.container_.classList.contains(this.CssClasses_.IS_VISIBLE)) {
1311 if (evt.keyCode === this.Keycodes_.UP_ARROW) {
1312 evt.preventDefault();
1313 items[items.length - 1].focus();
1314 } else if (evt.keyCode === this.Keycodes_.DOWN_ARROW) {
1315 evt.preventDefault();
1322 * Handles a keyboard event on an item.
1324 * @param {Event} evt The event that fired.
1327 MaterialMenu.prototype.handleItemKeyboardEvent_ = function (evt) {
1328 if (this.element_ && this.container_) {
1329 var items = this.element_.querySelectorAll('.' + this.CssClasses_.ITEM + ':not([disabled])');
1330 if (items && items.length > 0 && this.container_.classList.contains(this.CssClasses_.IS_VISIBLE)) {
1331 var currentIndex = Array.prototype.slice.call(items).indexOf(evt.target);
1332 if (evt.keyCode === this.Keycodes_.UP_ARROW) {
1333 evt.preventDefault();
1334 if (currentIndex > 0) {
1335 items[currentIndex - 1].focus();
1337 items[items.length - 1].focus();
1339 } else if (evt.keyCode === this.Keycodes_.DOWN_ARROW) {
1340 evt.preventDefault();
1341 if (items.length > currentIndex + 1) {
1342 items[currentIndex + 1].focus();
1346 } else if (evt.keyCode === this.Keycodes_.SPACE || evt.keyCode === this.Keycodes_.ENTER) {
1347 evt.preventDefault();
1348 // Send mousedown and mouseup to trigger ripple.
1349 var e = new MouseEvent('mousedown');
1350 evt.target.dispatchEvent(e);
1351 e = new MouseEvent('mouseup');
1352 evt.target.dispatchEvent(e);
1355 } else if (evt.keyCode === this.Keycodes_.ESCAPE) {
1356 evt.preventDefault();
1363 * Handles a click event on an item.
1365 * @param {Event} evt The event that fired.
1368 MaterialMenu.prototype.handleItemClick_ = function (evt) {
1369 if (evt.target.hasAttribute('disabled')) {
1370 evt.stopPropagation();
1372 // Wait some time before closing menu, so the user can see the ripple.
1373 this.closing_ = true;
1374 window.setTimeout(function (evt) {
1376 this.closing_ = false;
1377 }.bind(this), this.Constant_.CLOSE_TIMEOUT);
1381 * Calculates the initial clip (for opening the menu) or final clip (for closing
1382 * it), and applies it. This allows us to animate from or to the correct point,
1383 * that is, the point it's aligned to in the "for" element.
1385 * @param {number} height Height of the clip rectangle
1386 * @param {number} width Width of the clip rectangle
1389 MaterialMenu.prototype.applyClip_ = function (height, width) {
1390 if (this.element_.classList.contains(this.CssClasses_.UNALIGNED)) {
1392 this.element_.style.clip = '';
1393 } else if (this.element_.classList.contains(this.CssClasses_.BOTTOM_RIGHT)) {
1394 // Clip to the top right corner of the menu.
1395 this.element_.style.clip = 'rect(0 ' + width + 'px ' + '0 ' + width + 'px)';
1396 } else if (this.element_.classList.contains(this.CssClasses_.TOP_LEFT)) {
1397 // Clip to the bottom left corner of the menu.
1398 this.element_.style.clip = 'rect(' + height + 'px 0 ' + height + 'px 0)';
1399 } else if (this.element_.classList.contains(this.CssClasses_.TOP_RIGHT)) {
1400 // Clip to the bottom right corner of the menu.
1401 this.element_.style.clip = 'rect(' + height + 'px ' + width + 'px ' + height + 'px ' + width + 'px)';
1403 // Default: do not clip (same as clipping to the top left corner).
1404 this.element_.style.clip = '';
1408 * Cleanup function to remove animation listeners.
1410 * @param {Event} evt
1413 MaterialMenu.prototype.removeAnimationEndListener_ = function (evt) {
1414 evt.target.classList.remove(MaterialMenu.prototype.CssClasses_.IS_ANIMATING);
1417 * Adds an event listener to clean up after the animation ends.
1421 MaterialMenu.prototype.addAnimationEndListener_ = function () {
1422 this.element_.addEventListener('transitionend', this.removeAnimationEndListener_);
1423 this.element_.addEventListener('webkitTransitionEnd', this.removeAnimationEndListener_);
1426 * Displays the menu.
1430 MaterialMenu.prototype.show = function (evt) {
1431 if (this.element_ && this.container_ && this.outline_) {
1432 // Measure the inner element.
1433 var height = this.element_.getBoundingClientRect().height;
1434 var width = this.element_.getBoundingClientRect().width;
1435 // Apply the inner element's size to the container and outline.
1436 this.container_.style.width = width + 'px';
1437 this.container_.style.height = height + 'px';
1438 this.outline_.style.width = width + 'px';
1439 this.outline_.style.height = height + 'px';
1440 var transitionDuration = this.Constant_.TRANSITION_DURATION_SECONDS * this.Constant_.TRANSITION_DURATION_FRACTION;
1441 // Calculate transition delays for individual menu items, so that they fade
1442 // in one at a time.
1443 var items = this.element_.querySelectorAll('.' + this.CssClasses_.ITEM);
1444 for (var i = 0; i < items.length; i++) {
1445 var itemDelay = null;
1446 if (this.element_.classList.contains(this.CssClasses_.TOP_LEFT) || this.element_.classList.contains(this.CssClasses_.TOP_RIGHT)) {
1447 itemDelay = (height - items[i].offsetTop - items[i].offsetHeight) / height * transitionDuration + 's';
1449 itemDelay = items[i].offsetTop / height * transitionDuration + 's';
1451 items[i].style.transitionDelay = itemDelay;
1453 // Apply the initial clip to the text before we start animating.
1454 this.applyClip_(height, width);
1455 // Wait for the next frame, turn on animation, and apply the final clip.
1456 // Also make it visible. This triggers the transitions.
1457 window.requestAnimationFrame(function () {
1458 this.element_.classList.add(this.CssClasses_.IS_ANIMATING);
1459 this.element_.style.clip = 'rect(0 ' + width + 'px ' + height + 'px 0)';
1460 this.container_.classList.add(this.CssClasses_.IS_VISIBLE);
1462 // Clean up after the animation is complete.
1463 this.addAnimationEndListener_();
1464 // Add a click listener to the document, to close the menu.
1465 var callback = function (e) {
1466 // Check to see if the document is processing the same event that
1467 // displayed the menu in the first place. If so, do nothing.
1468 // Also check to see if the menu is in the process of closing itself, and
1469 // do nothing in that case.
1470 // Also check if the clicked element is a menu item
1471 // if so, do nothing.
1472 if (e !== evt && !this.closing_ && e.target.parentNode !== this.element_) {
1473 document.removeEventListener('click', callback);
1477 document.addEventListener('click', callback);
1480 MaterialMenu.prototype['show'] = MaterialMenu.prototype.show;
1486 MaterialMenu.prototype.hide = function () {
1487 if (this.element_ && this.container_ && this.outline_) {
1488 var items = this.element_.querySelectorAll('.' + this.CssClasses_.ITEM);
1489 // Remove all transition delays; menu items fade out concurrently.
1490 for (var i = 0; i < items.length; i++) {
1491 items[i].style.removeProperty('transition-delay');
1493 // Measure the inner element.
1494 var rect = this.element_.getBoundingClientRect();
1495 var height = rect.height;
1496 var width = rect.width;
1497 // Turn on animation, and apply the final clip. Also make invisible.
1498 // This triggers the transitions.
1499 this.element_.classList.add(this.CssClasses_.IS_ANIMATING);
1500 this.applyClip_(height, width);
1501 this.container_.classList.remove(this.CssClasses_.IS_VISIBLE);
1502 // Clean up after the animation is complete.
1503 this.addAnimationEndListener_();
1506 MaterialMenu.prototype['hide'] = MaterialMenu.prototype.hide;
1508 * Displays or hides the menu, depending on current state.
1512 MaterialMenu.prototype.toggle = function (evt) {
1513 if (this.container_.classList.contains(this.CssClasses_.IS_VISIBLE)) {
1519 MaterialMenu.prototype['toggle'] = MaterialMenu.prototype.toggle;
1520 // The component registers itself. It can assume componentHandler is available
1521 // in the global scope.
1522 componentHandler.register({
1523 constructor: MaterialMenu,
1524 classAsString: 'MaterialMenu',
1525 cssClass: 'mdl-js-menu',
1530 * Copyright 2015 Google Inc. All Rights Reserved.
1532 * Licensed under the Apache License, Version 2.0 (the "License");
1533 * you may not use this file except in compliance with the License.
1534 * You may obtain a copy of the License at
1536 * http://www.apache.org/licenses/LICENSE-2.0
1538 * Unless required by applicable law or agreed to in writing, software
1539 * distributed under the License is distributed on an "AS IS" BASIS,
1540 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1541 * See the License for the specific language governing permissions and
1542 * limitations under the License.
1545 * Class constructor for Progress MDL component.
1546 * Implements MDL component design pattern defined at:
1547 * https://github.com/jasonmayes/mdl-component-design-pattern
1550 * @param {HTMLElement} element The element that will be upgraded.
1552 var MaterialProgress = function MaterialProgress(element) {
1553 this.element_ = element;
1554 // Initialize instance.
1557 window['MaterialProgress'] = MaterialProgress;
1559 * Store constants in one place so they can be updated easily.
1561 * @enum {string | number}
1564 MaterialProgress.prototype.Constant_ = {};
1566 * Store strings for class names defined by this component that are used in
1567 * JavaScript. This allows us to simply change it in one place should we
1568 * decide to modify at a later date.
1573 MaterialProgress.prototype.CssClasses_ = { INDETERMINATE_CLASS: 'mdl-progress__indeterminate' };
1575 * Set the current progress of the progressbar.
1577 * @param {number} p Percentage of the progress (0-100)
1580 MaterialProgress.prototype.setProgress = function (p) {
1581 if (this.element_.classList.contains(this.CssClasses_.INDETERMINATE_CLASS)) {
1584 this.progressbar_.style.width = p + '%';
1586 MaterialProgress.prototype['setProgress'] = MaterialProgress.prototype.setProgress;
1588 * Set the current progress of the buffer.
1590 * @param {number} p Percentage of the buffer (0-100)
1593 MaterialProgress.prototype.setBuffer = function (p) {
1594 this.bufferbar_.style.width = p + '%';
1595 this.auxbar_.style.width = 100 - p + '%';
1597 MaterialProgress.prototype['setBuffer'] = MaterialProgress.prototype.setBuffer;
1599 * Initialize element.
1601 MaterialProgress.prototype.init = function () {
1602 if (this.element_) {
1603 var el = document.createElement('div');
1604 el.className = 'progressbar bar bar1';
1605 this.element_.appendChild(el);
1606 this.progressbar_ = el;
1607 el = document.createElement('div');
1608 el.className = 'bufferbar bar bar2';
1609 this.element_.appendChild(el);
1610 this.bufferbar_ = el;
1611 el = document.createElement('div');
1612 el.className = 'auxbar bar bar3';
1613 this.element_.appendChild(el);
1615 this.progressbar_.style.width = '0%';
1616 this.bufferbar_.style.width = '100%';
1617 this.auxbar_.style.width = '0%';
1618 this.element_.classList.add('is-upgraded');
1621 // The component registers itself. It can assume componentHandler is available
1622 // in the global scope.
1623 componentHandler.register({
1624 constructor: MaterialProgress,
1625 classAsString: 'MaterialProgress',
1626 cssClass: 'mdl-js-progress',
1631 * Copyright 2015 Google Inc. All Rights Reserved.
1633 * Licensed under the Apache License, Version 2.0 (the "License");
1634 * you may not use this file except in compliance with the License.
1635 * You may obtain a copy of the License at
1637 * http://www.apache.org/licenses/LICENSE-2.0
1639 * Unless required by applicable law or agreed to in writing, software
1640 * distributed under the License is distributed on an "AS IS" BASIS,
1641 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1642 * See the License for the specific language governing permissions and
1643 * limitations under the License.
1646 * Class constructor for Radio MDL component.
1647 * Implements MDL component design pattern defined at:
1648 * https://github.com/jasonmayes/mdl-component-design-pattern
1651 * @param {HTMLElement} element The element that will be upgraded.
1653 var MaterialRadio = function MaterialRadio(element) {
1654 this.element_ = element;
1655 // Initialize instance.
1658 window['MaterialRadio'] = MaterialRadio;
1660 * Store constants in one place so they can be updated easily.
1662 * @enum {string | number}
1665 MaterialRadio.prototype.Constant_ = { TINY_TIMEOUT: 0.001 };
1667 * Store strings for class names defined by this component that are used in
1668 * JavaScript. This allows us to simply change it in one place should we
1669 * decide to modify at a later date.
1674 MaterialRadio.prototype.CssClasses_ = {
1675 IS_FOCUSED: 'is-focused',
1676 IS_DISABLED: 'is-disabled',
1677 IS_CHECKED: 'is-checked',
1678 IS_UPGRADED: 'is-upgraded',
1679 JS_RADIO: 'mdl-js-radio',
1680 RADIO_BTN: 'mdl-radio__button',
1681 RADIO_OUTER_CIRCLE: 'mdl-radio__outer-circle',
1682 RADIO_INNER_CIRCLE: 'mdl-radio__inner-circle',
1683 RIPPLE_EFFECT: 'mdl-js-ripple-effect',
1684 RIPPLE_IGNORE_EVENTS: 'mdl-js-ripple-effect--ignore-events',
1685 RIPPLE_CONTAINER: 'mdl-radio__ripple-container',
1686 RIPPLE_CENTER: 'mdl-ripple--center',
1687 RIPPLE: 'mdl-ripple'
1690 * Handle change of state.
1692 * @param {Event} event The event that fired.
1695 MaterialRadio.prototype.onChange_ = function (event) {
1696 // Since other radio buttons don't get change events, we need to look for
1697 // them to update their classes.
1698 var radios = document.getElementsByClassName(this.CssClasses_.JS_RADIO);
1699 for (var i = 0; i < radios.length; i++) {
1700 var button = radios[i].querySelector('.' + this.CssClasses_.RADIO_BTN);
1701 // Different name == different group, so no point updating those.
1702 if (button.getAttribute('name') === this.btnElement_.getAttribute('name')) {
1703 if (typeof radios[i]['MaterialRadio'] !== 'undefined') {
1704 radios[i]['MaterialRadio'].updateClasses_();
1712 * @param {Event} event The event that fired.
1715 MaterialRadio.prototype.onFocus_ = function (event) {
1716 this.element_.classList.add(this.CssClasses_.IS_FOCUSED);
1719 * Handle lost focus.
1721 * @param {Event} event The event that fired.
1724 MaterialRadio.prototype.onBlur_ = function (event) {
1725 this.element_.classList.remove(this.CssClasses_.IS_FOCUSED);
1730 * @param {Event} event The event that fired.
1733 MaterialRadio.prototype.onMouseup_ = function (event) {
1741 MaterialRadio.prototype.updateClasses_ = function () {
1742 this.checkDisabled();
1743 this.checkToggleState();
1750 MaterialRadio.prototype.blur_ = function () {
1751 // TODO: figure out why there's a focus event being fired after our blur,
1752 // so that we can avoid this hack.
1753 window.setTimeout(function () {
1754 this.btnElement_.blur();
1755 }.bind(this), this.Constant_.TINY_TIMEOUT);
1759 * Check the components disabled state.
1763 MaterialRadio.prototype.checkDisabled = function () {
1764 if (this.btnElement_.disabled) {
1765 this.element_.classList.add(this.CssClasses_.IS_DISABLED);
1767 this.element_.classList.remove(this.CssClasses_.IS_DISABLED);
1770 MaterialRadio.prototype['checkDisabled'] = MaterialRadio.prototype.checkDisabled;
1772 * Check the components toggled state.
1776 MaterialRadio.prototype.checkToggleState = function () {
1777 if (this.btnElement_.checked) {
1778 this.element_.classList.add(this.CssClasses_.IS_CHECKED);
1780 this.element_.classList.remove(this.CssClasses_.IS_CHECKED);
1783 MaterialRadio.prototype['checkToggleState'] = MaterialRadio.prototype.checkToggleState;
1789 MaterialRadio.prototype.disable = function () {
1790 this.btnElement_.disabled = true;
1791 this.updateClasses_();
1793 MaterialRadio.prototype['disable'] = MaterialRadio.prototype.disable;
1799 MaterialRadio.prototype.enable = function () {
1800 this.btnElement_.disabled = false;
1801 this.updateClasses_();
1803 MaterialRadio.prototype['enable'] = MaterialRadio.prototype.enable;
1809 MaterialRadio.prototype.check = function () {
1810 this.btnElement_.checked = true;
1811 this.onChange_(null);
1813 MaterialRadio.prototype['check'] = MaterialRadio.prototype.check;
1819 MaterialRadio.prototype.uncheck = function () {
1820 this.btnElement_.checked = false;
1821 this.onChange_(null);
1823 MaterialRadio.prototype['uncheck'] = MaterialRadio.prototype.uncheck;
1825 * Initialize element.
1827 MaterialRadio.prototype.init = function () {
1828 if (this.element_) {
1829 this.btnElement_ = this.element_.querySelector('.' + this.CssClasses_.RADIO_BTN);
1830 this.boundChangeHandler_ = this.onChange_.bind(this);
1831 this.boundFocusHandler_ = this.onChange_.bind(this);
1832 this.boundBlurHandler_ = this.onBlur_.bind(this);
1833 this.boundMouseUpHandler_ = this.onMouseup_.bind(this);
1834 var outerCircle = document.createElement('span');
1835 outerCircle.classList.add(this.CssClasses_.RADIO_OUTER_CIRCLE);
1836 var innerCircle = document.createElement('span');
1837 innerCircle.classList.add(this.CssClasses_.RADIO_INNER_CIRCLE);
1838 this.element_.appendChild(outerCircle);
1839 this.element_.appendChild(innerCircle);
1840 var rippleContainer;
1841 if (this.element_.classList.contains(this.CssClasses_.RIPPLE_EFFECT)) {
1842 this.element_.classList.add(this.CssClasses_.RIPPLE_IGNORE_EVENTS);
1843 rippleContainer = document.createElement('span');
1844 rippleContainer.classList.add(this.CssClasses_.RIPPLE_CONTAINER);
1845 rippleContainer.classList.add(this.CssClasses_.RIPPLE_EFFECT);
1846 rippleContainer.classList.add(this.CssClasses_.RIPPLE_CENTER);
1847 rippleContainer.addEventListener('mouseup', this.boundMouseUpHandler_);
1848 var ripple = document.createElement('span');
1849 ripple.classList.add(this.CssClasses_.RIPPLE);
1850 rippleContainer.appendChild(ripple);
1851 this.element_.appendChild(rippleContainer);
1853 this.btnElement_.addEventListener('change', this.boundChangeHandler_);
1854 this.btnElement_.addEventListener('focus', this.boundFocusHandler_);
1855 this.btnElement_.addEventListener('blur', this.boundBlurHandler_);
1856 this.element_.addEventListener('mouseup', this.boundMouseUpHandler_);
1857 this.updateClasses_();
1858 this.element_.classList.add(this.CssClasses_.IS_UPGRADED);
1861 // The component registers itself. It can assume componentHandler is available
1862 // in the global scope.
1863 componentHandler.register({
1864 constructor: MaterialRadio,
1865 classAsString: 'MaterialRadio',
1866 cssClass: 'mdl-js-radio',
1871 * Copyright 2015 Google Inc. All Rights Reserved.
1873 * Licensed under the Apache License, Version 2.0 (the "License");
1874 * you may not use this file except in compliance with the License.
1875 * You may obtain a copy of the License at
1877 * http://www.apache.org/licenses/LICENSE-2.0
1879 * Unless required by applicable law or agreed to in writing, software
1880 * distributed under the License is distributed on an "AS IS" BASIS,
1881 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1882 * See the License for the specific language governing permissions and
1883 * limitations under the License.
1886 * Class constructor for Slider MDL component.
1887 * Implements MDL component design pattern defined at:
1888 * https://github.com/jasonmayes/mdl-component-design-pattern
1891 * @param {HTMLElement} element The element that will be upgraded.
1893 var MaterialSlider = function MaterialSlider(element) {
1894 this.element_ = element;
1895 // Browser feature detection.
1896 this.isIE_ = window.navigator.msPointerEnabled;
1897 // Initialize instance.
1900 window['MaterialSlider'] = MaterialSlider;
1902 * Store constants in one place so they can be updated easily.
1904 * @enum {string | number}
1907 MaterialSlider.prototype.Constant_ = {};
1909 * Store strings for class names defined by this component that are used in
1910 * JavaScript. This allows us to simply change it in one place should we
1911 * decide to modify at a later date.
1916 MaterialSlider.prototype.CssClasses_ = {
1917 IE_CONTAINER: 'mdl-slider__ie-container',
1918 SLIDER_CONTAINER: 'mdl-slider__container',
1919 BACKGROUND_FLEX: 'mdl-slider__background-flex',
1920 BACKGROUND_LOWER: 'mdl-slider__background-lower',
1921 BACKGROUND_UPPER: 'mdl-slider__background-upper',
1922 IS_LOWEST_VALUE: 'is-lowest-value',
1923 IS_UPGRADED: 'is-upgraded'
1926 * Handle input on element.
1928 * @param {Event} event The event that fired.
1931 MaterialSlider.prototype.onInput_ = function (event) {
1932 this.updateValueStyles_();
1935 * Handle change on element.
1937 * @param {Event} event The event that fired.
1940 MaterialSlider.prototype.onChange_ = function (event) {
1941 this.updateValueStyles_();
1944 * Handle mouseup on element.
1946 * @param {Event} event The event that fired.
1949 MaterialSlider.prototype.onMouseUp_ = function (event) {
1950 event.target.blur();
1953 * Handle mousedown on container element.
1954 * This handler is purpose is to not require the use to click
1955 * exactly on the 2px slider element, as FireFox seems to be very
1956 * strict about this.
1958 * @param {Event} event The event that fired.
1960 * @suppress {missingProperties}
1962 MaterialSlider.prototype.onContainerMouseDown_ = function (event) {
1963 // If this click is not on the parent element (but rather some child)
1964 // ignore. It may still bubble up.
1965 if (event.target !== this.element_.parentElement) {
1968 // Discard the original event and create a new event that
1969 // is on the slider element.
1970 event.preventDefault();
1971 var newEvent = new MouseEvent('mousedown', {
1972 target: event.target,
1973 buttons: event.buttons,
1974 clientX: event.clientX,
1975 clientY: this.element_.getBoundingClientRect().y
1977 this.element_.dispatchEvent(newEvent);
1980 * Handle updating of values.
1984 MaterialSlider.prototype.updateValueStyles_ = function () {
1985 // Calculate and apply percentages to div structure behind slider.
1986 var fraction = (this.element_.value - this.element_.min) / (this.element_.max - this.element_.min);
1987 if (fraction === 0) {
1988 this.element_.classList.add(this.CssClasses_.IS_LOWEST_VALUE);
1990 this.element_.classList.remove(this.CssClasses_.IS_LOWEST_VALUE);
1993 this.backgroundLower_.style.flex = fraction;
1994 this.backgroundLower_.style.webkitFlex = fraction;
1995 this.backgroundUpper_.style.flex = 1 - fraction;
1996 this.backgroundUpper_.style.webkitFlex = 1 - fraction;
2005 MaterialSlider.prototype.disable = function () {
2006 this.element_.disabled = true;
2008 MaterialSlider.prototype['disable'] = MaterialSlider.prototype.disable;
2014 MaterialSlider.prototype.enable = function () {
2015 this.element_.disabled = false;
2017 MaterialSlider.prototype['enable'] = MaterialSlider.prototype.enable;
2019 * Update slider value.
2021 * @param {number} value The value to which to set the control (optional).
2024 MaterialSlider.prototype.change = function (value) {
2025 if (typeof value !== 'undefined') {
2026 this.element_.value = value;
2028 this.updateValueStyles_();
2030 MaterialSlider.prototype['change'] = MaterialSlider.prototype.change;
2032 * Initialize element.
2034 MaterialSlider.prototype.init = function () {
2035 if (this.element_) {
2037 // Since we need to specify a very large height in IE due to
2038 // implementation limitations, we add a parent here that trims it down to
2039 // a reasonable size.
2040 var containerIE = document.createElement('div');
2041 containerIE.classList.add(this.CssClasses_.IE_CONTAINER);
2042 this.element_.parentElement.insertBefore(containerIE, this.element_);
2043 this.element_.parentElement.removeChild(this.element_);
2044 containerIE.appendChild(this.element_);
2046 // For non-IE browsers, we need a div structure that sits behind the
2047 // slider and allows us to style the left and right sides of it with
2048 // different colors.
2049 var container = document.createElement('div');
2050 container.classList.add(this.CssClasses_.SLIDER_CONTAINER);
2051 this.element_.parentElement.insertBefore(container, this.element_);
2052 this.element_.parentElement.removeChild(this.element_);
2053 container.appendChild(this.element_);
2054 var backgroundFlex = document.createElement('div');
2055 backgroundFlex.classList.add(this.CssClasses_.BACKGROUND_FLEX);
2056 container.appendChild(backgroundFlex);
2057 this.backgroundLower_ = document.createElement('div');
2058 this.backgroundLower_.classList.add(this.CssClasses_.BACKGROUND_LOWER);
2059 backgroundFlex.appendChild(this.backgroundLower_);
2060 this.backgroundUpper_ = document.createElement('div');
2061 this.backgroundUpper_.classList.add(this.CssClasses_.BACKGROUND_UPPER);
2062 backgroundFlex.appendChild(this.backgroundUpper_);
2064 this.boundInputHandler = this.onInput_.bind(this);
2065 this.boundChangeHandler = this.onChange_.bind(this);
2066 this.boundMouseUpHandler = this.onMouseUp_.bind(this);
2067 this.boundContainerMouseDownHandler = this.onContainerMouseDown_.bind(this);
2068 this.element_.addEventListener('input', this.boundInputHandler);
2069 this.element_.addEventListener('change', this.boundChangeHandler);
2070 this.element_.addEventListener('mouseup', this.boundMouseUpHandler);
2071 this.element_.parentElement.addEventListener('mousedown', this.boundContainerMouseDownHandler);
2072 this.updateValueStyles_();
2073 this.element_.classList.add(this.CssClasses_.IS_UPGRADED);
2076 // The component registers itself. It can assume componentHandler is available
2077 // in the global scope.
2078 componentHandler.register({
2079 constructor: MaterialSlider,
2080 classAsString: 'MaterialSlider',
2081 cssClass: 'mdl-js-slider',
2085 * Copyright 2015 Google Inc. All Rights Reserved.
2087 * Licensed under the Apache License, Version 2.0 (the "License");
2088 * you may not use this file except in compliance with the License.
2089 * You may obtain a copy of the License at
2091 * http://www.apache.org/licenses/LICENSE-2.0
2093 * Unless required by applicable law or agreed to in writing, software
2094 * distributed under the License is distributed on an "AS IS" BASIS,
2095 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2096 * See the License for the specific language governing permissions and
2097 * limitations under the License.
2100 * Class constructor for Snackbar MDL component.
2101 * Implements MDL component design pattern defined at:
2102 * https://github.com/jasonmayes/mdl-component-design-pattern
2105 * @param {HTMLElement} element The element that will be upgraded.
2107 var MaterialSnackbar = function MaterialSnackbar(element) {
2108 this.element_ = element;
2109 this.textElement_ = this.element_.querySelector('.' + this.cssClasses_.MESSAGE);
2110 this.actionElement_ = this.element_.querySelector('.' + this.cssClasses_.ACTION);
2111 if (!this.textElement_) {
2112 throw new Error('There must be a message element for a snackbar.');
2114 if (!this.actionElement_) {
2115 throw new Error('There must be an action element for a snackbar.');
2117 this.active = false;
2118 this.actionHandler_ = undefined;
2119 this.message_ = undefined;
2120 this.actionText_ = undefined;
2121 this.queuedNotifications_ = [];
2122 this.setActionHidden_(true);
2124 window['MaterialSnackbar'] = MaterialSnackbar;
2126 * Store constants in one place so they can be updated easily.
2128 * @enum {string | number}
2131 MaterialSnackbar.prototype.Constant_ = {
2132 // The duration of the snackbar show/hide animation, in ms.
2133 ANIMATION_LENGTH: 250
2136 * Store strings for class names defined by this component that are used in
2137 * JavaScript. This allows us to simply change it in one place should we
2138 * decide to modify at a later date.
2143 MaterialSnackbar.prototype.cssClasses_ = {
2144 SNACKBAR: 'mdl-snackbar',
2145 MESSAGE: 'mdl-snackbar__text',
2146 ACTION: 'mdl-snackbar__action',
2147 ACTIVE: 'mdl-snackbar--active'
2150 * Display the snackbar.
2154 MaterialSnackbar.prototype.displaySnackbar_ = function () {
2155 this.element_.setAttribute('aria-hidden', 'true');
2156 if (this.actionHandler_) {
2157 this.actionElement_.textContent = this.actionText_;
2158 this.actionElement_.addEventListener('click', this.actionHandler_);
2159 this.setActionHidden_(false);
2161 this.textElement_.textContent = this.message_;
2162 this.element_.classList.add(this.cssClasses_.ACTIVE);
2163 this.element_.setAttribute('aria-hidden', 'false');
2164 setTimeout(this.cleanup_.bind(this), this.timeout_);
2167 * Show the snackbar.
2169 * @param {Object} data The data for the notification.
2172 MaterialSnackbar.prototype.showSnackbar = function (data) {
2173 if (data === undefined) {
2174 throw new Error('Please provide a data object with at least a message to display.');
2176 if (data['message'] === undefined) {
2177 throw new Error('Please provide a message to be displayed.');
2179 if (data['actionHandler'] && !data['actionText']) {
2180 throw new Error('Please provide action text with the handler.');
2183 this.queuedNotifications_.push(data);
2186 this.message_ = data['message'];
2187 if (data['timeout']) {
2188 this.timeout_ = data['timeout'];
2190 this.timeout_ = 2750;
2192 if (data['actionHandler']) {
2193 this.actionHandler_ = data['actionHandler'];
2195 if (data['actionText']) {
2196 this.actionText_ = data['actionText'];
2198 this.displaySnackbar_();
2201 MaterialSnackbar.prototype['showSnackbar'] = MaterialSnackbar.prototype.showSnackbar;
2203 * Check if the queue has items within it.
2204 * If it does, display the next entry.
2208 MaterialSnackbar.prototype.checkQueue_ = function () {
2209 if (this.queuedNotifications_.length > 0) {
2210 this.showSnackbar(this.queuedNotifications_.shift());
2214 * Cleanup the snackbar event listeners and accessiblity attributes.
2218 MaterialSnackbar.prototype.cleanup_ = function () {
2219 this.element_.classList.remove(this.cssClasses_.ACTIVE);
2220 setTimeout(function () {
2221 this.element_.setAttribute('aria-hidden', 'true');
2222 this.textElement_.textContent = '';
2223 if (!Boolean(this.actionElement_.getAttribute('aria-hidden'))) {
2224 this.setActionHidden_(true);
2225 this.actionElement_.textContent = '';
2226 this.actionElement_.removeEventListener('click', this.actionHandler_);
2228 this.actionHandler_ = undefined;
2229 this.message_ = undefined;
2230 this.actionText_ = undefined;
2231 this.active = false;
2233 }.bind(this), this.Constant_.ANIMATION_LENGTH);
2236 * Set the action handler hidden state.
2238 * @param {boolean} value
2241 MaterialSnackbar.prototype.setActionHidden_ = function (value) {
2243 this.actionElement_.setAttribute('aria-hidden', 'true');
2245 this.actionElement_.removeAttribute('aria-hidden');
2248 // The component registers itself. It can assume componentHandler is available
2249 // in the global scope.
2250 componentHandler.register({
2251 constructor: MaterialSnackbar,
2252 classAsString: 'MaterialSnackbar',
2253 cssClass: 'mdl-js-snackbar',
2258 * Copyright 2015 Google Inc. All Rights Reserved.
2260 * Licensed under the Apache License, Version 2.0 (the "License");
2261 * you may not use this file except in compliance with the License.
2262 * You may obtain a copy of the License at
2264 * http://www.apache.org/licenses/LICENSE-2.0
2266 * Unless required by applicable law or agreed to in writing, software
2267 * distributed under the License is distributed on an "AS IS" BASIS,
2268 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2269 * See the License for the specific language governing permissions and
2270 * limitations under the License.
2273 * Class constructor for Spinner MDL component.
2274 * Implements MDL component design pattern defined at:
2275 * https://github.com/jasonmayes/mdl-component-design-pattern
2277 * @param {HTMLElement} element The element that will be upgraded.
2280 var MaterialSpinner = function MaterialSpinner(element) {
2281 this.element_ = element;
2282 // Initialize instance.
2285 window['MaterialSpinner'] = MaterialSpinner;
2287 * Store constants in one place so they can be updated easily.
2289 * @enum {string | number}
2292 MaterialSpinner.prototype.Constant_ = { MDL_SPINNER_LAYER_COUNT: 4 };
2294 * Store strings for class names defined by this component that are used in
2295 * JavaScript. This allows us to simply change it in one place should we
2296 * decide to modify at a later date.
2301 MaterialSpinner.prototype.CssClasses_ = {
2302 MDL_SPINNER_LAYER: 'mdl-spinner__layer',
2303 MDL_SPINNER_CIRCLE_CLIPPER: 'mdl-spinner__circle-clipper',
2304 MDL_SPINNER_CIRCLE: 'mdl-spinner__circle',
2305 MDL_SPINNER_GAP_PATCH: 'mdl-spinner__gap-patch',
2306 MDL_SPINNER_LEFT: 'mdl-spinner__left',
2307 MDL_SPINNER_RIGHT: 'mdl-spinner__right'
2310 * Auxiliary method to create a spinner layer.
2312 * @param {number} index Index of the layer to be created.
2315 MaterialSpinner.prototype.createLayer = function (index) {
2316 var layer = document.createElement('div');
2317 layer.classList.add(this.CssClasses_.MDL_SPINNER_LAYER);
2318 layer.classList.add(this.CssClasses_.MDL_SPINNER_LAYER + '-' + index);
2319 var leftClipper = document.createElement('div');
2320 leftClipper.classList.add(this.CssClasses_.MDL_SPINNER_CIRCLE_CLIPPER);
2321 leftClipper.classList.add(this.CssClasses_.MDL_SPINNER_LEFT);
2322 var gapPatch = document.createElement('div');
2323 gapPatch.classList.add(this.CssClasses_.MDL_SPINNER_GAP_PATCH);
2324 var rightClipper = document.createElement('div');
2325 rightClipper.classList.add(this.CssClasses_.MDL_SPINNER_CIRCLE_CLIPPER);
2326 rightClipper.classList.add(this.CssClasses_.MDL_SPINNER_RIGHT);
2327 var circleOwners = [
2332 for (var i = 0; i < circleOwners.length; i++) {
2333 var circle = document.createElement('div');
2334 circle.classList.add(this.CssClasses_.MDL_SPINNER_CIRCLE);
2335 circleOwners[i].appendChild(circle);
2337 layer.appendChild(leftClipper);
2338 layer.appendChild(gapPatch);
2339 layer.appendChild(rightClipper);
2340 this.element_.appendChild(layer);
2342 MaterialSpinner.prototype['createLayer'] = MaterialSpinner.prototype.createLayer;
2344 * Stops the spinner animation.
2345 * Public method for users who need to stop the spinner for any reason.
2349 MaterialSpinner.prototype.stop = function () {
2350 this.element_.classList.remove('is-active');
2352 MaterialSpinner.prototype['stop'] = MaterialSpinner.prototype.stop;
2354 * Starts the spinner animation.
2355 * Public method for users who need to manually start the spinner for any reason
2356 * (instead of just adding the 'is-active' class to their markup).
2360 MaterialSpinner.prototype.start = function () {
2361 this.element_.classList.add('is-active');
2363 MaterialSpinner.prototype['start'] = MaterialSpinner.prototype.start;
2365 * Initialize element.
2367 MaterialSpinner.prototype.init = function () {
2368 if (this.element_) {
2369 for (var i = 1; i <= this.Constant_.MDL_SPINNER_LAYER_COUNT; i++) {
2370 this.createLayer(i);
2372 this.element_.classList.add('is-upgraded');
2375 // The component registers itself. It can assume componentHandler is available
2376 // in the global scope.
2377 componentHandler.register({
2378 constructor: MaterialSpinner,
2379 classAsString: 'MaterialSpinner',
2380 cssClass: 'mdl-js-spinner',
2385 * Copyright 2015 Google Inc. All Rights Reserved.
2387 * Licensed under the Apache License, Version 2.0 (the "License");
2388 * you may not use this file except in compliance with the License.
2389 * You may obtain a copy of the License at
2391 * http://www.apache.org/licenses/LICENSE-2.0
2393 * Unless required by applicable law or agreed to in writing, software
2394 * distributed under the License is distributed on an "AS IS" BASIS,
2395 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2396 * See the License for the specific language governing permissions and
2397 * limitations under the License.
2400 * Class constructor for Checkbox MDL component.
2401 * Implements MDL component design pattern defined at:
2402 * https://github.com/jasonmayes/mdl-component-design-pattern
2405 * @param {HTMLElement} element The element that will be upgraded.
2407 var MaterialSwitch = function MaterialSwitch(element) {
2408 this.element_ = element;
2409 // Initialize instance.
2412 window['MaterialSwitch'] = MaterialSwitch;
2414 * Store constants in one place so they can be updated easily.
2416 * @enum {string | number}
2419 MaterialSwitch.prototype.Constant_ = { TINY_TIMEOUT: 0.001 };
2421 * Store strings for class names defined by this component that are used in
2422 * JavaScript. This allows us to simply change it in one place should we
2423 * decide to modify at a later date.
2428 MaterialSwitch.prototype.CssClasses_ = {
2429 INPUT: 'mdl-switch__input',
2430 TRACK: 'mdl-switch__track',
2431 THUMB: 'mdl-switch__thumb',
2432 FOCUS_HELPER: 'mdl-switch__focus-helper',
2433 RIPPLE_EFFECT: 'mdl-js-ripple-effect',
2434 RIPPLE_IGNORE_EVENTS: 'mdl-js-ripple-effect--ignore-events',
2435 RIPPLE_CONTAINER: 'mdl-switch__ripple-container',
2436 RIPPLE_CENTER: 'mdl-ripple--center',
2437 RIPPLE: 'mdl-ripple',
2438 IS_FOCUSED: 'is-focused',
2439 IS_DISABLED: 'is-disabled',
2440 IS_CHECKED: 'is-checked'
2443 * Handle change of state.
2445 * @param {Event} event The event that fired.
2448 MaterialSwitch.prototype.onChange_ = function (event) {
2449 this.updateClasses_();
2452 * Handle focus of element.
2454 * @param {Event} event The event that fired.
2457 MaterialSwitch.prototype.onFocus_ = function (event) {
2458 this.element_.classList.add(this.CssClasses_.IS_FOCUSED);
2461 * Handle lost focus of element.
2463 * @param {Event} event The event that fired.
2466 MaterialSwitch.prototype.onBlur_ = function (event) {
2467 this.element_.classList.remove(this.CssClasses_.IS_FOCUSED);
2472 * @param {Event} event The event that fired.
2475 MaterialSwitch.prototype.onMouseUp_ = function (event) {
2479 * Handle class updates.
2483 MaterialSwitch.prototype.updateClasses_ = function () {
2484 this.checkDisabled();
2485 this.checkToggleState();
2492 MaterialSwitch.prototype.blur_ = function () {
2493 // TODO: figure out why there's a focus event being fired after our blur,
2494 // so that we can avoid this hack.
2495 window.setTimeout(function () {
2496 this.inputElement_.blur();
2497 }.bind(this), this.Constant_.TINY_TIMEOUT);
2501 * Check the components disabled state.
2505 MaterialSwitch.prototype.checkDisabled = function () {
2506 if (this.inputElement_.disabled) {
2507 this.element_.classList.add(this.CssClasses_.IS_DISABLED);
2509 this.element_.classList.remove(this.CssClasses_.IS_DISABLED);
2512 MaterialSwitch.prototype['checkDisabled'] = MaterialSwitch.prototype.checkDisabled;
2514 * Check the components toggled state.
2518 MaterialSwitch.prototype.checkToggleState = function () {
2519 if (this.inputElement_.checked) {
2520 this.element_.classList.add(this.CssClasses_.IS_CHECKED);
2522 this.element_.classList.remove(this.CssClasses_.IS_CHECKED);
2525 MaterialSwitch.prototype['checkToggleState'] = MaterialSwitch.prototype.checkToggleState;
2531 MaterialSwitch.prototype.disable = function () {
2532 this.inputElement_.disabled = true;
2533 this.updateClasses_();
2535 MaterialSwitch.prototype['disable'] = MaterialSwitch.prototype.disable;
2541 MaterialSwitch.prototype.enable = function () {
2542 this.inputElement_.disabled = false;
2543 this.updateClasses_();
2545 MaterialSwitch.prototype['enable'] = MaterialSwitch.prototype.enable;
2551 MaterialSwitch.prototype.on = function () {
2552 this.inputElement_.checked = true;
2553 this.updateClasses_();
2555 MaterialSwitch.prototype['on'] = MaterialSwitch.prototype.on;
2557 * Deactivate switch.
2561 MaterialSwitch.prototype.off = function () {
2562 this.inputElement_.checked = false;
2563 this.updateClasses_();
2565 MaterialSwitch.prototype['off'] = MaterialSwitch.prototype.off;
2567 * Initialize element.
2569 MaterialSwitch.prototype.init = function () {
2570 if (this.element_) {
2571 this.inputElement_ = this.element_.querySelector('.' + this.CssClasses_.INPUT);
2572 var track = document.createElement('div');
2573 track.classList.add(this.CssClasses_.TRACK);
2574 var thumb = document.createElement('div');
2575 thumb.classList.add(this.CssClasses_.THUMB);
2576 var focusHelper = document.createElement('span');
2577 focusHelper.classList.add(this.CssClasses_.FOCUS_HELPER);
2578 thumb.appendChild(focusHelper);
2579 this.element_.appendChild(track);
2580 this.element_.appendChild(thumb);
2581 this.boundMouseUpHandler = this.onMouseUp_.bind(this);
2582 if (this.element_.classList.contains(this.CssClasses_.RIPPLE_EFFECT)) {
2583 this.element_.classList.add(this.CssClasses_.RIPPLE_IGNORE_EVENTS);
2584 this.rippleContainerElement_ = document.createElement('span');
2585 this.rippleContainerElement_.classList.add(this.CssClasses_.RIPPLE_CONTAINER);
2586 this.rippleContainerElement_.classList.add(this.CssClasses_.RIPPLE_EFFECT);
2587 this.rippleContainerElement_.classList.add(this.CssClasses_.RIPPLE_CENTER);
2588 this.rippleContainerElement_.addEventListener('mouseup', this.boundMouseUpHandler);
2589 var ripple = document.createElement('span');
2590 ripple.classList.add(this.CssClasses_.RIPPLE);
2591 this.rippleContainerElement_.appendChild(ripple);
2592 this.element_.appendChild(this.rippleContainerElement_);
2594 this.boundChangeHandler = this.onChange_.bind(this);
2595 this.boundFocusHandler = this.onFocus_.bind(this);
2596 this.boundBlurHandler = this.onBlur_.bind(this);
2597 this.inputElement_.addEventListener('change', this.boundChangeHandler);
2598 this.inputElement_.addEventListener('focus', this.boundFocusHandler);
2599 this.inputElement_.addEventListener('blur', this.boundBlurHandler);
2600 this.element_.addEventListener('mouseup', this.boundMouseUpHandler);
2601 this.updateClasses_();
2602 this.element_.classList.add('is-upgraded');
2605 // The component registers itself. It can assume componentHandler is available
2606 // in the global scope.
2607 componentHandler.register({
2608 constructor: MaterialSwitch,
2609 classAsString: 'MaterialSwitch',
2610 cssClass: 'mdl-js-switch',
2615 * Copyright 2015 Google Inc. All Rights Reserved.
2617 * Licensed under the Apache License, Version 2.0 (the "License");
2618 * you may not use this file except in compliance with the License.
2619 * You may obtain a copy of the License at
2621 * http://www.apache.org/licenses/LICENSE-2.0
2623 * Unless required by applicable law or agreed to in writing, software
2624 * distributed under the License is distributed on an "AS IS" BASIS,
2625 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2626 * See the License for the specific language governing permissions and
2627 * limitations under the License.
2630 * Class constructor for Tabs MDL component.
2631 * Implements MDL component design pattern defined at:
2632 * https://github.com/jasonmayes/mdl-component-design-pattern
2635 * @param {Element} element The element that will be upgraded.
2637 var MaterialTabs = function MaterialTabs(element) {
2638 // Stores the HTML element.
2639 this.element_ = element;
2640 // Initialize instance.
2643 window['MaterialTabs'] = MaterialTabs;
2645 * Store constants in one place so they can be updated easily.
2650 MaterialTabs.prototype.Constant_ = {};
2652 * Store strings for class names defined by this component that are used in
2653 * JavaScript. This allows us to simply change it in one place should we
2654 * decide to modify at a later date.
2659 MaterialTabs.prototype.CssClasses_ = {
2660 TAB_CLASS: 'mdl-tabs__tab',
2661 PANEL_CLASS: 'mdl-tabs__panel',
2662 ACTIVE_CLASS: 'is-active',
2663 UPGRADED_CLASS: 'is-upgraded',
2664 MDL_JS_RIPPLE_EFFECT: 'mdl-js-ripple-effect',
2665 MDL_RIPPLE_CONTAINER: 'mdl-tabs__ripple-container',
2666 MDL_RIPPLE: 'mdl-ripple',
2667 MDL_JS_RIPPLE_EFFECT_IGNORE_EVENTS: 'mdl-js-ripple-effect--ignore-events'
2670 * Handle clicks to a tabs component
2674 MaterialTabs.prototype.initTabs_ = function () {
2675 if (this.element_.classList.contains(this.CssClasses_.MDL_JS_RIPPLE_EFFECT)) {
2676 this.element_.classList.add(this.CssClasses_.MDL_JS_RIPPLE_EFFECT_IGNORE_EVENTS);
2678 // Select element tabs, document panels
2679 this.tabs_ = this.element_.querySelectorAll('.' + this.CssClasses_.TAB_CLASS);
2680 this.panels_ = this.element_.querySelectorAll('.' + this.CssClasses_.PANEL_CLASS);
2681 // Create new tabs for each tab element
2682 for (var i = 0; i < this.tabs_.length; i++) {
2683 new MaterialTab(this.tabs_[i], this);
2685 this.element_.classList.add(this.CssClasses_.UPGRADED_CLASS);
2688 * Reset tab state, dropping active classes
2692 MaterialTabs.prototype.resetTabState_ = function () {
2693 for (var k = 0; k < this.tabs_.length; k++) {
2694 this.tabs_[k].classList.remove(this.CssClasses_.ACTIVE_CLASS);
2698 * Reset panel state, droping active classes
2702 MaterialTabs.prototype.resetPanelState_ = function () {
2703 for (var j = 0; j < this.panels_.length; j++) {
2704 this.panels_[j].classList.remove(this.CssClasses_.ACTIVE_CLASS);
2708 * Initialize element.
2710 MaterialTabs.prototype.init = function () {
2711 if (this.element_) {
2716 * Constructor for an individual tab.
2719 * @param {Element} tab The HTML element for the tab.
2720 * @param {MaterialTabs} ctx The MaterialTabs object that owns the tab.
2722 function MaterialTab(tab, ctx) {
2724 if (ctx.element_.classList.contains(ctx.CssClasses_.MDL_JS_RIPPLE_EFFECT)) {
2725 var rippleContainer = document.createElement('span');
2726 rippleContainer.classList.add(ctx.CssClasses_.MDL_RIPPLE_CONTAINER);
2727 rippleContainer.classList.add(ctx.CssClasses_.MDL_JS_RIPPLE_EFFECT);
2728 var ripple = document.createElement('span');
2729 ripple.classList.add(ctx.CssClasses_.MDL_RIPPLE);
2730 rippleContainer.appendChild(ripple);
2731 tab.appendChild(rippleContainer);
2733 tab.addEventListener('click', function (e) {
2734 if (tab.getAttribute('href').charAt(0) === '#') {
2736 var href = tab.href.split('#')[1];
2737 var panel = ctx.element_.querySelector('#' + href);
2738 ctx.resetTabState_();
2739 ctx.resetPanelState_();
2740 tab.classList.add(ctx.CssClasses_.ACTIVE_CLASS);
2741 panel.classList.add(ctx.CssClasses_.ACTIVE_CLASS);
2746 // The component registers itself. It can assume componentHandler is available
2747 // in the global scope.
2748 componentHandler.register({
2749 constructor: MaterialTabs,
2750 classAsString: 'MaterialTabs',
2751 cssClass: 'mdl-js-tabs'
2755 * Copyright 2015 Google Inc. All Rights Reserved.
2757 * Licensed under the Apache License, Version 2.0 (the "License");
2758 * you may not use this file except in compliance with the License.
2759 * You may obtain a copy of the License at
2761 * http://www.apache.org/licenses/LICENSE-2.0
2763 * Unless required by applicable law or agreed to in writing, software
2764 * distributed under the License is distributed on an "AS IS" BASIS,
2765 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2766 * See the License for the specific language governing permissions and
2767 * limitations under the License.
2770 * Class constructor for Textfield MDL component.
2771 * Implements MDL component design pattern defined at:
2772 * https://github.com/jasonmayes/mdl-component-design-pattern
2775 * @param {HTMLElement} element The element that will be upgraded.
2777 var MaterialTextfield = function MaterialTextfield(element) {
2778 this.element_ = element;
2779 this.maxRows = this.Constant_.NO_MAX_ROWS;
2780 // Initialize instance.
2783 window['MaterialTextfield'] = MaterialTextfield;
2785 * Store constants in one place so they can be updated easily.
2787 * @enum {string | number}
2790 MaterialTextfield.prototype.Constant_ = {
2792 MAX_ROWS_ATTRIBUTE: 'maxrows'
2795 * Store strings for class names defined by this component that are used in
2796 * JavaScript. This allows us to simply change it in one place should we
2797 * decide to modify at a later date.
2802 MaterialTextfield.prototype.CssClasses_ = {
2803 LABEL: 'mdl-textfield__label',
2804 INPUT: 'mdl-textfield__input',
2805 IS_DIRTY: 'is-dirty',
2806 IS_FOCUSED: 'is-focused',
2807 IS_DISABLED: 'is-disabled',
2808 IS_INVALID: 'is-invalid',
2809 IS_UPGRADED: 'is-upgraded',
2810 HAS_PLACEHOLDER: 'has-placeholder'
2813 * Handle input being entered.
2815 * @param {Event} event The event that fired.
2818 MaterialTextfield.prototype.onKeyDown_ = function (event) {
2819 var currentRowCount = event.target.value.split('\n').length;
2820 if (event.keyCode === 13) {
2821 if (currentRowCount >= this.maxRows) {
2822 event.preventDefault();
2829 * @param {Event} event The event that fired.
2832 MaterialTextfield.prototype.onFocus_ = function (event) {
2833 this.element_.classList.add(this.CssClasses_.IS_FOCUSED);
2836 * Handle lost focus.
2838 * @param {Event} event The event that fired.
2841 MaterialTextfield.prototype.onBlur_ = function (event) {
2842 this.element_.classList.remove(this.CssClasses_.IS_FOCUSED);
2845 * Handle reset event from out side.
2847 * @param {Event} event The event that fired.
2850 MaterialTextfield.prototype.onReset_ = function (event) {
2851 this.updateClasses_();
2854 * Handle class updates.
2858 MaterialTextfield.prototype.updateClasses_ = function () {
2859 this.checkDisabled();
2860 this.checkValidity();
2866 * Check the disabled state and update field accordingly.
2870 MaterialTextfield.prototype.checkDisabled = function () {
2871 if (this.input_.disabled) {
2872 this.element_.classList.add(this.CssClasses_.IS_DISABLED);
2874 this.element_.classList.remove(this.CssClasses_.IS_DISABLED);
2877 MaterialTextfield.prototype['checkDisabled'] = MaterialTextfield.prototype.checkDisabled;
2879 * Check the focus state and update field accordingly.
2883 MaterialTextfield.prototype.checkFocus = function () {
2884 if (Boolean(this.element_.querySelector(':focus'))) {
2885 this.element_.classList.add(this.CssClasses_.IS_FOCUSED);
2887 this.element_.classList.remove(this.CssClasses_.IS_FOCUSED);
2890 MaterialTextfield.prototype['checkFocus'] = MaterialTextfield.prototype.checkFocus;
2892 * Check the validity state and update field accordingly.
2896 MaterialTextfield.prototype.checkValidity = function () {
2897 if (this.input_.validity) {
2898 if (this.input_.validity.valid) {
2899 this.element_.classList.remove(this.CssClasses_.IS_INVALID);
2901 this.element_.classList.add(this.CssClasses_.IS_INVALID);
2905 MaterialTextfield.prototype['checkValidity'] = MaterialTextfield.prototype.checkValidity;
2907 * Check the dirty state and update field accordingly.
2911 MaterialTextfield.prototype.checkDirty = function () {
2912 if (this.input_.value && this.input_.value.length > 0) {
2913 this.element_.classList.add(this.CssClasses_.IS_DIRTY);
2915 this.element_.classList.remove(this.CssClasses_.IS_DIRTY);
2918 MaterialTextfield.prototype['checkDirty'] = MaterialTextfield.prototype.checkDirty;
2920 * Disable text field.
2924 MaterialTextfield.prototype.disable = function () {
2925 this.input_.disabled = true;
2926 this.updateClasses_();
2928 MaterialTextfield.prototype['disable'] = MaterialTextfield.prototype.disable;
2930 * Enable text field.
2934 MaterialTextfield.prototype.enable = function () {
2935 this.input_.disabled = false;
2936 this.updateClasses_();
2938 MaterialTextfield.prototype['enable'] = MaterialTextfield.prototype.enable;
2940 * Update text field value.
2942 * @param {string} value The value to which to set the control (optional).
2945 MaterialTextfield.prototype.change = function (value) {
2946 this.input_.value = value || '';
2947 this.updateClasses_();
2949 MaterialTextfield.prototype['change'] = MaterialTextfield.prototype.change;
2951 * Initialize element.
2953 MaterialTextfield.prototype.init = function () {
2954 if (this.element_) {
2955 this.label_ = this.element_.querySelector('.' + this.CssClasses_.LABEL);
2956 this.input_ = this.element_.querySelector('.' + this.CssClasses_.INPUT);
2958 if (this.input_.hasAttribute(this.Constant_.MAX_ROWS_ATTRIBUTE)) {
2959 this.maxRows = parseInt(this.input_.getAttribute(this.Constant_.MAX_ROWS_ATTRIBUTE), 10);
2960 if (isNaN(this.maxRows)) {
2961 this.maxRows = this.Constant_.NO_MAX_ROWS;
2964 if (this.input_.hasAttribute('placeholder')) {
2965 this.element_.classList.add(this.CssClasses_.HAS_PLACEHOLDER);
2967 this.boundUpdateClassesHandler = this.updateClasses_.bind(this);
2968 this.boundFocusHandler = this.onFocus_.bind(this);
2969 this.boundBlurHandler = this.onBlur_.bind(this);
2970 this.boundResetHandler = this.onReset_.bind(this);
2971 this.input_.addEventListener('input', this.boundUpdateClassesHandler);
2972 this.input_.addEventListener('focus', this.boundFocusHandler);
2973 this.input_.addEventListener('blur', this.boundBlurHandler);
2974 this.input_.addEventListener('reset', this.boundResetHandler);
2975 if (this.maxRows !== this.Constant_.NO_MAX_ROWS) {
2976 // TODO: This should handle pasting multi line text.
2977 // Currently doesn't.
2978 this.boundKeyDownHandler = this.onKeyDown_.bind(this);
2979 this.input_.addEventListener('keydown', this.boundKeyDownHandler);
2981 var invalid = this.element_.classList.contains(this.CssClasses_.IS_INVALID);
2982 this.updateClasses_();
2983 this.element_.classList.add(this.CssClasses_.IS_UPGRADED);
2985 this.element_.classList.add(this.CssClasses_.IS_INVALID);
2987 if (this.input_.hasAttribute('autofocus')) {
2988 this.element_.focus();
2994 // The component registers itself. It can assume componentHandler is available
2995 // in the global scope.
2996 componentHandler.register({
2997 constructor: MaterialTextfield,
2998 classAsString: 'MaterialTextfield',
2999 cssClass: 'mdl-js-textfield',
3004 * Copyright 2015 Google Inc. All Rights Reserved.
3006 * Licensed under the Apache License, Version 2.0 (the "License");
3007 * you may not use this file except in compliance with the License.
3008 * You may obtain a copy of the License at
3010 * http://www.apache.org/licenses/LICENSE-2.0
3012 * Unless required by applicable law or agreed to in writing, software
3013 * distributed under the License is distributed on an "AS IS" BASIS,
3014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
3015 * See the License for the specific language governing permissions and
3016 * limitations under the License.
3019 * Class constructor for Tooltip MDL component.
3020 * Implements MDL component design pattern defined at:
3021 * https://github.com/jasonmayes/mdl-component-design-pattern
3024 * @param {HTMLElement} element The element that will be upgraded.
3026 var MaterialTooltip = function MaterialTooltip(element) {
3027 this.element_ = element;
3028 // Initialize instance.
3031 window['MaterialTooltip'] = MaterialTooltip;
3033 * Store constants in one place so they can be updated easily.
3035 * @enum {string | number}
3038 MaterialTooltip.prototype.Constant_ = {};
3040 * Store strings for class names defined by this component that are used in
3041 * JavaScript. This allows us to simply change it in one place should we
3042 * decide to modify at a later date.
3047 MaterialTooltip.prototype.CssClasses_ = {
3048 IS_ACTIVE: 'is-active',
3049 BOTTOM: 'mdl-tooltip--bottom',
3050 LEFT: 'mdl-tooltip--left',
3051 RIGHT: 'mdl-tooltip--right',
3052 TOP: 'mdl-tooltip--top'
3055 * Handle mouseenter for tooltip.
3057 * @param {Event} event The event that fired.
3060 MaterialTooltip.prototype.handleMouseEnter_ = function (event) {
3061 var props = event.target.getBoundingClientRect();
3062 var left = props.left + props.width / 2;
3063 var top = props.top + props.height / 2;
3064 var marginLeft = -1 * (this.element_.offsetWidth / 2);
3065 var marginTop = -1 * (this.element_.offsetHeight / 2);
3066 if (this.element_.classList.contains(this.CssClasses_.LEFT) || this.element_.classList.contains(this.CssClasses_.RIGHT)) {
3067 left = props.width / 2;
3068 if (top + marginTop < 0) {
3069 this.element_.style.top = '0';
3070 this.element_.style.marginTop = '0';
3072 this.element_.style.top = top + 'px';
3073 this.element_.style.marginTop = marginTop + 'px';
3076 if (left + marginLeft < 0) {
3077 this.element_.style.left = '0';
3078 this.element_.style.marginLeft = '0';
3080 this.element_.style.left = left + 'px';
3081 this.element_.style.marginLeft = marginLeft + 'px';
3084 if (this.element_.classList.contains(this.CssClasses_.TOP)) {
3085 this.element_.style.top = props.top - this.element_.offsetHeight - 10 + 'px';
3086 } else if (this.element_.classList.contains(this.CssClasses_.RIGHT)) {
3087 this.element_.style.left = props.left + props.width + 10 + 'px';
3088 } else if (this.element_.classList.contains(this.CssClasses_.LEFT)) {
3089 this.element_.style.left = props.left - this.element_.offsetWidth - 10 + 'px';
3091 this.element_.style.top = props.top + props.height + 10 + 'px';
3093 this.element_.classList.add(this.CssClasses_.IS_ACTIVE);
3096 * Hide tooltip on mouseleave or scroll
3100 MaterialTooltip.prototype.hideTooltip_ = function () {
3101 this.element_.classList.remove(this.CssClasses_.IS_ACTIVE);
3104 * Initialize element.
3106 MaterialTooltip.prototype.init = function () {
3107 if (this.element_) {
3108 var forElId = this.element_.getAttribute('for') || this.element_.getAttribute('data-mdl-for');
3110 this.forElement_ = document.getElementById(forElId);
3112 if (this.forElement_) {
3113 // It's left here because it prevents accidental text selection on Android
3114 if (!this.forElement_.hasAttribute('tabindex')) {
3115 this.forElement_.setAttribute('tabindex', '0');
3117 this.boundMouseEnterHandler = this.handleMouseEnter_.bind(this);
3118 this.boundMouseLeaveAndScrollHandler = this.hideTooltip_.bind(this);
3119 this.forElement_.addEventListener('mouseenter', this.boundMouseEnterHandler, false);
3120 this.forElement_.addEventListener('touchend', this.boundMouseEnterHandler, false);
3121 this.forElement_.addEventListener('mouseleave', this.boundMouseLeaveAndScrollHandler, false);
3122 window.addEventListener('scroll', this.boundMouseLeaveAndScrollHandler, true);
3123 window.addEventListener('touchstart', this.boundMouseLeaveAndScrollHandler);
3127 // The component registers itself. It can assume componentHandler is available
3128 // in the global scope.
3129 componentHandler.register({
3130 constructor: MaterialTooltip,
3131 classAsString: 'MaterialTooltip',
3132 cssClass: 'mdl-tooltip'
3136 * Copyright 2015 Google Inc. All Rights Reserved.
3138 * Licensed under the Apache License, Version 2.0 (the "License");
3139 * you may not use this file except in compliance with the License.
3140 * You may obtain a copy of the License at
3142 * http://www.apache.org/licenses/LICENSE-2.0
3144 * Unless required by applicable law or agreed to in writing, software
3145 * distributed under the License is distributed on an "AS IS" BASIS,
3146 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
3147 * See the License for the specific language governing permissions and
3148 * limitations under the License.
3151 * Class constructor for Layout MDL component.
3152 * Implements MDL component design pattern defined at:
3153 * https://github.com/jasonmayes/mdl-component-design-pattern
3156 * @param {HTMLElement} element The element that will be upgraded.
3158 var MaterialLayout = function MaterialLayout(element) {
3159 this.element_ = element;
3160 // Initialize instance.
3163 window['MaterialLayout'] = MaterialLayout;
3165 * Store constants in one place so they can be updated easily.
3167 * @enum {string | number}
3170 MaterialLayout.prototype.Constant_ = {
3171 MAX_WIDTH: '(max-width: 1024px)',
3172 TAB_SCROLL_PIXELS: 100,
3173 RESIZE_TIMEOUT: 100,
3174 MENU_ICON: '',
3175 CHEVRON_LEFT: 'chevron_left',
3176 CHEVRON_RIGHT: 'chevron_right'
3179 * Keycodes, for code readability.
3184 MaterialLayout.prototype.Keycodes_ = {
3195 MaterialLayout.prototype.Mode_ = {
3202 * Store strings for class names defined by this component that are used in
3203 * JavaScript. This allows us to simply change it in one place should we
3204 * decide to modify at a later date.
3209 MaterialLayout.prototype.CssClasses_ = {
3210 CONTAINER: 'mdl-layout__container',
3211 HEADER: 'mdl-layout__header',
3212 DRAWER: 'mdl-layout__drawer',
3213 CONTENT: 'mdl-layout__content',
3214 DRAWER_BTN: 'mdl-layout__drawer-button',
3215 ICON: 'material-icons',
3216 JS_RIPPLE_EFFECT: 'mdl-js-ripple-effect',
3217 RIPPLE_CONTAINER: 'mdl-layout__tab-ripple-container',
3218 RIPPLE: 'mdl-ripple',
3219 RIPPLE_IGNORE_EVENTS: 'mdl-js-ripple-effect--ignore-events',
3220 HEADER_SEAMED: 'mdl-layout__header--seamed',
3221 HEADER_WATERFALL: 'mdl-layout__header--waterfall',
3222 HEADER_SCROLL: 'mdl-layout__header--scroll',
3223 FIXED_HEADER: 'mdl-layout--fixed-header',
3224 OBFUSCATOR: 'mdl-layout__obfuscator',
3225 TAB_BAR: 'mdl-layout__tab-bar',
3226 TAB_CONTAINER: 'mdl-layout__tab-bar-container',
3227 TAB: 'mdl-layout__tab',
3228 TAB_BAR_BUTTON: 'mdl-layout__tab-bar-button',
3229 TAB_BAR_LEFT_BUTTON: 'mdl-layout__tab-bar-left-button',
3230 TAB_BAR_RIGHT_BUTTON: 'mdl-layout__tab-bar-right-button',
3231 TAB_MANUAL_SWITCH: 'mdl-layout__tab-manual-switch',
3232 PANEL: 'mdl-layout__tab-panel',
3233 HAS_DRAWER: 'has-drawer',
3234 HAS_TABS: 'has-tabs',
3235 HAS_SCROLLING_HEADER: 'has-scrolling-header',
3236 CASTING_SHADOW: 'is-casting-shadow',
3237 IS_COMPACT: 'is-compact',
3238 IS_SMALL_SCREEN: 'is-small-screen',
3239 IS_DRAWER_OPEN: 'is-visible',
3240 IS_ACTIVE: 'is-active',
3241 IS_UPGRADED: 'is-upgraded',
3242 IS_ANIMATING: 'is-animating',
3243 ON_LARGE_SCREEN: 'mdl-layout--large-screen-only',
3244 ON_SMALL_SCREEN: 'mdl-layout--small-screen-only'
3247 * Handles scrolling on the content.
3251 MaterialLayout.prototype.contentScrollHandler_ = function () {
3252 if (this.header_.classList.contains(this.CssClasses_.IS_ANIMATING)) {
3255 var headerVisible = !this.element_.classList.contains(this.CssClasses_.IS_SMALL_SCREEN) || this.element_.classList.contains(this.CssClasses_.FIXED_HEADER);
3256 if (this.content_.scrollTop > 0 && !this.header_.classList.contains(this.CssClasses_.IS_COMPACT)) {
3257 this.header_.classList.add(this.CssClasses_.CASTING_SHADOW);
3258 this.header_.classList.add(this.CssClasses_.IS_COMPACT);
3259 if (headerVisible) {
3260 this.header_.classList.add(this.CssClasses_.IS_ANIMATING);
3262 } else if (this.content_.scrollTop <= 0 && this.header_.classList.contains(this.CssClasses_.IS_COMPACT)) {
3263 this.header_.classList.remove(this.CssClasses_.CASTING_SHADOW);
3264 this.header_.classList.remove(this.CssClasses_.IS_COMPACT);
3265 if (headerVisible) {
3266 this.header_.classList.add(this.CssClasses_.IS_ANIMATING);
3271 * Handles a keyboard event on the drawer.
3273 * @param {Event} evt The event that fired.
3276 MaterialLayout.prototype.keyboardEventHandler_ = function (evt) {
3277 // Only react when the drawer is open.
3278 if (evt.keyCode === this.Keycodes_.ESCAPE && this.drawer_.classList.contains(this.CssClasses_.IS_DRAWER_OPEN)) {
3279 this.toggleDrawer();
3283 * Handles changes in screen size.
3287 MaterialLayout.prototype.screenSizeHandler_ = function () {
3288 if (this.screenSizeMediaQuery_.matches) {
3289 this.element_.classList.add(this.CssClasses_.IS_SMALL_SCREEN);
3291 this.element_.classList.remove(this.CssClasses_.IS_SMALL_SCREEN);
3292 // Collapse drawer (if any) when moving to a large screen size.
3294 this.drawer_.classList.remove(this.CssClasses_.IS_DRAWER_OPEN);
3295 this.obfuscator_.classList.remove(this.CssClasses_.IS_DRAWER_OPEN);
3300 * Handles events of drawer button.
3302 * @param {Event} evt The event that fired.
3305 MaterialLayout.prototype.drawerToggleHandler_ = function (evt) {
3306 if (evt && evt.type === 'keydown') {
3307 if (evt.keyCode === this.Keycodes_.SPACE || evt.keyCode === this.Keycodes_.ENTER) {
3308 // prevent scrolling in drawer nav
3309 evt.preventDefault();
3311 // prevent other keys
3315 this.toggleDrawer();
3318 * Handles (un)setting the `is-animating` class
3322 MaterialLayout.prototype.headerTransitionEndHandler_ = function () {
3323 this.header_.classList.remove(this.CssClasses_.IS_ANIMATING);
3326 * Handles expanding the header on click
3330 MaterialLayout.prototype.headerClickHandler_ = function () {
3331 if (this.header_.classList.contains(this.CssClasses_.IS_COMPACT)) {
3332 this.header_.classList.remove(this.CssClasses_.IS_COMPACT);
3333 this.header_.classList.add(this.CssClasses_.IS_ANIMATING);
3337 * Reset tab state, dropping active classes
3341 MaterialLayout.prototype.resetTabState_ = function (tabBar) {
3342 for (var k = 0; k < tabBar.length; k++) {
3343 tabBar[k].classList.remove(this.CssClasses_.IS_ACTIVE);
3347 * Reset panel state, droping active classes
3351 MaterialLayout.prototype.resetPanelState_ = function (panels) {
3352 for (var j = 0; j < panels.length; j++) {
3353 panels[j].classList.remove(this.CssClasses_.IS_ACTIVE);
3357 * Toggle drawer state
3361 MaterialLayout.prototype.toggleDrawer = function () {
3362 var drawerButton = this.element_.querySelector('.' + this.CssClasses_.DRAWER_BTN);
3363 this.drawer_.classList.toggle(this.CssClasses_.IS_DRAWER_OPEN);
3364 this.obfuscator_.classList.toggle(this.CssClasses_.IS_DRAWER_OPEN);
3365 // Set accessibility properties.
3366 if (this.drawer_.classList.contains(this.CssClasses_.IS_DRAWER_OPEN)) {
3367 this.drawer_.setAttribute('aria-hidden', 'false');
3368 drawerButton.setAttribute('aria-expanded', 'true');
3370 this.drawer_.setAttribute('aria-hidden', 'true');
3371 drawerButton.setAttribute('aria-expanded', 'false');
3374 MaterialLayout.prototype['toggleDrawer'] = MaterialLayout.prototype.toggleDrawer;
3376 * Initialize element.
3378 MaterialLayout.prototype.init = function () {
3379 if (this.element_) {
3380 var container = document.createElement('div');
3381 container.classList.add(this.CssClasses_.CONTAINER);
3382 var focusedElement = this.element_.querySelector(':focus');
3383 this.element_.parentElement.insertBefore(container, this.element_);
3384 this.element_.parentElement.removeChild(this.element_);
3385 container.appendChild(this.element_);
3386 if (focusedElement) {
3387 focusedElement.focus();
3389 var directChildren = this.element_.childNodes;
3390 var numChildren = directChildren.length;
3391 for (var c = 0; c < numChildren; c++) {
3392 var child = directChildren[c];
3393 if (child.classList && child.classList.contains(this.CssClasses_.HEADER)) {
3394 this.header_ = child;
3396 if (child.classList && child.classList.contains(this.CssClasses_.DRAWER)) {
3397 this.drawer_ = child;
3399 if (child.classList && child.classList.contains(this.CssClasses_.CONTENT)) {
3400 this.content_ = child;
3403 window.addEventListener('pageshow', function (e) {
3405 // when page is loaded from back/forward cache
3406 // trigger repaint to let layout scroll in safari
3407 this.element_.style.overflowY = 'hidden';
3408 requestAnimationFrame(function () {
3409 this.element_.style.overflowY = '';
3412 }.bind(this), false);
3414 this.tabBar_ = this.header_.querySelector('.' + this.CssClasses_.TAB_BAR);
3416 var mode = this.Mode_.STANDARD;
3418 if (this.header_.classList.contains(this.CssClasses_.HEADER_SEAMED)) {
3419 mode = this.Mode_.SEAMED;
3420 } else if (this.header_.classList.contains(this.CssClasses_.HEADER_WATERFALL)) {
3421 mode = this.Mode_.WATERFALL;
3422 this.header_.addEventListener('transitionend', this.headerTransitionEndHandler_.bind(this));
3423 this.header_.addEventListener('click', this.headerClickHandler_.bind(this));
3424 } else if (this.header_.classList.contains(this.CssClasses_.HEADER_SCROLL)) {
3425 mode = this.Mode_.SCROLL;
3426 container.classList.add(this.CssClasses_.HAS_SCROLLING_HEADER);
3428 if (mode === this.Mode_.STANDARD) {
3429 this.header_.classList.add(this.CssClasses_.CASTING_SHADOW);
3431 this.tabBar_.classList.add(this.CssClasses_.CASTING_SHADOW);
3433 } else if (mode === this.Mode_.SEAMED || mode === this.Mode_.SCROLL) {
3434 this.header_.classList.remove(this.CssClasses_.CASTING_SHADOW);
3436 this.tabBar_.classList.remove(this.CssClasses_.CASTING_SHADOW);
3438 } else if (mode === this.Mode_.WATERFALL) {
3439 // Add and remove shadows depending on scroll position.
3440 // Also add/remove auxiliary class for styling of the compact version of
3442 this.content_.addEventListener('scroll', this.contentScrollHandler_.bind(this));
3443 this.contentScrollHandler_();
3446 // Add drawer toggling button to our layout, if we have an openable drawer.
3448 var drawerButton = this.element_.querySelector('.' + this.CssClasses_.DRAWER_BTN);
3449 if (!drawerButton) {
3450 drawerButton = document.createElement('div');
3451 drawerButton.setAttribute('aria-expanded', 'false');
3452 drawerButton.setAttribute('role', 'button');
3453 drawerButton.setAttribute('tabindex', '0');
3454 drawerButton.classList.add(this.CssClasses_.DRAWER_BTN);
3455 var drawerButtonIcon = document.createElement('i');
3456 drawerButtonIcon.classList.add(this.CssClasses_.ICON);
3457 drawerButtonIcon.innerHTML = this.Constant_.MENU_ICON;
3458 drawerButton.appendChild(drawerButtonIcon);
3460 if (this.drawer_.classList.contains(this.CssClasses_.ON_LARGE_SCREEN)) {
3461 //If drawer has ON_LARGE_SCREEN class then add it to the drawer toggle button as well.
3462 drawerButton.classList.add(this.CssClasses_.ON_LARGE_SCREEN);
3463 } else if (this.drawer_.classList.contains(this.CssClasses_.ON_SMALL_SCREEN)) {
3464 //If drawer has ON_SMALL_SCREEN class then add it to the drawer toggle button as well.
3465 drawerButton.classList.add(this.CssClasses_.ON_SMALL_SCREEN);
3467 drawerButton.addEventListener('click', this.drawerToggleHandler_.bind(this));
3468 drawerButton.addEventListener('keydown', this.drawerToggleHandler_.bind(this));
3469 // Add a class if the layout has a drawer, for altering the left padding.
3470 // Adds the HAS_DRAWER to the elements since this.header_ may or may
3472 this.element_.classList.add(this.CssClasses_.HAS_DRAWER);
3473 // If we have a fixed header, add the button to the header rather than
3475 if (this.element_.classList.contains(this.CssClasses_.FIXED_HEADER)) {
3476 this.header_.insertBefore(drawerButton, this.header_.firstChild);
3478 this.element_.insertBefore(drawerButton, this.content_);
3480 var obfuscator = document.createElement('div');
3481 obfuscator.classList.add(this.CssClasses_.OBFUSCATOR);
3482 this.element_.appendChild(obfuscator);
3483 obfuscator.addEventListener('click', this.drawerToggleHandler_.bind(this));
3484 this.obfuscator_ = obfuscator;
3485 this.drawer_.addEventListener('keydown', this.keyboardEventHandler_.bind(this));
3486 this.drawer_.setAttribute('aria-hidden', 'true');
3488 // Keep an eye on screen size, and add/remove auxiliary class for styling
3489 // of small screens.
3490 this.screenSizeMediaQuery_ = window.matchMedia(this.Constant_.MAX_WIDTH);
3491 this.screenSizeMediaQuery_.addListener(this.screenSizeHandler_.bind(this));
3492 this.screenSizeHandler_();
3493 // Initialize tabs, if any.
3494 if (this.header_ && this.tabBar_) {
3495 this.element_.classList.add(this.CssClasses_.HAS_TABS);
3496 var tabContainer = document.createElement('div');
3497 tabContainer.classList.add(this.CssClasses_.TAB_CONTAINER);
3498 this.header_.insertBefore(tabContainer, this.tabBar_);
3499 this.header_.removeChild(this.tabBar_);
3500 var leftButton = document.createElement('div');
3501 leftButton.classList.add(this.CssClasses_.TAB_BAR_BUTTON);
3502 leftButton.classList.add(this.CssClasses_.TAB_BAR_LEFT_BUTTON);
3503 var leftButtonIcon = document.createElement('i');
3504 leftButtonIcon.classList.add(this.CssClasses_.ICON);
3505 leftButtonIcon.textContent = this.Constant_.CHEVRON_LEFT;
3506 leftButton.appendChild(leftButtonIcon);
3507 leftButton.addEventListener('click', function () {
3508 this.tabBar_.scrollLeft -= this.Constant_.TAB_SCROLL_PIXELS;
3510 var rightButton = document.createElement('div');
3511 rightButton.classList.add(this.CssClasses_.TAB_BAR_BUTTON);
3512 rightButton.classList.add(this.CssClasses_.TAB_BAR_RIGHT_BUTTON);
3513 var rightButtonIcon = document.createElement('i');
3514 rightButtonIcon.classList.add(this.CssClasses_.ICON);
3515 rightButtonIcon.textContent = this.Constant_.CHEVRON_RIGHT;
3516 rightButton.appendChild(rightButtonIcon);
3517 rightButton.addEventListener('click', function () {
3518 this.tabBar_.scrollLeft += this.Constant_.TAB_SCROLL_PIXELS;
3520 tabContainer.appendChild(leftButton);
3521 tabContainer.appendChild(this.tabBar_);
3522 tabContainer.appendChild(rightButton);
3523 // Add and remove tab buttons depending on scroll position and total
3525 var tabUpdateHandler = function () {
3526 if (this.tabBar_.scrollLeft > 0) {
3527 leftButton.classList.add(this.CssClasses_.IS_ACTIVE);
3529 leftButton.classList.remove(this.CssClasses_.IS_ACTIVE);
3531 if (this.tabBar_.scrollLeft < this.tabBar_.scrollWidth - this.tabBar_.offsetWidth) {
3532 rightButton.classList.add(this.CssClasses_.IS_ACTIVE);
3534 rightButton.classList.remove(this.CssClasses_.IS_ACTIVE);
3537 this.tabBar_.addEventListener('scroll', tabUpdateHandler);
3539 // Update tabs when the window resizes.
3540 var windowResizeHandler = function () {
3541 // Use timeouts to make sure it doesn't happen too often.
3542 if (this.resizeTimeoutId_) {
3543 clearTimeout(this.resizeTimeoutId_);
3545 this.resizeTimeoutId_ = setTimeout(function () {
3547 this.resizeTimeoutId_ = null;
3548 }.bind(this), this.Constant_.RESIZE_TIMEOUT);
3550 window.addEventListener('resize', windowResizeHandler);
3551 if (this.tabBar_.classList.contains(this.CssClasses_.JS_RIPPLE_EFFECT)) {
3552 this.tabBar_.classList.add(this.CssClasses_.RIPPLE_IGNORE_EVENTS);
3554 // Select element tabs, document panels
3555 var tabs = this.tabBar_.querySelectorAll('.' + this.CssClasses_.TAB);
3556 var panels = this.content_.querySelectorAll('.' + this.CssClasses_.PANEL);
3557 // Create new tabs for each tab element
3558 for (var i = 0; i < tabs.length; i++) {
3559 new MaterialLayoutTab(tabs[i], tabs, panels, this);
3562 this.element_.classList.add(this.CssClasses_.IS_UPGRADED);
3566 * Constructor for an individual tab.
3569 * @param {HTMLElement} tab The HTML element for the tab.
3570 * @param {!Array<HTMLElement>} tabs Array with HTML elements for all tabs.
3571 * @param {!Array<HTMLElement>} panels Array with HTML elements for all panels.
3572 * @param {MaterialLayout} layout The MaterialLayout object that owns the tab.
3574 function MaterialLayoutTab(tab, tabs, panels, layout) {
3576 * Auxiliary method to programmatically select a tab in the UI.
3578 function selectTab() {
3579 var href = tab.href.split('#')[1];
3580 var panel = layout.content_.querySelector('#' + href);
3581 layout.resetTabState_(tabs);
3582 layout.resetPanelState_(panels);
3583 tab.classList.add(layout.CssClasses_.IS_ACTIVE);
3584 panel.classList.add(layout.CssClasses_.IS_ACTIVE);
3586 if (layout.tabBar_.classList.contains(layout.CssClasses_.JS_RIPPLE_EFFECT)) {
3587 var rippleContainer = document.createElement('span');
3588 rippleContainer.classList.add(layout.CssClasses_.RIPPLE_CONTAINER);
3589 rippleContainer.classList.add(layout.CssClasses_.JS_RIPPLE_EFFECT);
3590 var ripple = document.createElement('span');
3591 ripple.classList.add(layout.CssClasses_.RIPPLE);
3592 rippleContainer.appendChild(ripple);
3593 tab.appendChild(rippleContainer);
3595 if (!layout.tabBar_.classList.contains(layout.CssClasses_.TAB_MANUAL_SWITCH)) {
3596 tab.addEventListener('click', function (e) {
3597 if (tab.getAttribute('href').charAt(0) === '#') {
3603 tab.show = selectTab;
3605 window['MaterialLayoutTab'] = MaterialLayoutTab;
3606 // The component registers itself. It can assume componentHandler is available
3607 // in the global scope.
3608 componentHandler.register({
3609 constructor: MaterialLayout,
3610 classAsString: 'MaterialLayout',
3611 cssClass: 'mdl-js-layout'
3615 * Copyright 2015 Google Inc. All Rights Reserved.
3617 * Licensed under the Apache License, Version 2.0 (the "License");
3618 * you may not use this file except in compliance with the License.
3619 * You may obtain a copy of the License at
3621 * http://www.apache.org/licenses/LICENSE-2.0
3623 * Unless required by applicable law or agreed to in writing, software
3624 * distributed under the License is distributed on an "AS IS" BASIS,
3625 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
3626 * See the License for the specific language governing permissions and
3627 * limitations under the License.
3630 * Class constructor for Data Table Card MDL component.
3631 * Implements MDL component design pattern defined at:
3632 * https://github.com/jasonmayes/mdl-component-design-pattern
3635 * @param {Element} element The element that will be upgraded.
3637 var MaterialDataTable = function MaterialDataTable(element) {
3638 this.element_ = element;
3639 // Initialize instance.
3642 window['MaterialDataTable'] = MaterialDataTable;
3644 * Store constants in one place so they can be updated easily.
3646 * @enum {string | number}
3649 MaterialDataTable.prototype.Constant_ = {};
3651 * Store strings for class names defined by this component that are used in
3652 * JavaScript. This allows us to simply change it in one place should we
3653 * decide to modify at a later date.
3658 MaterialDataTable.prototype.CssClasses_ = {
3659 DATA_TABLE: 'mdl-data-table',
3660 SELECTABLE: 'mdl-data-table--selectable',
3661 SELECT_ELEMENT: 'mdl-data-table__select',
3662 IS_SELECTED: 'is-selected',
3663 IS_UPGRADED: 'is-upgraded'
3666 * Generates and returns a function that toggles the selection state of a
3667 * single row (or multiple rows).
3669 * @param {Element} checkbox Checkbox that toggles the selection state.
3670 * @param {Element} row Row to toggle when checkbox changes.
3671 * @param {(Array<Object>|NodeList)=} opt_rows Rows to toggle when checkbox changes.
3674 MaterialDataTable.prototype.selectRow_ = function (checkbox, row, opt_rows) {
3676 return function () {
3677 if (checkbox.checked) {
3678 row.classList.add(this.CssClasses_.IS_SELECTED);
3680 row.classList.remove(this.CssClasses_.IS_SELECTED);
3685 return function () {
3688 if (checkbox.checked) {
3689 for (i = 0; i < opt_rows.length; i++) {
3690 el = opt_rows[i].querySelector('td').querySelector('.mdl-checkbox');
3691 el['MaterialCheckbox'].check();
3692 opt_rows[i].classList.add(this.CssClasses_.IS_SELECTED);
3695 for (i = 0; i < opt_rows.length; i++) {
3696 el = opt_rows[i].querySelector('td').querySelector('.mdl-checkbox');
3697 el['MaterialCheckbox'].uncheck();
3698 opt_rows[i].classList.remove(this.CssClasses_.IS_SELECTED);
3705 * Creates a checkbox for a single or or multiple rows and hooks up the
3708 * @param {Element} row Row to toggle when checkbox changes.
3709 * @param {(Array<Object>|NodeList)=} opt_rows Rows to toggle when checkbox changes.
3712 MaterialDataTable.prototype.createCheckbox_ = function (row, opt_rows) {
3713 var label = document.createElement('label');
3714 var labelClasses = [
3717 'mdl-js-ripple-effect',
3718 this.CssClasses_.SELECT_ELEMENT
3720 label.className = labelClasses.join(' ');
3721 var checkbox = document.createElement('input');
3722 checkbox.type = 'checkbox';
3723 checkbox.classList.add('mdl-checkbox__input');
3725 checkbox.checked = row.classList.contains(this.CssClasses_.IS_SELECTED);
3726 checkbox.addEventListener('change', this.selectRow_(checkbox, row));
3727 } else if (opt_rows) {
3728 checkbox.addEventListener('change', this.selectRow_(checkbox, null, opt_rows));
3730 label.appendChild(checkbox);
3731 componentHandler.upgradeElement(label, 'MaterialCheckbox');
3735 * Initialize element.
3737 MaterialDataTable.prototype.init = function () {
3738 if (this.element_) {
3739 var firstHeader = this.element_.querySelector('th');
3740 var bodyRows = Array.prototype.slice.call(this.element_.querySelectorAll('tbody tr'));
3741 var footRows = Array.prototype.slice.call(this.element_.querySelectorAll('tfoot tr'));
3742 var rows = bodyRows.concat(footRows);
3743 if (this.element_.classList.contains(this.CssClasses_.SELECTABLE)) {
3744 var th = document.createElement('th');
3745 var headerCheckbox = this.createCheckbox_(null, rows);
3746 th.appendChild(headerCheckbox);
3747 firstHeader.parentElement.insertBefore(th, firstHeader);
3748 for (var i = 0; i < rows.length; i++) {
3749 var firstCell = rows[i].querySelector('td');
3751 var td = document.createElement('td');
3752 if (rows[i].parentNode.nodeName.toUpperCase() === 'TBODY') {
3753 var rowCheckbox = this.createCheckbox_(rows[i]);
3754 td.appendChild(rowCheckbox);
3756 rows[i].insertBefore(td, firstCell);
3759 this.element_.classList.add(this.CssClasses_.IS_UPGRADED);
3763 // The component registers itself. It can assume componentHandler is available
3764 // in the global scope.
3765 componentHandler.register({
3766 constructor: MaterialDataTable,
3767 classAsString: 'MaterialDataTable',
3768 cssClass: 'mdl-js-data-table'
3772 * Copyright 2015 Google Inc. All Rights Reserved.
3774 * Licensed under the Apache License, Version 2.0 (the "License");
3775 * you may not use this file except in compliance with the License.
3776 * You may obtain a copy of the License at
3778 * http://www.apache.org/licenses/LICENSE-2.0
3780 * Unless required by applicable law or agreed to in writing, software
3781 * distributed under the License is distributed on an "AS IS" BASIS,
3782 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
3783 * See the License for the specific language governing permissions and
3784 * limitations under the License.
3787 * Class constructor for Ripple MDL component.
3788 * Implements MDL component design pattern defined at:
3789 * https://github.com/jasonmayes/mdl-component-design-pattern
3792 * @param {HTMLElement} element The element that will be upgraded.
3794 var MaterialRipple = function MaterialRipple(element) {
3795 this.element_ = element;
3796 // Initialize instance.
3799 window['MaterialRipple'] = MaterialRipple;
3801 * Store constants in one place so they can be updated easily.
3803 * @enum {string | number}
3806 MaterialRipple.prototype.Constant_ = {
3807 INITIAL_SCALE: 'scale(0.0001, 0.0001)',
3808 INITIAL_SIZE: '1px',
3809 INITIAL_OPACITY: '0.4',
3814 * Store strings for class names defined by this component that are used in
3815 * JavaScript. This allows us to simply change it in one place should we
3816 * decide to modify at a later date.
3821 MaterialRipple.prototype.CssClasses_ = {
3822 RIPPLE_CENTER: 'mdl-ripple--center',
3823 RIPPLE_EFFECT_IGNORE_EVENTS: 'mdl-js-ripple-effect--ignore-events',
3824 RIPPLE: 'mdl-ripple',
3825 IS_ANIMATING: 'is-animating',
3826 IS_VISIBLE: 'is-visible'
3829 * Handle mouse / finger down on element.
3831 * @param {Event} event The event that fired.
3834 MaterialRipple.prototype.downHandler_ = function (event) {
3835 if (!this.rippleElement_.style.width && !this.rippleElement_.style.height) {
3836 var rect = this.element_.getBoundingClientRect();
3837 this.boundHeight = rect.height;
3838 this.boundWidth = rect.width;
3839 this.rippleSize_ = Math.sqrt(rect.width * rect.width + rect.height * rect.height) * 2 + 2;
3840 this.rippleElement_.style.width = this.rippleSize_ + 'px';
3841 this.rippleElement_.style.height = this.rippleSize_ + 'px';
3843 this.rippleElement_.classList.add(this.CssClasses_.IS_VISIBLE);
3844 if (event.type === 'mousedown' && this.ignoringMouseDown_) {
3845 this.ignoringMouseDown_ = false;
3847 if (event.type === 'touchstart') {
3848 this.ignoringMouseDown_ = true;
3850 var frameCount = this.getFrameCount();
3851 if (frameCount > 0) {
3854 this.setFrameCount(1);
3855 var bound = event.currentTarget.getBoundingClientRect();
3858 // Check if we are handling a keyboard click.
3859 if (event.clientX === 0 && event.clientY === 0) {
3860 x = Math.round(bound.width / 2);
3861 y = Math.round(bound.height / 2);
3863 var clientX = event.clientX !== undefined ? event.clientX : event.touches[0].clientX;
3864 var clientY = event.clientY !== undefined ? event.clientY : event.touches[0].clientY;
3865 x = Math.round(clientX - bound.left);
3866 y = Math.round(clientY - bound.top);
3868 this.setRippleXY(x, y);
3869 this.setRippleStyles(true);
3870 window.requestAnimationFrame(this.animFrameHandler.bind(this));
3874 * Handle mouse / finger up on element.
3876 * @param {Event} event The event that fired.
3879 MaterialRipple.prototype.upHandler_ = function (event) {
3880 // Don't fire for the artificial "mouseup" generated by a double-click.
3881 if (event && event.detail !== 2) {
3882 // Allow a repaint to occur before removing this class, so the animation
3883 // shows for tap events, which seem to trigger a mouseup too soon after
3885 window.setTimeout(function () {
3886 this.rippleElement_.classList.remove(this.CssClasses_.IS_VISIBLE);
3891 * Initialize element.
3893 MaterialRipple.prototype.init = function () {
3894 if (this.element_) {
3895 var recentering = this.element_.classList.contains(this.CssClasses_.RIPPLE_CENTER);
3896 if (!this.element_.classList.contains(this.CssClasses_.RIPPLE_EFFECT_IGNORE_EVENTS)) {
3897 this.rippleElement_ = this.element_.querySelector('.' + this.CssClasses_.RIPPLE);
3898 this.frameCount_ = 0;
3899 this.rippleSize_ = 0;
3902 // Touch start produces a compat mouse down event, which would cause a
3903 // second ripples. To avoid that, we use this property to ignore the first
3904 // mouse down after a touch start.
3905 this.ignoringMouseDown_ = false;
3906 this.boundDownHandler = this.downHandler_.bind(this);
3907 this.element_.addEventListener('mousedown', this.boundDownHandler);
3908 this.element_.addEventListener('touchstart', this.boundDownHandler);
3909 this.boundUpHandler = this.upHandler_.bind(this);
3910 this.element_.addEventListener('mouseup', this.boundUpHandler);
3911 this.element_.addEventListener('mouseleave', this.boundUpHandler);
3912 this.element_.addEventListener('touchend', this.boundUpHandler);
3913 this.element_.addEventListener('blur', this.boundUpHandler);
3915 * Getter for frameCount_.
3916 * @return {number} the frame count.
3918 this.getFrameCount = function () {
3919 return this.frameCount_;
3922 * Setter for frameCount_.
3923 * @param {number} fC the frame count.
3925 this.setFrameCount = function (fC) {
3926 this.frameCount_ = fC;
3929 * Getter for rippleElement_.
3930 * @return {Element} the ripple element.
3932 this.getRippleElement = function () {
3933 return this.rippleElement_;
3936 * Sets the ripple X and Y coordinates.
3937 * @param {number} newX the new X coordinate
3938 * @param {number} newY the new Y coordinate
3940 this.setRippleXY = function (newX, newY) {
3945 * Sets the ripple styles.
3946 * @param {boolean} start whether or not this is the start frame.
3948 this.setRippleStyles = function (start) {
3949 if (this.rippleElement_ !== null) {
3950 var transformString;
3953 var offset = 'translate(' + this.x_ + 'px, ' + this.y_ + 'px)';
3955 scale = this.Constant_.INITIAL_SCALE;
3956 size = this.Constant_.INITIAL_SIZE;
3958 scale = this.Constant_.FINAL_SCALE;
3959 size = this.rippleSize_ + 'px';
3961 offset = 'translate(' + this.boundWidth / 2 + 'px, ' + this.boundHeight / 2 + 'px)';
3964 transformString = 'translate(-50%, -50%) ' + offset + scale;
3965 this.rippleElement_.style.webkitTransform = transformString;
3966 this.rippleElement_.style.msTransform = transformString;
3967 this.rippleElement_.style.transform = transformString;
3969 this.rippleElement_.classList.remove(this.CssClasses_.IS_ANIMATING);
3971 this.rippleElement_.classList.add(this.CssClasses_.IS_ANIMATING);
3976 * Handles an animation frame.
3978 this.animFrameHandler = function () {
3979 if (this.frameCount_-- > 0) {
3980 window.requestAnimationFrame(this.animFrameHandler.bind(this));
3982 this.setRippleStyles(false);
3988 // The component registers itself. It can assume componentHandler is available
3989 // in the global scope.
3990 componentHandler.register({
3991 constructor: MaterialRipple,
3992 classAsString: 'MaterialRipple',
3993 cssClass: 'mdl-js-ripple-effect',