blob: 994825a4ba00b1e6f8435d98d4c88ee507067909 [file] [log] [blame]
Copybara botbe50d492023-11-30 00:16:42 +01001;(function() {
2"use strict";
3
4/**
5 * @license
6 * Copyright 2015 Google Inc. All Rights Reserved.
7 *
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
11 *
12 * http://www.apache.org/licenses/LICENSE-2.0
13 *
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.
19 */
20
21/**
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
25 *
26 * @author Jason Mayes.
27 */
28/* exported componentHandler */
29
30// Pre-defining the componentHandler interface, for closure documentation and
31// static verification.
32var componentHandler = {
33 /**
34 * Searches existing DOM for elements of our component type and upgrades them
35 * if they have not already been upgraded.
36 *
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
40 * type will have.
41 */
42 upgradeDom: function(optJsClass, optCssClass) {},
43 /**
44 * Upgrades a specific element rather than all in the DOM.
45 *
46 * @param {!Element} element The element we wish to upgrade.
47 * @param {string=} optJsClass Optional name of the class we want to upgrade
48 * the element to.
49 */
50 upgradeElement: function(element, optJsClass) {},
51 /**
52 * Upgrades a specific list of elements rather than all in the DOM.
53 *
54 * @param {!Element|!Array<!Element>|!NodeList|!HTMLCollection} elements
55 * The elements we wish to upgrade.
56 */
57 upgradeElements: function(elements) {},
58 /**
59 * Upgrades all registered components found in the current DOM. This is
60 * automatically called on window load.
61 */
62 upgradeAllRegistered: function() {},
63 /**
64 * Allows user to be alerted to any upgrades that are performed for a given
65 * component type
66 *
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
71 * got upgraded.
72 */
73 registerUpgradedCallback: function(jsClass, callback) {},
74 /**
75 * Registers a class for future use and attempts to upgrade existing DOM.
76 *
77 * @param {componentHandler.ComponentConfigPublic} config the registration configuration
78 */
79 register: function(config) {},
80 /**
81 * Downgrade either a given node, an array of nodes, or a NodeList.
82 *
83 * @param {!Node|!Array<!Node>|!NodeList} nodes
84 */
85 downgradeElements: function(nodes) {}
86};
87
88componentHandler = (function() {
89 'use strict';
90
91 /** @type {!Array<componentHandler.ComponentConfig>} */
92 var registeredComponents_ = [];
93
94 /** @type {!Array<componentHandler.Component>} */
95 var createdComponents_ = [];
96
97 var componentConfigProperty_ = 'mdlComponentConfigInternal_';
98
99 /**
100 * Searches registered components for a class we are interested in using.
101 * Optionally replaces a match with passed object if specified.
102 *
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}
106 * @private
107 */
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;
113 }
114 return registeredComponents_[i];
115 }
116 }
117 return false;
118 }
119
120 /**
121 * Returns an array of the classNames of the upgraded classes on the element.
122 *
123 * @param {!Element} element The element to fetch data from.
124 * @return {!Array<string>}
125 * @private
126 */
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(',');
131 }
132
133 /**
134 * Returns true if the given element has already been upgraded for the given
135 * class.
136 *
137 * @param {!Element} element The element we want to check.
138 * @param {string} jsClass The class to check for.
139 * @returns {boolean}
140 * @private
141 */
142 function isElementUpgraded_(element, jsClass) {
143 var upgradedList = getUpgradedListOfElement_(element);
144 return upgradedList.indexOf(jsClass) !== -1;
145 }
146
147 /**
148 * Create an event object.
149 *
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.
153 * @returns {!Event}
154 */
155 function createEvent_(eventType, bubbles, cancelable) {
156 if ('CustomEvent' in window && typeof window.CustomEvent === 'function') {
157 return new CustomEvent(eventType, {
158 bubbles: bubbles,
159 cancelable: cancelable
160 });
161 } else {
162 var ev = document.createEvent('Events');
163 ev.initEvent(eventType, bubbles, cancelable);
164 return ev;
165 }
166 }
167
168 /**
169 * Searches existing DOM for elements of our component type and upgrades them
170 * if they have not already been upgraded.
171 *
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
175 * type will have.
176 */
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);
183 }
184 } else {
185 var jsClass = /** @type {string} */ (optJsClass);
186 if (typeof optCssClass === 'undefined') {
187 var registeredClass = findRegisteredClass_(jsClass);
188 if (registeredClass) {
189 optCssClass = registeredClass.cssClass;
190 }
191 }
192
193 var elements = document.querySelectorAll('.' + optCssClass);
194 for (var n = 0; n < elements.length; n++) {
195 upgradeElementInternal(elements[n], jsClass);
196 }
197 }
198 }
199
200 /**
201 * Upgrades a specific element rather than all in the DOM.
202 *
203 * @param {!Element} element The element we wish to upgrade.
204 * @param {string=} optJsClass Optional name of the class we want to upgrade
205 * the element to.
206 */
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.');
211 }
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) {
216 return;
217 }
218
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.
223 if (!optJsClass) {
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);
231 }
232 });
233 } else if (!isElementUpgraded_(element, optJsClass)) {
234 classesToUpgrade.push(findRegisteredClass_(optJsClass));
235 }
236
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);
250 }
251
252 if (registeredClass.widget) {
253 // Assign per element instance for control over API
254 element[registeredClass.className] = instance;
255 }
256 } else {
257 throw new Error(
258 'Unable to find a registered component for the given class.');
259 }
260
261 var upgradedEv = createEvent_('mdl-componentupgraded', true, false);
262 element.dispatchEvent(upgradedEv);
263 }
264 }
265
266 /**
267 * Upgrades a specific list of elements rather than all in the DOM.
268 *
269 * @param {!Element|!Array<!Element>|!NodeList|!HTMLCollection} elements
270 * The elements we wish to upgrade.
271 */
272 function upgradeElementsInternal(elements) {
273 if (!Array.isArray(elements)) {
274 if (elements instanceof Element) {
275 elements = [elements];
276 } else {
277 elements = Array.prototype.slice.call(elements);
278 }
279 }
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);
286 }
287 }
288 }
289 }
290
291 /**
292 * Registers a class for future use and attempts to upgrade existing DOM.
293 *
294 * @param {componentHandler.ComponentConfigPublic} config
295 */
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');
303 var widget = true;
304
305 if (!widgetMissing) {
306 widget = config.widget || config['widget'];
307 }
308
309 var newConfig = /** @type {componentHandler.ComponentConfig} */ ({
310 classConstructor: config.constructor || config['constructor'],
311 className: config.classAsString || config['classAsString'],
312 cssClass: config.cssClass || config['cssClass'],
313 widget: widget,
314 callbacks: []
315 });
316
317 registeredComponents_.forEach(function(item) {
318 if (item.cssClass === newConfig.cssClass) {
319 throw new Error('The provided cssClass has already been registered: ' + item.cssClass);
320 }
321 if (item.className === newConfig.className) {
322 throw new Error('The provided className has already been registered');
323 }
324 });
325
326 if (config.constructor.prototype
327 .hasOwnProperty(componentConfigProperty_)) {
328 throw new Error(
329 'MDL component classes must not have ' + componentConfigProperty_ +
330 ' defined as a property.');
331 }
332
333 var found = findRegisteredClass_(config.classAsString, newConfig);
334
335 if (!found) {
336 registeredComponents_.push(newConfig);
337 }
338 }
339
340 /**
341 * Allows user to be alerted to any upgrades that are performed for a given
342 * component type
343 *
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
348 * got upgraded.
349 */
350 function registerUpgradedCallbackInternal(jsClass, callback) {
351 var regClass = findRegisteredClass_(jsClass);
352 if (regClass) {
353 regClass.callbacks.push(callback);
354 }
355 }
356
357 /**
358 * Upgrades all registered components found in the current DOM. This is
359 * automatically called on window load.
360 */
361 function upgradeAllRegisteredInternal() {
362 for (var n = 0; n < registeredComponents_.length; n++) {
363 upgradeDomInternal(registeredComponents_[n].className);
364 }
365 }
366
367 /**
368 * Check the component for the downgrade method.
369 * Execute if found.
370 * Remove component from createdComponents list.
371 *
372 * @param {?componentHandler.Component} component
373 */
374 function deconstructComponentInternal(component) {
375 if (component) {
376 var componentIndex = createdComponents_.indexOf(component);
377 createdComponents_.splice(componentIndex, 1);
378
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(','));
383
384 var ev = createEvent_('mdl-componentdowngraded', true, false);
385 component.element_.dispatchEvent(ev);
386 }
387 }
388
389 /**
390 * Downgrade either a given node, an array of nodes, or a NodeList.
391 *
392 * @param {!Node|!Array<!Node>|!NodeList} nodes
393 */
394 function downgradeNodesInternal(nodes) {
395 /**
396 * Auxiliary function to downgrade a single node.
397 * @param {!Node} node the node to be downgraded
398 */
399 var downgradeNode = function(node) {
400 createdComponents_.filter(function(item) {
401 return item.element_ === node;
402 }).forEach(deconstructComponentInternal);
403 };
404 if (nodes instanceof Array || nodes instanceof NodeList) {
405 for (var n = 0; n < nodes.length; n++) {
406 downgradeNode(nodes[n]);
407 }
408 } else if (nodes instanceof Node) {
409 downgradeNode(nodes);
410 } else {
411 throw new Error('Invalid argument provided to downgrade MDL nodes.');
412 }
413 }
414
415 // Now return the functions that should be made public with their publicly
416 // facing names...
417 return {
418 upgradeDom: upgradeDomInternal,
419 upgradeElement: upgradeElementInternal,
420 upgradeElements: upgradeElementsInternal,
421 upgradeAllRegistered: upgradeAllRegisteredInternal,
422 registerUpgradedCallback: registerUpgradedCallbackInternal,
423 register: registerInternal,
424 downgradeElements: downgradeNodesInternal
425 };
426})();
427
428/**
429 * Describes the type of a registered component type managed by
430 * componentHandler. Provided for benefit of the Closure compiler.
431 *
432 * @typedef {{
433 * constructor: Function,
434 * classAsString: string,
435 * cssClass: string,
436 * widget: (string|boolean|undefined)
437 * }}
438 */
439componentHandler.ComponentConfigPublic; // jshint ignore:line
440
441/**
442 * Describes the type of a registered component type managed by
443 * componentHandler. Provided for benefit of the Closure compiler.
444 *
445 * @typedef {{
446 * constructor: !Function,
447 * className: string,
448 * cssClass: string,
449 * widget: (string|boolean),
450 * callbacks: !Array<function(!HTMLElement)>
451 * }}
452 */
453componentHandler.ComponentConfig; // jshint ignore:line
454
455/**
456 * Created component (i.e., upgraded element) type as managed by
457 * componentHandler. Provided for benefit of the Closure compiler.
458 *
459 * @typedef {{
460 * element_: !HTMLElement,
461 * className: string,
462 * classAsString: string,
463 * cssClass: string,
464 * widget: string
465 * }}
466 */
467componentHandler.Component; // jshint ignore:line
468
469// Export all symbols, for the benefit of Closure compiler.
470// No effect on uncompiled code.
471componentHandler['upgradeDom'] = componentHandler.upgradeDom;
472componentHandler['upgradeElement'] = componentHandler.upgradeElement;
473componentHandler['upgradeElements'] = componentHandler.upgradeElements;
474componentHandler['upgradeAllRegistered'] =
475 componentHandler.upgradeAllRegistered;
476componentHandler['registerUpgradedCallback'] =
477 componentHandler.registerUpgradedCallback;
478componentHandler['register'] = componentHandler.register;
479componentHandler['downgradeElements'] = componentHandler.downgradeElements;
480window.componentHandler = componentHandler;
481window['componentHandler'] = componentHandler;
482
483window.addEventListener('load', function() {
484 'use strict';
485
486 /**
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.
490 */
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();
496 } else {
497 /**
498 * Dummy function to avoid JS errors.
499 */
500 componentHandler.upgradeElement = function() {};
501 /**
502 * Dummy function to avoid JS errors.
503 */
504 componentHandler.register = function() {};
505 }
506});
507
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
514// MIT license
515if (!Date.now) {
516 /**
517 * Date.now polyfill.
518 * @return {number} the current Date
519 */
520 Date.now = function () {
521 return new Date().getTime();
522 };
523 Date['now'] = Date.now;
524}
525var vendors = [
526 'webkit',
527 'moz'
528];
529for (var i = 0; i < vendors.length && !window.requestAnimationFrame; ++i) {
530 var vp = vendors[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;
535}
536if (/iP(ad|hone|od).*OS 6/.test(window.navigator.userAgent) || !window.requestAnimationFrame || !window.cancelAnimationFrame) {
537 var lastTime = 0;
538 /**
539 * requestAnimationFrame polyfill.
540 * @param {!Function} callback the callback function.
541 */
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);
547 }, nextTime - now);
548 };
549 window.cancelAnimationFrame = clearTimeout;
550 window['requestAnimationFrame'] = window.requestAnimationFrame;
551 window['cancelAnimationFrame'] = window.cancelAnimationFrame;
552}
553/**
554 * @license
555 * Copyright 2015 Google Inc. All Rights Reserved.
556 *
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
560 *
561 * http://www.apache.org/licenses/LICENSE-2.0
562 *
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.
568 */
569/**
570 * Class constructor for Button MDL component.
571 * Implements MDL component design pattern defined at:
572 * https://github.com/jasonmayes/mdl-component-design-pattern
573 *
574 * @param {HTMLElement} element The element that will be upgraded.
575 */
576var MaterialButton = function MaterialButton(element) {
577 this.element_ = element;
578 // Initialize instance.
579 this.init();
580};
581window['MaterialButton'] = MaterialButton;
582/**
583 * Store constants in one place so they can be updated easily.
584 *
585 * @enum {string | number}
586 * @private
587 */
588MaterialButton.prototype.Constant_ = {};
589/**
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.
593 *
594 * @enum {string}
595 * @private
596 */
597MaterialButton.prototype.CssClasses_ = {
598 RIPPLE_EFFECT: 'mdl-js-ripple-effect',
599 RIPPLE_CONTAINER: 'mdl-button__ripple-container',
600 RIPPLE: 'mdl-ripple'
601};
602/**
603 * Handle blur of element.
604 *
605 * @param {Event} event The event that fired.
606 * @private
607 */
608MaterialButton.prototype.blurHandler_ = function (event) {
609 if (event) {
610 this.element_.blur();
611 }
612};
613// Public methods.
614/**
615 * Disable button.
616 *
617 * @public
618 */
619MaterialButton.prototype.disable = function () {
620 this.element_.disabled = true;
621};
622MaterialButton.prototype['disable'] = MaterialButton.prototype.disable;
623/**
624 * Enable button.
625 *
626 * @public
627 */
628MaterialButton.prototype.enable = function () {
629 this.element_.disabled = false;
630};
631MaterialButton.prototype['enable'] = MaterialButton.prototype.enable;
632/**
633 * Initialize element.
634 */
635MaterialButton.prototype.init = function () {
636 if (this.element_) {
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);
646 }
647 this.boundButtonBlurHandler = this.blurHandler_.bind(this);
648 this.element_.addEventListener('mouseup', this.boundButtonBlurHandler);
649 this.element_.addEventListener('mouseleave', this.boundButtonBlurHandler);
650 }
651};
652// The component registers itself. It can assume componentHandler is available
653// in the global scope.
654componentHandler.register({
655 constructor: MaterialButton,
656 classAsString: 'MaterialButton',
657 cssClass: 'mdl-js-button',
658 widget: true
659});
660/**
661 * @license
662 * Copyright 2015 Google Inc. All Rights Reserved.
663 *
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
667 *
668 * http://www.apache.org/licenses/LICENSE-2.0
669 *
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.
675 */
676/**
677 * Class constructor for Checkbox MDL component.
678 * Implements MDL component design pattern defined at:
679 * https://github.com/jasonmayes/mdl-component-design-pattern
680 *
681 * @constructor
682 * @param {HTMLElement} element The element that will be upgraded.
683 */
684var MaterialCheckbox = function MaterialCheckbox(element) {
685 this.element_ = element;
686 // Initialize instance.
687 this.init();
688};
689window['MaterialCheckbox'] = MaterialCheckbox;
690/**
691 * Store constants in one place so they can be updated easily.
692 *
693 * @enum {string | number}
694 * @private
695 */
696MaterialCheckbox.prototype.Constant_ = { TINY_TIMEOUT: 0.001 };
697/**
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.
701 *
702 * @enum {string}
703 * @private
704 */
705MaterialCheckbox.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'
719};
720/**
721 * Handle change of state.
722 *
723 * @param {Event} event The event that fired.
724 * @private
725 */
726MaterialCheckbox.prototype.onChange_ = function (event) {
727 this.updateClasses_();
728};
729/**
730 * Handle focus of element.
731 *
732 * @param {Event} event The event that fired.
733 * @private
734 */
735MaterialCheckbox.prototype.onFocus_ = function (event) {
736 this.element_.classList.add(this.CssClasses_.IS_FOCUSED);
737};
738/**
739 * Handle lost focus of element.
740 *
741 * @param {Event} event The event that fired.
742 * @private
743 */
744MaterialCheckbox.prototype.onBlur_ = function (event) {
745 this.element_.classList.remove(this.CssClasses_.IS_FOCUSED);
746};
747/**
748 * Handle mouseup.
749 *
750 * @param {Event} event The event that fired.
751 * @private
752 */
753MaterialCheckbox.prototype.onMouseUp_ = function (event) {
754 this.blur_();
755};
756/**
757 * Handle class updates.
758 *
759 * @private
760 */
761MaterialCheckbox.prototype.updateClasses_ = function () {
762 this.checkDisabled();
763 this.checkToggleState();
764};
765/**
766 * Add blur.
767 *
768 * @private
769 */
770MaterialCheckbox.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);
776};
777// Public methods.
778/**
779 * Check the inputs toggle state and update display.
780 *
781 * @public
782 */
783MaterialCheckbox.prototype.checkToggleState = function () {
784 if (this.inputElement_.checked) {
785 this.element_.classList.add(this.CssClasses_.IS_CHECKED);
786 } else {
787 this.element_.classList.remove(this.CssClasses_.IS_CHECKED);
788 }
789};
790MaterialCheckbox.prototype['checkToggleState'] = MaterialCheckbox.prototype.checkToggleState;
791/**
792 * Check the inputs disabled state and update display.
793 *
794 * @public
795 */
796MaterialCheckbox.prototype.checkDisabled = function () {
797 if (this.inputElement_.disabled) {
798 this.element_.classList.add(this.CssClasses_.IS_DISABLED);
799 } else {
800 this.element_.classList.remove(this.CssClasses_.IS_DISABLED);
801 }
802};
803MaterialCheckbox.prototype['checkDisabled'] = MaterialCheckbox.prototype.checkDisabled;
804/**
805 * Disable checkbox.
806 *
807 * @public
808 */
809MaterialCheckbox.prototype.disable = function () {
810 this.inputElement_.disabled = true;
811 this.updateClasses_();
812};
813MaterialCheckbox.prototype['disable'] = MaterialCheckbox.prototype.disable;
814/**
815 * Enable checkbox.
816 *
817 * @public
818 */
819MaterialCheckbox.prototype.enable = function () {
820 this.inputElement_.disabled = false;
821 this.updateClasses_();
822};
823MaterialCheckbox.prototype['enable'] = MaterialCheckbox.prototype.enable;
824/**
825 * Check checkbox.
826 *
827 * @public
828 */
829MaterialCheckbox.prototype.check = function () {
830 this.inputElement_.checked = true;
831 this.updateClasses_();
832};
833MaterialCheckbox.prototype['check'] = MaterialCheckbox.prototype.check;
834/**
835 * Uncheck checkbox.
836 *
837 * @public
838 */
839MaterialCheckbox.prototype.uncheck = function () {
840 this.inputElement_.checked = false;
841 this.updateClasses_();
842};
843MaterialCheckbox.prototype['uncheck'] = MaterialCheckbox.prototype.uncheck;
844/**
845 * Initialize element.
846 */
847MaterialCheckbox.prototype.init = function () {
848 if (this.element_) {
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_);
871 }
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);
882 }
883};
884// The component registers itself. It can assume componentHandler is available
885// in the global scope.
886componentHandler.register({
887 constructor: MaterialCheckbox,
888 classAsString: 'MaterialCheckbox',
889 cssClass: 'mdl-js-checkbox',
890 widget: true
891});
892/**
893 * @license
894 * Copyright 2015 Google Inc. All Rights Reserved.
895 *
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
899 *
900 * http://www.apache.org/licenses/LICENSE-2.0
901 *
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.
907 */
908/**
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
912 *
913 * @constructor
914 * @param {HTMLElement} element The element that will be upgraded.
915 */
916var MaterialIconToggle = function MaterialIconToggle(element) {
917 this.element_ = element;
918 // Initialize instance.
919 this.init();
920};
921window['MaterialIconToggle'] = MaterialIconToggle;
922/**
923 * Store constants in one place so they can be updated easily.
924 *
925 * @enum {string | number}
926 * @private
927 */
928MaterialIconToggle.prototype.Constant_ = { TINY_TIMEOUT: 0.001 };
929/**
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.
933 *
934 * @enum {string}
935 * @private
936 */
937MaterialIconToggle.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'
947};
948/**
949 * Handle change of state.
950 *
951 * @param {Event} event The event that fired.
952 * @private
953 */
954MaterialIconToggle.prototype.onChange_ = function (event) {
955 this.updateClasses_();
956};
957/**
958 * Handle focus of element.
959 *
960 * @param {Event} event The event that fired.
961 * @private
962 */
963MaterialIconToggle.prototype.onFocus_ = function (event) {
964 this.element_.classList.add(this.CssClasses_.IS_FOCUSED);
965};
966/**
967 * Handle lost focus of element.
968 *
969 * @param {Event} event The event that fired.
970 * @private
971 */
972MaterialIconToggle.prototype.onBlur_ = function (event) {
973 this.element_.classList.remove(this.CssClasses_.IS_FOCUSED);
974};
975/**
976 * Handle mouseup.
977 *
978 * @param {Event} event The event that fired.
979 * @private
980 */
981MaterialIconToggle.prototype.onMouseUp_ = function (event) {
982 this.blur_();
983};
984/**
985 * Handle class updates.
986 *
987 * @private
988 */
989MaterialIconToggle.prototype.updateClasses_ = function () {
990 this.checkDisabled();
991 this.checkToggleState();
992};
993/**
994 * Add blur.
995 *
996 * @private
997 */
998MaterialIconToggle.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);
1004};
1005// Public methods.
1006/**
1007 * Check the inputs toggle state and update display.
1008 *
1009 * @public
1010 */
1011MaterialIconToggle.prototype.checkToggleState = function () {
1012 if (this.inputElement_.checked) {
1013 this.element_.classList.add(this.CssClasses_.IS_CHECKED);
1014 } else {
1015 this.element_.classList.remove(this.CssClasses_.IS_CHECKED);
1016 }
1017};
1018MaterialIconToggle.prototype['checkToggleState'] = MaterialIconToggle.prototype.checkToggleState;
1019/**
1020 * Check the inputs disabled state and update display.
1021 *
1022 * @public
1023 */
1024MaterialIconToggle.prototype.checkDisabled = function () {
1025 if (this.inputElement_.disabled) {
1026 this.element_.classList.add(this.CssClasses_.IS_DISABLED);
1027 } else {
1028 this.element_.classList.remove(this.CssClasses_.IS_DISABLED);
1029 }
1030};
1031MaterialIconToggle.prototype['checkDisabled'] = MaterialIconToggle.prototype.checkDisabled;
1032/**
1033 * Disable icon toggle.
1034 *
1035 * @public
1036 */
1037MaterialIconToggle.prototype.disable = function () {
1038 this.inputElement_.disabled = true;
1039 this.updateClasses_();
1040};
1041MaterialIconToggle.prototype['disable'] = MaterialIconToggle.prototype.disable;
1042/**
1043 * Enable icon toggle.
1044 *
1045 * @public
1046 */
1047MaterialIconToggle.prototype.enable = function () {
1048 this.inputElement_.disabled = false;
1049 this.updateClasses_();
1050};
1051MaterialIconToggle.prototype['enable'] = MaterialIconToggle.prototype.enable;
1052/**
1053 * Check icon toggle.
1054 *
1055 * @public
1056 */
1057MaterialIconToggle.prototype.check = function () {
1058 this.inputElement_.checked = true;
1059 this.updateClasses_();
1060};
1061MaterialIconToggle.prototype['check'] = MaterialIconToggle.prototype.check;
1062/**
1063 * Uncheck icon toggle.
1064 *
1065 * @public
1066 */
1067MaterialIconToggle.prototype.uncheck = function () {
1068 this.inputElement_.checked = false;
1069 this.updateClasses_();
1070};
1071MaterialIconToggle.prototype['uncheck'] = MaterialIconToggle.prototype.uncheck;
1072/**
1073 * Initialize element.
1074 */
1075MaterialIconToggle.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_);
1090 }
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');
1101 }
1102};
1103// The component registers itself. It can assume componentHandler is available
1104// in the global scope.
1105componentHandler.register({
1106 constructor: MaterialIconToggle,
1107 classAsString: 'MaterialIconToggle',
1108 cssClass: 'mdl-js-icon-toggle',
1109 widget: true
1110});
1111/**
1112 * @license
1113 * Copyright 2015 Google Inc. All Rights Reserved.
1114 *
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
1118 *
1119 * http://www.apache.org/licenses/LICENSE-2.0
1120 *
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.
1126 */
1127/**
1128 * Class constructor for dropdown MDL component.
1129 * Implements MDL component design pattern defined at:
1130 * https://github.com/jasonmayes/mdl-component-design-pattern
1131 *
1132 * @constructor
1133 * @param {HTMLElement} element The element that will be upgraded.
1134 */
1135var MaterialMenu = function MaterialMenu(element) {
1136 this.element_ = element;
1137 // Initialize instance.
1138 this.init();
1139};
1140window['MaterialMenu'] = MaterialMenu;
1141/**
1142 * Store constants in one place so they can be updated easily.
1143 *
1144 * @enum {string | number}
1145 * @private
1146 */
1147MaterialMenu.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
1153 // the ripple).
1154 CLOSE_TIMEOUT: 150
1155};
1156/**
1157 * Keycodes, for code readability.
1158 *
1159 * @enum {number}
1160 * @private
1161 */
1162MaterialMenu.prototype.Keycodes_ = {
1163 ENTER: 13,
1164 ESCAPE: 27,
1165 SPACE: 32,
1166 UP_ARROW: 38,
1167 DOWN_ARROW: 40
1168};
1169/**
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.
1173 *
1174 * @enum {string}
1175 * @private
1176 */
1177MaterialMenu.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',
1185 // Statuses
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'
1196};
1197/**
1198 * Initialize element.
1199 */
1200MaterialMenu.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');
1216 var forEl = null;
1217 if (forElId) {
1218 forEl = document.getElementById(forElId);
1219 if (forEl) {
1220 this.forElement_ = forEl;
1221 forEl.addEventListener('click', this.handleForClick_.bind(this));
1222 forEl.addEventListener('keydown', this.handleForKeyboardEvent_.bind(this));
1223 }
1224 }
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_);
1235 }
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);
1248 }
1249 }
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);
1253 }
1254 if (this.element_.classList.contains(this.CssClasses_.BOTTOM_RIGHT)) {
1255 this.outline_.classList.add(this.CssClasses_.BOTTOM_RIGHT);
1256 }
1257 if (this.element_.classList.contains(this.CssClasses_.TOP_LEFT)) {
1258 this.outline_.classList.add(this.CssClasses_.TOP_LEFT);
1259 }
1260 if (this.element_.classList.contains(this.CssClasses_.TOP_RIGHT)) {
1261 this.outline_.classList.add(this.CssClasses_.TOP_RIGHT);
1262 }
1263 if (this.element_.classList.contains(this.CssClasses_.UNALIGNED)) {
1264 this.outline_.classList.add(this.CssClasses_.UNALIGNED);
1265 }
1266 container.classList.add(this.CssClasses_.IS_UPGRADED);
1267 }
1268};
1269/**
1270 * Handles a click on the "for" element, by positioning the menu and then
1271 * toggling it.
1272 *
1273 * @param {Event} evt The event that fired.
1274 * @private
1275 */
1276MaterialMenu.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';
1293 } else {
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';
1297 }
1298 }
1299 this.toggle(evt);
1300};
1301/**
1302 * Handles a keyboard event on the "for" element.
1303 *
1304 * @param {Event} evt The event that fired.
1305 * @private
1306 */
1307MaterialMenu.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();
1316 items[0].focus();
1317 }
1318 }
1319 }
1320};
1321/**
1322 * Handles a keyboard event on an item.
1323 *
1324 * @param {Event} evt The event that fired.
1325 * @private
1326 */
1327MaterialMenu.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();
1336 } else {
1337 items[items.length - 1].focus();
1338 }
1339 } else if (evt.keyCode === this.Keycodes_.DOWN_ARROW) {
1340 evt.preventDefault();
1341 if (items.length > currentIndex + 1) {
1342 items[currentIndex + 1].focus();
1343 } else {
1344 items[0].focus();
1345 }
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);
1353 // Send click.
1354 evt.target.click();
1355 } else if (evt.keyCode === this.Keycodes_.ESCAPE) {
1356 evt.preventDefault();
1357 this.hide();
1358 }
1359 }
1360 }
1361};
1362/**
1363 * Handles a click event on an item.
1364 *
1365 * @param {Event} evt The event that fired.
1366 * @private
1367 */
1368MaterialMenu.prototype.handleItemClick_ = function (evt) {
1369 if (evt.target.hasAttribute('disabled')) {
1370 evt.stopPropagation();
1371 } else {
1372 // Wait some time before closing menu, so the user can see the ripple.
1373 this.closing_ = true;
1374 window.setTimeout(function (evt) {
1375 this.hide();
1376 this.closing_ = false;
1377 }.bind(this), this.Constant_.CLOSE_TIMEOUT);
1378 }
1379};
1380/**
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.
1384 *
1385 * @param {number} height Height of the clip rectangle
1386 * @param {number} width Width of the clip rectangle
1387 * @private
1388 */
1389MaterialMenu.prototype.applyClip_ = function (height, width) {
1390 if (this.element_.classList.contains(this.CssClasses_.UNALIGNED)) {
1391 // Do not clip.
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)';
1402 } else {
1403 // Default: do not clip (same as clipping to the top left corner).
1404 this.element_.style.clip = '';
1405 }
1406};
1407/**
1408 * Cleanup function to remove animation listeners.
1409 *
1410 * @param {Event} evt
1411 * @private
1412 */
1413MaterialMenu.prototype.removeAnimationEndListener_ = function (evt) {
1414 evt.target.classList.remove(MaterialMenu.prototype.CssClasses_.IS_ANIMATING);
1415};
1416/**
1417 * Adds an event listener to clean up after the animation ends.
1418 *
1419 * @private
1420 */
1421MaterialMenu.prototype.addAnimationEndListener_ = function () {
1422 this.element_.addEventListener('transitionend', this.removeAnimationEndListener_);
1423 this.element_.addEventListener('webkitTransitionEnd', this.removeAnimationEndListener_);
1424};
1425/**
1426 * Displays the menu.
1427 *
1428 * @public
1429 */
1430MaterialMenu.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';
1448 } else {
1449 itemDelay = items[i].offsetTop / height * transitionDuration + 's';
1450 }
1451 items[i].style.transitionDelay = itemDelay;
1452 }
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);
1461 }.bind(this));
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);
1474 this.hide();
1475 }
1476 }.bind(this);
1477 document.addEventListener('click', callback);
1478 }
1479};
1480MaterialMenu.prototype['show'] = MaterialMenu.prototype.show;
1481/**
1482 * Hides the menu.
1483 *
1484 * @public
1485 */
1486MaterialMenu.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');
1492 }
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_();
1504 }
1505};
1506MaterialMenu.prototype['hide'] = MaterialMenu.prototype.hide;
1507/**
1508 * Displays or hides the menu, depending on current state.
1509 *
1510 * @public
1511 */
1512MaterialMenu.prototype.toggle = function (evt) {
1513 if (this.container_.classList.contains(this.CssClasses_.IS_VISIBLE)) {
1514 this.hide();
1515 } else {
1516 this.show(evt);
1517 }
1518};
1519MaterialMenu.prototype['toggle'] = MaterialMenu.prototype.toggle;
1520// The component registers itself. It can assume componentHandler is available
1521// in the global scope.
1522componentHandler.register({
1523 constructor: MaterialMenu,
1524 classAsString: 'MaterialMenu',
1525 cssClass: 'mdl-js-menu',
1526 widget: true
1527});
1528/**
1529 * @license
1530 * Copyright 2015 Google Inc. All Rights Reserved.
1531 *
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
1535 *
1536 * http://www.apache.org/licenses/LICENSE-2.0
1537 *
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.
1543 */
1544/**
1545 * Class constructor for Progress MDL component.
1546 * Implements MDL component design pattern defined at:
1547 * https://github.com/jasonmayes/mdl-component-design-pattern
1548 *
1549 * @constructor
1550 * @param {HTMLElement} element The element that will be upgraded.
1551 */
1552var MaterialProgress = function MaterialProgress(element) {
1553 this.element_ = element;
1554 // Initialize instance.
1555 this.init();
1556};
1557window['MaterialProgress'] = MaterialProgress;
1558/**
1559 * Store constants in one place so they can be updated easily.
1560 *
1561 * @enum {string | number}
1562 * @private
1563 */
1564MaterialProgress.prototype.Constant_ = {};
1565/**
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.
1569 *
1570 * @enum {string}
1571 * @private
1572 */
1573MaterialProgress.prototype.CssClasses_ = { INDETERMINATE_CLASS: 'mdl-progress__indeterminate' };
1574/**
1575 * Set the current progress of the progressbar.
1576 *
1577 * @param {number} p Percentage of the progress (0-100)
1578 * @public
1579 */
1580MaterialProgress.prototype.setProgress = function (p) {
1581 if (this.element_.classList.contains(this.CssClasses_.INDETERMINATE_CLASS)) {
1582 return;
1583 }
1584 this.progressbar_.style.width = p + '%';
1585};
1586MaterialProgress.prototype['setProgress'] = MaterialProgress.prototype.setProgress;
1587/**
1588 * Set the current progress of the buffer.
1589 *
1590 * @param {number} p Percentage of the buffer (0-100)
1591 * @public
1592 */
1593MaterialProgress.prototype.setBuffer = function (p) {
1594 this.bufferbar_.style.width = p + '%';
1595 this.auxbar_.style.width = 100 - p + '%';
1596};
1597MaterialProgress.prototype['setBuffer'] = MaterialProgress.prototype.setBuffer;
1598/**
1599 * Initialize element.
1600 */
1601MaterialProgress.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);
1614 this.auxbar_ = 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');
1619 }
1620};
1621// The component registers itself. It can assume componentHandler is available
1622// in the global scope.
1623componentHandler.register({
1624 constructor: MaterialProgress,
1625 classAsString: 'MaterialProgress',
1626 cssClass: 'mdl-js-progress',
1627 widget: true
1628});
1629/**
1630 * @license
1631 * Copyright 2015 Google Inc. All Rights Reserved.
1632 *
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
1636 *
1637 * http://www.apache.org/licenses/LICENSE-2.0
1638 *
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.
1644 */
1645/**
1646 * Class constructor for Radio MDL component.
1647 * Implements MDL component design pattern defined at:
1648 * https://github.com/jasonmayes/mdl-component-design-pattern
1649 *
1650 * @constructor
1651 * @param {HTMLElement} element The element that will be upgraded.
1652 */
1653var MaterialRadio = function MaterialRadio(element) {
1654 this.element_ = element;
1655 // Initialize instance.
1656 this.init();
1657};
1658window['MaterialRadio'] = MaterialRadio;
1659/**
1660 * Store constants in one place so they can be updated easily.
1661 *
1662 * @enum {string | number}
1663 * @private
1664 */
1665MaterialRadio.prototype.Constant_ = { TINY_TIMEOUT: 0.001 };
1666/**
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.
1670 *
1671 * @enum {string}
1672 * @private
1673 */
1674MaterialRadio.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'
1688};
1689/**
1690 * Handle change of state.
1691 *
1692 * @param {Event} event The event that fired.
1693 * @private
1694 */
1695MaterialRadio.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_();
1705 }
1706 }
1707 }
1708};
1709/**
1710 * Handle focus.
1711 *
1712 * @param {Event} event The event that fired.
1713 * @private
1714 */
1715MaterialRadio.prototype.onFocus_ = function (event) {
1716 this.element_.classList.add(this.CssClasses_.IS_FOCUSED);
1717};
1718/**
1719 * Handle lost focus.
1720 *
1721 * @param {Event} event The event that fired.
1722 * @private
1723 */
1724MaterialRadio.prototype.onBlur_ = function (event) {
1725 this.element_.classList.remove(this.CssClasses_.IS_FOCUSED);
1726};
1727/**
1728 * Handle mouseup.
1729 *
1730 * @param {Event} event The event that fired.
1731 * @private
1732 */
1733MaterialRadio.prototype.onMouseup_ = function (event) {
1734 this.blur_();
1735};
1736/**
1737 * Update classes.
1738 *
1739 * @private
1740 */
1741MaterialRadio.prototype.updateClasses_ = function () {
1742 this.checkDisabled();
1743 this.checkToggleState();
1744};
1745/**
1746 * Add blur.
1747 *
1748 * @private
1749 */
1750MaterialRadio.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);
1756};
1757// Public methods.
1758/**
1759 * Check the components disabled state.
1760 *
1761 * @public
1762 */
1763MaterialRadio.prototype.checkDisabled = function () {
1764 if (this.btnElement_.disabled) {
1765 this.element_.classList.add(this.CssClasses_.IS_DISABLED);
1766 } else {
1767 this.element_.classList.remove(this.CssClasses_.IS_DISABLED);
1768 }
1769};
1770MaterialRadio.prototype['checkDisabled'] = MaterialRadio.prototype.checkDisabled;
1771/**
1772 * Check the components toggled state.
1773 *
1774 * @public
1775 */
1776MaterialRadio.prototype.checkToggleState = function () {
1777 if (this.btnElement_.checked) {
1778 this.element_.classList.add(this.CssClasses_.IS_CHECKED);
1779 } else {
1780 this.element_.classList.remove(this.CssClasses_.IS_CHECKED);
1781 }
1782};
1783MaterialRadio.prototype['checkToggleState'] = MaterialRadio.prototype.checkToggleState;
1784/**
1785 * Disable radio.
1786 *
1787 * @public
1788 */
1789MaterialRadio.prototype.disable = function () {
1790 this.btnElement_.disabled = true;
1791 this.updateClasses_();
1792};
1793MaterialRadio.prototype['disable'] = MaterialRadio.prototype.disable;
1794/**
1795 * Enable radio.
1796 *
1797 * @public
1798 */
1799MaterialRadio.prototype.enable = function () {
1800 this.btnElement_.disabled = false;
1801 this.updateClasses_();
1802};
1803MaterialRadio.prototype['enable'] = MaterialRadio.prototype.enable;
1804/**
1805 * Check radio.
1806 *
1807 * @public
1808 */
1809MaterialRadio.prototype.check = function () {
1810 this.btnElement_.checked = true;
1811 this.onChange_(null);
1812};
1813MaterialRadio.prototype['check'] = MaterialRadio.prototype.check;
1814/**
1815 * Uncheck radio.
1816 *
1817 * @public
1818 */
1819MaterialRadio.prototype.uncheck = function () {
1820 this.btnElement_.checked = false;
1821 this.onChange_(null);
1822};
1823MaterialRadio.prototype['uncheck'] = MaterialRadio.prototype.uncheck;
1824/**
1825 * Initialize element.
1826 */
1827MaterialRadio.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);
1852 }
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);
1859 }
1860};
1861// The component registers itself. It can assume componentHandler is available
1862// in the global scope.
1863componentHandler.register({
1864 constructor: MaterialRadio,
1865 classAsString: 'MaterialRadio',
1866 cssClass: 'mdl-js-radio',
1867 widget: true
1868});
1869/**
1870 * @license
1871 * Copyright 2015 Google Inc. All Rights Reserved.
1872 *
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
1876 *
1877 * http://www.apache.org/licenses/LICENSE-2.0
1878 *
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.
1884 */
1885/**
1886 * Class constructor for Slider MDL component.
1887 * Implements MDL component design pattern defined at:
1888 * https://github.com/jasonmayes/mdl-component-design-pattern
1889 *
1890 * @constructor
1891 * @param {HTMLElement} element The element that will be upgraded.
1892 */
1893var MaterialSlider = function MaterialSlider(element) {
1894 this.element_ = element;
1895 // Browser feature detection.
1896 this.isIE_ = window.navigator.msPointerEnabled;
1897 // Initialize instance.
1898 this.init();
1899};
1900window['MaterialSlider'] = MaterialSlider;
1901/**
1902 * Store constants in one place so they can be updated easily.
1903 *
1904 * @enum {string | number}
1905 * @private
1906 */
1907MaterialSlider.prototype.Constant_ = {};
1908/**
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.
1912 *
1913 * @enum {string}
1914 * @private
1915 */
1916MaterialSlider.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'
1924};
1925/**
1926 * Handle input on element.
1927 *
1928 * @param {Event} event The event that fired.
1929 * @private
1930 */
1931MaterialSlider.prototype.onInput_ = function (event) {
1932 this.updateValueStyles_();
1933};
1934/**
1935 * Handle change on element.
1936 *
1937 * @param {Event} event The event that fired.
1938 * @private
1939 */
1940MaterialSlider.prototype.onChange_ = function (event) {
1941 this.updateValueStyles_();
1942};
1943/**
1944 * Handle mouseup on element.
1945 *
1946 * @param {Event} event The event that fired.
1947 * @private
1948 */
1949MaterialSlider.prototype.onMouseUp_ = function (event) {
1950 event.target.blur();
1951};
1952/**
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.
1957 *
1958 * @param {Event} event The event that fired.
1959 * @private
1960 * @suppress {missingProperties}
1961 */
1962MaterialSlider.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) {
1966 return;
1967 }
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
1976 });
1977 this.element_.dispatchEvent(newEvent);
1978};
1979/**
1980 * Handle updating of values.
1981 *
1982 * @private
1983 */
1984MaterialSlider.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);
1989 } else {
1990 this.element_.classList.remove(this.CssClasses_.IS_LOWEST_VALUE);
1991 }
1992 if (!this.isIE_) {
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;
1997 }
1998};
1999// Public methods.
2000/**
2001 * Disable slider.
2002 *
2003 * @public
2004 */
2005MaterialSlider.prototype.disable = function () {
2006 this.element_.disabled = true;
2007};
2008MaterialSlider.prototype['disable'] = MaterialSlider.prototype.disable;
2009/**
2010 * Enable slider.
2011 *
2012 * @public
2013 */
2014MaterialSlider.prototype.enable = function () {
2015 this.element_.disabled = false;
2016};
2017MaterialSlider.prototype['enable'] = MaterialSlider.prototype.enable;
2018/**
2019 * Update slider value.
2020 *
2021 * @param {number} value The value to which to set the control (optional).
2022 * @public
2023 */
2024MaterialSlider.prototype.change = function (value) {
2025 if (typeof value !== 'undefined') {
2026 this.element_.value = value;
2027 }
2028 this.updateValueStyles_();
2029};
2030MaterialSlider.prototype['change'] = MaterialSlider.prototype.change;
2031/**
2032 * Initialize element.
2033 */
2034MaterialSlider.prototype.init = function () {
2035 if (this.element_) {
2036 if (this.isIE_) {
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_);
2045 } else {
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_);
2063 }
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);
2074 }
2075};
2076// The component registers itself. It can assume componentHandler is available
2077// in the global scope.
2078componentHandler.register({
2079 constructor: MaterialSlider,
2080 classAsString: 'MaterialSlider',
2081 cssClass: 'mdl-js-slider',
2082 widget: true
2083});
2084/**
2085 * Copyright 2015 Google Inc. All Rights Reserved.
2086 *
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
2090 *
2091 * http://www.apache.org/licenses/LICENSE-2.0
2092 *
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.
2098 */
2099/**
2100 * Class constructor for Snackbar MDL component.
2101 * Implements MDL component design pattern defined at:
2102 * https://github.com/jasonmayes/mdl-component-design-pattern
2103 *
2104 * @constructor
2105 * @param {HTMLElement} element The element that will be upgraded.
2106 */
2107var 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.');
2113 }
2114 if (!this.actionElement_) {
2115 throw new Error('There must be an action element for a snackbar.');
2116 }
2117 this.active = false;
2118 this.actionHandler_ = undefined;
2119 this.message_ = undefined;
2120 this.actionText_ = undefined;
2121 this.queuedNotifications_ = [];
2122 this.setActionHidden_(true);
2123};
2124window['MaterialSnackbar'] = MaterialSnackbar;
2125/**
2126 * Store constants in one place so they can be updated easily.
2127 *
2128 * @enum {string | number}
2129 * @private
2130 */
2131MaterialSnackbar.prototype.Constant_ = {
2132 // The duration of the snackbar show/hide animation, in ms.
2133 ANIMATION_LENGTH: 250
2134};
2135/**
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.
2139 *
2140 * @enum {string}
2141 * @private
2142 */
2143MaterialSnackbar.prototype.cssClasses_ = {
2144 SNACKBAR: 'mdl-snackbar',
2145 MESSAGE: 'mdl-snackbar__text',
2146 ACTION: 'mdl-snackbar__action',
2147 ACTIVE: 'mdl-snackbar--active'
2148};
2149/**
2150 * Display the snackbar.
2151 *
2152 * @private
2153 */
2154MaterialSnackbar.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);
2160 }
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_);
2165};
2166/**
2167 * Show the snackbar.
2168 *
2169 * @param {Object} data The data for the notification.
2170 * @public
2171 */
2172MaterialSnackbar.prototype.showSnackbar = function (data) {
2173 if (data === undefined) {
2174 throw new Error('Please provide a data object with at least a message to display.');
2175 }
2176 if (data['message'] === undefined) {
2177 throw new Error('Please provide a message to be displayed.');
2178 }
2179 if (data['actionHandler'] && !data['actionText']) {
2180 throw new Error('Please provide action text with the handler.');
2181 }
2182 if (this.active) {
2183 this.queuedNotifications_.push(data);
2184 } else {
2185 this.active = true;
2186 this.message_ = data['message'];
2187 if (data['timeout']) {
2188 this.timeout_ = data['timeout'];
2189 } else {
2190 this.timeout_ = 2750;
2191 }
2192 if (data['actionHandler']) {
2193 this.actionHandler_ = data['actionHandler'];
2194 }
2195 if (data['actionText']) {
2196 this.actionText_ = data['actionText'];
2197 }
2198 this.displaySnackbar_();
2199 }
2200};
2201MaterialSnackbar.prototype['showSnackbar'] = MaterialSnackbar.prototype.showSnackbar;
2202/**
2203 * Check if the queue has items within it.
2204 * If it does, display the next entry.
2205 *
2206 * @private
2207 */
2208MaterialSnackbar.prototype.checkQueue_ = function () {
2209 if (this.queuedNotifications_.length > 0) {
2210 this.showSnackbar(this.queuedNotifications_.shift());
2211 }
2212};
2213/**
2214 * Cleanup the snackbar event listeners and accessiblity attributes.
2215 *
2216 * @private
2217 */
2218MaterialSnackbar.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_);
2227 }
2228 this.actionHandler_ = undefined;
2229 this.message_ = undefined;
2230 this.actionText_ = undefined;
2231 this.active = false;
2232 this.checkQueue_();
2233 }.bind(this), this.Constant_.ANIMATION_LENGTH);
2234};
2235/**
2236 * Set the action handler hidden state.
2237 *
2238 * @param {boolean} value
2239 * @private
2240 */
2241MaterialSnackbar.prototype.setActionHidden_ = function (value) {
2242 if (value) {
2243 this.actionElement_.setAttribute('aria-hidden', 'true');
2244 } else {
2245 this.actionElement_.removeAttribute('aria-hidden');
2246 }
2247};
2248// The component registers itself. It can assume componentHandler is available
2249// in the global scope.
2250componentHandler.register({
2251 constructor: MaterialSnackbar,
2252 classAsString: 'MaterialSnackbar',
2253 cssClass: 'mdl-js-snackbar',
2254 widget: true
2255});
2256/**
2257 * @license
2258 * Copyright 2015 Google Inc. All Rights Reserved.
2259 *
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
2263 *
2264 * http://www.apache.org/licenses/LICENSE-2.0
2265 *
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.
2271 */
2272/**
2273 * Class constructor for Spinner MDL component.
2274 * Implements MDL component design pattern defined at:
2275 * https://github.com/jasonmayes/mdl-component-design-pattern
2276 *
2277 * @param {HTMLElement} element The element that will be upgraded.
2278 * @constructor
2279 */
2280var MaterialSpinner = function MaterialSpinner(element) {
2281 this.element_ = element;
2282 // Initialize instance.
2283 this.init();
2284};
2285window['MaterialSpinner'] = MaterialSpinner;
2286/**
2287 * Store constants in one place so they can be updated easily.
2288 *
2289 * @enum {string | number}
2290 * @private
2291 */
2292MaterialSpinner.prototype.Constant_ = { MDL_SPINNER_LAYER_COUNT: 4 };
2293/**
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.
2297 *
2298 * @enum {string}
2299 * @private
2300 */
2301MaterialSpinner.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'
2308};
2309/**
2310 * Auxiliary method to create a spinner layer.
2311 *
2312 * @param {number} index Index of the layer to be created.
2313 * @public
2314 */
2315MaterialSpinner.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 = [
2328 leftClipper,
2329 gapPatch,
2330 rightClipper
2331 ];
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);
2336 }
2337 layer.appendChild(leftClipper);
2338 layer.appendChild(gapPatch);
2339 layer.appendChild(rightClipper);
2340 this.element_.appendChild(layer);
2341};
2342MaterialSpinner.prototype['createLayer'] = MaterialSpinner.prototype.createLayer;
2343/**
2344 * Stops the spinner animation.
2345 * Public method for users who need to stop the spinner for any reason.
2346 *
2347 * @public
2348 */
2349MaterialSpinner.prototype.stop = function () {
2350 this.element_.classList.remove('is-active');
2351};
2352MaterialSpinner.prototype['stop'] = MaterialSpinner.prototype.stop;
2353/**
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).
2357 *
2358 * @public
2359 */
2360MaterialSpinner.prototype.start = function () {
2361 this.element_.classList.add('is-active');
2362};
2363MaterialSpinner.prototype['start'] = MaterialSpinner.prototype.start;
2364/**
2365 * Initialize element.
2366 */
2367MaterialSpinner.prototype.init = function () {
2368 if (this.element_) {
2369 for (var i = 1; i <= this.Constant_.MDL_SPINNER_LAYER_COUNT; i++) {
2370 this.createLayer(i);
2371 }
2372 this.element_.classList.add('is-upgraded');
2373 }
2374};
2375// The component registers itself. It can assume componentHandler is available
2376// in the global scope.
2377componentHandler.register({
2378 constructor: MaterialSpinner,
2379 classAsString: 'MaterialSpinner',
2380 cssClass: 'mdl-js-spinner',
2381 widget: true
2382});
2383/**
2384 * @license
2385 * Copyright 2015 Google Inc. All Rights Reserved.
2386 *
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
2390 *
2391 * http://www.apache.org/licenses/LICENSE-2.0
2392 *
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.
2398 */
2399/**
2400 * Class constructor for Checkbox MDL component.
2401 * Implements MDL component design pattern defined at:
2402 * https://github.com/jasonmayes/mdl-component-design-pattern
2403 *
2404 * @constructor
2405 * @param {HTMLElement} element The element that will be upgraded.
2406 */
2407var MaterialSwitch = function MaterialSwitch(element) {
2408 this.element_ = element;
2409 // Initialize instance.
2410 this.init();
2411};
2412window['MaterialSwitch'] = MaterialSwitch;
2413/**
2414 * Store constants in one place so they can be updated easily.
2415 *
2416 * @enum {string | number}
2417 * @private
2418 */
2419MaterialSwitch.prototype.Constant_ = { TINY_TIMEOUT: 0.001 };
2420/**
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.
2424 *
2425 * @enum {string}
2426 * @private
2427 */
2428MaterialSwitch.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'
2441};
2442/**
2443 * Handle change of state.
2444 *
2445 * @param {Event} event The event that fired.
2446 * @private
2447 */
2448MaterialSwitch.prototype.onChange_ = function (event) {
2449 this.updateClasses_();
2450};
2451/**
2452 * Handle focus of element.
2453 *
2454 * @param {Event} event The event that fired.
2455 * @private
2456 */
2457MaterialSwitch.prototype.onFocus_ = function (event) {
2458 this.element_.classList.add(this.CssClasses_.IS_FOCUSED);
2459};
2460/**
2461 * Handle lost focus of element.
2462 *
2463 * @param {Event} event The event that fired.
2464 * @private
2465 */
2466MaterialSwitch.prototype.onBlur_ = function (event) {
2467 this.element_.classList.remove(this.CssClasses_.IS_FOCUSED);
2468};
2469/**
2470 * Handle mouseup.
2471 *
2472 * @param {Event} event The event that fired.
2473 * @private
2474 */
2475MaterialSwitch.prototype.onMouseUp_ = function (event) {
2476 this.blur_();
2477};
2478/**
2479 * Handle class updates.
2480 *
2481 * @private
2482 */
2483MaterialSwitch.prototype.updateClasses_ = function () {
2484 this.checkDisabled();
2485 this.checkToggleState();
2486};
2487/**
2488 * Add blur.
2489 *
2490 * @private
2491 */
2492MaterialSwitch.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);
2498};
2499// Public methods.
2500/**
2501 * Check the components disabled state.
2502 *
2503 * @public
2504 */
2505MaterialSwitch.prototype.checkDisabled = function () {
2506 if (this.inputElement_.disabled) {
2507 this.element_.classList.add(this.CssClasses_.IS_DISABLED);
2508 } else {
2509 this.element_.classList.remove(this.CssClasses_.IS_DISABLED);
2510 }
2511};
2512MaterialSwitch.prototype['checkDisabled'] = MaterialSwitch.prototype.checkDisabled;
2513/**
2514 * Check the components toggled state.
2515 *
2516 * @public
2517 */
2518MaterialSwitch.prototype.checkToggleState = function () {
2519 if (this.inputElement_.checked) {
2520 this.element_.classList.add(this.CssClasses_.IS_CHECKED);
2521 } else {
2522 this.element_.classList.remove(this.CssClasses_.IS_CHECKED);
2523 }
2524};
2525MaterialSwitch.prototype['checkToggleState'] = MaterialSwitch.prototype.checkToggleState;
2526/**
2527 * Disable switch.
2528 *
2529 * @public
2530 */
2531MaterialSwitch.prototype.disable = function () {
2532 this.inputElement_.disabled = true;
2533 this.updateClasses_();
2534};
2535MaterialSwitch.prototype['disable'] = MaterialSwitch.prototype.disable;
2536/**
2537 * Enable switch.
2538 *
2539 * @public
2540 */
2541MaterialSwitch.prototype.enable = function () {
2542 this.inputElement_.disabled = false;
2543 this.updateClasses_();
2544};
2545MaterialSwitch.prototype['enable'] = MaterialSwitch.prototype.enable;
2546/**
2547 * Activate switch.
2548 *
2549 * @public
2550 */
2551MaterialSwitch.prototype.on = function () {
2552 this.inputElement_.checked = true;
2553 this.updateClasses_();
2554};
2555MaterialSwitch.prototype['on'] = MaterialSwitch.prototype.on;
2556/**
2557 * Deactivate switch.
2558 *
2559 * @public
2560 */
2561MaterialSwitch.prototype.off = function () {
2562 this.inputElement_.checked = false;
2563 this.updateClasses_();
2564};
2565MaterialSwitch.prototype['off'] = MaterialSwitch.prototype.off;
2566/**
2567 * Initialize element.
2568 */
2569MaterialSwitch.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_);
2593 }
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');
2603 }
2604};
2605// The component registers itself. It can assume componentHandler is available
2606// in the global scope.
2607componentHandler.register({
2608 constructor: MaterialSwitch,
2609 classAsString: 'MaterialSwitch',
2610 cssClass: 'mdl-js-switch',
2611 widget: true
2612});
2613/**
2614 * @license
2615 * Copyright 2015 Google Inc. All Rights Reserved.
2616 *
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
2620 *
2621 * http://www.apache.org/licenses/LICENSE-2.0
2622 *
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.
2628 */
2629/**
2630 * Class constructor for Tabs MDL component.
2631 * Implements MDL component design pattern defined at:
2632 * https://github.com/jasonmayes/mdl-component-design-pattern
2633 *
2634 * @constructor
2635 * @param {Element} element The element that will be upgraded.
2636 */
2637var MaterialTabs = function MaterialTabs(element) {
2638 // Stores the HTML element.
2639 this.element_ = element;
2640 // Initialize instance.
2641 this.init();
2642};
2643window['MaterialTabs'] = MaterialTabs;
2644/**
2645 * Store constants in one place so they can be updated easily.
2646 *
2647 * @enum {string}
2648 * @private
2649 */
2650MaterialTabs.prototype.Constant_ = {};
2651/**
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.
2655 *
2656 * @enum {string}
2657 * @private
2658 */
2659MaterialTabs.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'
2668};
2669/**
2670 * Handle clicks to a tabs component
2671 *
2672 * @private
2673 */
2674MaterialTabs.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);
2677 }
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);
2684 }
2685 this.element_.classList.add(this.CssClasses_.UPGRADED_CLASS);
2686};
2687/**
2688 * Reset tab state, dropping active classes
2689 *
2690 * @private
2691 */
2692MaterialTabs.prototype.resetTabState_ = function () {
2693 for (var k = 0; k < this.tabs_.length; k++) {
2694 this.tabs_[k].classList.remove(this.CssClasses_.ACTIVE_CLASS);
2695 }
2696};
2697/**
2698 * Reset panel state, droping active classes
2699 *
2700 * @private
2701 */
2702MaterialTabs.prototype.resetPanelState_ = function () {
2703 for (var j = 0; j < this.panels_.length; j++) {
2704 this.panels_[j].classList.remove(this.CssClasses_.ACTIVE_CLASS);
2705 }
2706};
2707/**
2708 * Initialize element.
2709 */
2710MaterialTabs.prototype.init = function () {
2711 if (this.element_) {
2712 this.initTabs_();
2713 }
2714};
2715/**
2716 * Constructor for an individual tab.
2717 *
2718 * @constructor
2719 * @param {Element} tab The HTML element for the tab.
2720 * @param {MaterialTabs} ctx The MaterialTabs object that owns the tab.
2721 */
2722function MaterialTab(tab, ctx) {
2723 if (tab) {
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);
2732 }
2733 tab.addEventListener('click', function (e) {
2734 if (tab.getAttribute('href').charAt(0) === '#') {
2735 e.preventDefault();
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);
2742 }
2743 });
2744 }
2745}
2746// The component registers itself. It can assume componentHandler is available
2747// in the global scope.
2748componentHandler.register({
2749 constructor: MaterialTabs,
2750 classAsString: 'MaterialTabs',
2751 cssClass: 'mdl-js-tabs'
2752});
2753/**
2754 * @license
2755 * Copyright 2015 Google Inc. All Rights Reserved.
2756 *
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
2760 *
2761 * http://www.apache.org/licenses/LICENSE-2.0
2762 *
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.
2768 */
2769/**
2770 * Class constructor for Textfield MDL component.
2771 * Implements MDL component design pattern defined at:
2772 * https://github.com/jasonmayes/mdl-component-design-pattern
2773 *
2774 * @constructor
2775 * @param {HTMLElement} element The element that will be upgraded.
2776 */
2777var MaterialTextfield = function MaterialTextfield(element) {
2778 this.element_ = element;
2779 this.maxRows = this.Constant_.NO_MAX_ROWS;
2780 // Initialize instance.
2781 this.init();
2782};
2783window['MaterialTextfield'] = MaterialTextfield;
2784/**
2785 * Store constants in one place so they can be updated easily.
2786 *
2787 * @enum {string | number}
2788 * @private
2789 */
2790MaterialTextfield.prototype.Constant_ = {
2791 NO_MAX_ROWS: -1,
2792 MAX_ROWS_ATTRIBUTE: 'maxrows'
2793};
2794/**
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.
2798 *
2799 * @enum {string}
2800 * @private
2801 */
2802MaterialTextfield.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'
2811};
2812/**
2813 * Handle input being entered.
2814 *
2815 * @param {Event} event The event that fired.
2816 * @private
2817 */
2818MaterialTextfield.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();
2823 }
2824 }
2825};
2826/**
2827 * Handle focus.
2828 *
2829 * @param {Event} event The event that fired.
2830 * @private
2831 */
2832MaterialTextfield.prototype.onFocus_ = function (event) {
2833 this.element_.classList.add(this.CssClasses_.IS_FOCUSED);
2834};
2835/**
2836 * Handle lost focus.
2837 *
2838 * @param {Event} event The event that fired.
2839 * @private
2840 */
2841MaterialTextfield.prototype.onBlur_ = function (event) {
2842 this.element_.classList.remove(this.CssClasses_.IS_FOCUSED);
2843};
2844/**
2845 * Handle reset event from out side.
2846 *
2847 * @param {Event} event The event that fired.
2848 * @private
2849 */
2850MaterialTextfield.prototype.onReset_ = function (event) {
2851 this.updateClasses_();
2852};
2853/**
2854 * Handle class updates.
2855 *
2856 * @private
2857 */
2858MaterialTextfield.prototype.updateClasses_ = function () {
2859 this.checkDisabled();
2860 this.checkValidity();
2861 this.checkDirty();
2862 this.checkFocus();
2863};
2864// Public methods.
2865/**
2866 * Check the disabled state and update field accordingly.
2867 *
2868 * @public
2869 */
2870MaterialTextfield.prototype.checkDisabled = function () {
2871 if (this.input_.disabled) {
2872 this.element_.classList.add(this.CssClasses_.IS_DISABLED);
2873 } else {
2874 this.element_.classList.remove(this.CssClasses_.IS_DISABLED);
2875 }
2876};
2877MaterialTextfield.prototype['checkDisabled'] = MaterialTextfield.prototype.checkDisabled;
2878/**
2879 * Check the focus state and update field accordingly.
2880 *
2881 * @public
2882 */
2883MaterialTextfield.prototype.checkFocus = function () {
2884 if (Boolean(this.element_.querySelector(':focus'))) {
2885 this.element_.classList.add(this.CssClasses_.IS_FOCUSED);
2886 } else {
2887 this.element_.classList.remove(this.CssClasses_.IS_FOCUSED);
2888 }
2889};
2890MaterialTextfield.prototype['checkFocus'] = MaterialTextfield.prototype.checkFocus;
2891/**
2892 * Check the validity state and update field accordingly.
2893 *
2894 * @public
2895 */
2896MaterialTextfield.prototype.checkValidity = function () {
2897 if (this.input_.validity) {
2898 if (this.input_.validity.valid) {
2899 this.element_.classList.remove(this.CssClasses_.IS_INVALID);
2900 } else {
2901 this.element_.classList.add(this.CssClasses_.IS_INVALID);
2902 }
2903 }
2904};
2905MaterialTextfield.prototype['checkValidity'] = MaterialTextfield.prototype.checkValidity;
2906/**
2907 * Check the dirty state and update field accordingly.
2908 *
2909 * @public
2910 */
2911MaterialTextfield.prototype.checkDirty = function () {
2912 if (this.input_.value && this.input_.value.length > 0) {
2913 this.element_.classList.add(this.CssClasses_.IS_DIRTY);
2914 } else {
2915 this.element_.classList.remove(this.CssClasses_.IS_DIRTY);
2916 }
2917};
2918MaterialTextfield.prototype['checkDirty'] = MaterialTextfield.prototype.checkDirty;
2919/**
2920 * Disable text field.
2921 *
2922 * @public
2923 */
2924MaterialTextfield.prototype.disable = function () {
2925 this.input_.disabled = true;
2926 this.updateClasses_();
2927};
2928MaterialTextfield.prototype['disable'] = MaterialTextfield.prototype.disable;
2929/**
2930 * Enable text field.
2931 *
2932 * @public
2933 */
2934MaterialTextfield.prototype.enable = function () {
2935 this.input_.disabled = false;
2936 this.updateClasses_();
2937};
2938MaterialTextfield.prototype['enable'] = MaterialTextfield.prototype.enable;
2939/**
2940 * Update text field value.
2941 *
2942 * @param {string} value The value to which to set the control (optional).
2943 * @public
2944 */
2945MaterialTextfield.prototype.change = function (value) {
2946 this.input_.value = value || '';
2947 this.updateClasses_();
2948};
2949MaterialTextfield.prototype['change'] = MaterialTextfield.prototype.change;
2950/**
2951 * Initialize element.
2952 */
2953MaterialTextfield.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);
2957 if (this.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;
2962 }
2963 }
2964 if (this.input_.hasAttribute('placeholder')) {
2965 this.element_.classList.add(this.CssClasses_.HAS_PLACEHOLDER);
2966 }
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);
2980 }
2981 var invalid = this.element_.classList.contains(this.CssClasses_.IS_INVALID);
2982 this.updateClasses_();
2983 this.element_.classList.add(this.CssClasses_.IS_UPGRADED);
2984 if (invalid) {
2985 this.element_.classList.add(this.CssClasses_.IS_INVALID);
2986 }
2987 if (this.input_.hasAttribute('autofocus')) {
2988 this.element_.focus();
2989 this.checkFocus();
2990 }
2991 }
2992 }
2993};
2994// The component registers itself. It can assume componentHandler is available
2995// in the global scope.
2996componentHandler.register({
2997 constructor: MaterialTextfield,
2998 classAsString: 'MaterialTextfield',
2999 cssClass: 'mdl-js-textfield',
3000 widget: true
3001});
3002/**
3003 * @license
3004 * Copyright 2015 Google Inc. All Rights Reserved.
3005 *
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
3009 *
3010 * http://www.apache.org/licenses/LICENSE-2.0
3011 *
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.
3017 */
3018/**
3019 * Class constructor for Tooltip MDL component.
3020 * Implements MDL component design pattern defined at:
3021 * https://github.com/jasonmayes/mdl-component-design-pattern
3022 *
3023 * @constructor
3024 * @param {HTMLElement} element The element that will be upgraded.
3025 */
3026var MaterialTooltip = function MaterialTooltip(element) {
3027 this.element_ = element;
3028 // Initialize instance.
3029 this.init();
3030};
3031window['MaterialTooltip'] = MaterialTooltip;
3032/**
3033 * Store constants in one place so they can be updated easily.
3034 *
3035 * @enum {string | number}
3036 * @private
3037 */
3038MaterialTooltip.prototype.Constant_ = {};
3039/**
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.
3043 *
3044 * @enum {string}
3045 * @private
3046 */
3047MaterialTooltip.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'
3053};
3054/**
3055 * Handle mouseenter for tooltip.
3056 *
3057 * @param {Event} event The event that fired.
3058 * @private
3059 */
3060MaterialTooltip.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';
3071 } else {
3072 this.element_.style.top = top + 'px';
3073 this.element_.style.marginTop = marginTop + 'px';
3074 }
3075 } else {
3076 if (left + marginLeft < 0) {
3077 this.element_.style.left = '0';
3078 this.element_.style.marginLeft = '0';
3079 } else {
3080 this.element_.style.left = left + 'px';
3081 this.element_.style.marginLeft = marginLeft + 'px';
3082 }
3083 }
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';
3090 } else {
3091 this.element_.style.top = props.top + props.height + 10 + 'px';
3092 }
3093 this.element_.classList.add(this.CssClasses_.IS_ACTIVE);
3094};
3095/**
3096 * Hide tooltip on mouseleave or scroll
3097 *
3098 * @private
3099 */
3100MaterialTooltip.prototype.hideTooltip_ = function () {
3101 this.element_.classList.remove(this.CssClasses_.IS_ACTIVE);
3102};
3103/**
3104 * Initialize element.
3105 */
3106MaterialTooltip.prototype.init = function () {
3107 if (this.element_) {
3108 var forElId = this.element_.getAttribute('for') || this.element_.getAttribute('data-mdl-for');
3109 if (forElId) {
3110 this.forElement_ = document.getElementById(forElId);
3111 }
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');
3116 }
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);
3124 }
3125 }
3126};
3127// The component registers itself. It can assume componentHandler is available
3128// in the global scope.
3129componentHandler.register({
3130 constructor: MaterialTooltip,
3131 classAsString: 'MaterialTooltip',
3132 cssClass: 'mdl-tooltip'
3133});
3134/**
3135 * @license
3136 * Copyright 2015 Google Inc. All Rights Reserved.
3137 *
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
3141 *
3142 * http://www.apache.org/licenses/LICENSE-2.0
3143 *
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.
3149 */
3150/**
3151 * Class constructor for Layout MDL component.
3152 * Implements MDL component design pattern defined at:
3153 * https://github.com/jasonmayes/mdl-component-design-pattern
3154 *
3155 * @constructor
3156 * @param {HTMLElement} element The element that will be upgraded.
3157 */
3158var MaterialLayout = function MaterialLayout(element) {
3159 this.element_ = element;
3160 // Initialize instance.
3161 this.init();
3162};
3163window['MaterialLayout'] = MaterialLayout;
3164/**
3165 * Store constants in one place so they can be updated easily.
3166 *
3167 * @enum {string | number}
3168 * @private
3169 */
3170MaterialLayout.prototype.Constant_ = {
3171 MAX_WIDTH: '(max-width: 1024px)',
3172 TAB_SCROLL_PIXELS: 100,
3173 RESIZE_TIMEOUT: 100,
3174 MENU_ICON: '&#xE5D2;',
3175 CHEVRON_LEFT: 'chevron_left',
3176 CHEVRON_RIGHT: 'chevron_right'
3177};
3178/**
3179 * Keycodes, for code readability.
3180 *
3181 * @enum {number}
3182 * @private
3183 */
3184MaterialLayout.prototype.Keycodes_ = {
3185 ENTER: 13,
3186 ESCAPE: 27,
3187 SPACE: 32
3188};
3189/**
3190 * Modes.
3191 *
3192 * @enum {number}
3193 * @private
3194 */
3195MaterialLayout.prototype.Mode_ = {
3196 STANDARD: 0,
3197 SEAMED: 1,
3198 WATERFALL: 2,
3199 SCROLL: 3
3200};
3201/**
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.
3205 *
3206 * @enum {string}
3207 * @private
3208 */
3209MaterialLayout.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'
3245};
3246/**
3247 * Handles scrolling on the content.
3248 *
3249 * @private
3250 */
3251MaterialLayout.prototype.contentScrollHandler_ = function () {
3252 if (this.header_.classList.contains(this.CssClasses_.IS_ANIMATING)) {
3253 return;
3254 }
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);
3261 }
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);
3267 }
3268 }
3269};
3270/**
3271 * Handles a keyboard event on the drawer.
3272 *
3273 * @param {Event} evt The event that fired.
3274 * @private
3275 */
3276MaterialLayout.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();
3280 }
3281};
3282/**
3283 * Handles changes in screen size.
3284 *
3285 * @private
3286 */
3287MaterialLayout.prototype.screenSizeHandler_ = function () {
3288 if (this.screenSizeMediaQuery_.matches) {
3289 this.element_.classList.add(this.CssClasses_.IS_SMALL_SCREEN);
3290 } else {
3291 this.element_.classList.remove(this.CssClasses_.IS_SMALL_SCREEN);
3292 // Collapse drawer (if any) when moving to a large screen size.
3293 if (this.drawer_) {
3294 this.drawer_.classList.remove(this.CssClasses_.IS_DRAWER_OPEN);
3295 this.obfuscator_.classList.remove(this.CssClasses_.IS_DRAWER_OPEN);
3296 }
3297 }
3298};
3299/**
3300 * Handles events of drawer button.
3301 *
3302 * @param {Event} evt The event that fired.
3303 * @private
3304 */
3305MaterialLayout.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();
3310 } else {
3311 // prevent other keys
3312 return;
3313 }
3314 }
3315 this.toggleDrawer();
3316};
3317/**
3318 * Handles (un)setting the `is-animating` class
3319 *
3320 * @private
3321 */
3322MaterialLayout.prototype.headerTransitionEndHandler_ = function () {
3323 this.header_.classList.remove(this.CssClasses_.IS_ANIMATING);
3324};
3325/**
3326 * Handles expanding the header on click
3327 *
3328 * @private
3329 */
3330MaterialLayout.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);
3334 }
3335};
3336/**
3337 * Reset tab state, dropping active classes
3338 *
3339 * @private
3340 */
3341MaterialLayout.prototype.resetTabState_ = function (tabBar) {
3342 for (var k = 0; k < tabBar.length; k++) {
3343 tabBar[k].classList.remove(this.CssClasses_.IS_ACTIVE);
3344 }
3345};
3346/**
3347 * Reset panel state, droping active classes
3348 *
3349 * @private
3350 */
3351MaterialLayout.prototype.resetPanelState_ = function (panels) {
3352 for (var j = 0; j < panels.length; j++) {
3353 panels[j].classList.remove(this.CssClasses_.IS_ACTIVE);
3354 }
3355};
3356/**
3357 * Toggle drawer state
3358 *
3359 * @public
3360 */
3361MaterialLayout.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');
3369 } else {
3370 this.drawer_.setAttribute('aria-hidden', 'true');
3371 drawerButton.setAttribute('aria-expanded', 'false');
3372 }
3373};
3374MaterialLayout.prototype['toggleDrawer'] = MaterialLayout.prototype.toggleDrawer;
3375/**
3376 * Initialize element.
3377 */
3378MaterialLayout.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();
3388 }
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;
3395 }
3396 if (child.classList && child.classList.contains(this.CssClasses_.DRAWER)) {
3397 this.drawer_ = child;
3398 }
3399 if (child.classList && child.classList.contains(this.CssClasses_.CONTENT)) {
3400 this.content_ = child;
3401 }
3402 }
3403 window.addEventListener('pageshow', function (e) {
3404 if (e.persisted) {
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 = '';
3410 }.bind(this));
3411 }
3412 }.bind(this), false);
3413 if (this.header_) {
3414 this.tabBar_ = this.header_.querySelector('.' + this.CssClasses_.TAB_BAR);
3415 }
3416 var mode = this.Mode_.STANDARD;
3417 if (this.header_) {
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);
3427 }
3428 if (mode === this.Mode_.STANDARD) {
3429 this.header_.classList.add(this.CssClasses_.CASTING_SHADOW);
3430 if (this.tabBar_) {
3431 this.tabBar_.classList.add(this.CssClasses_.CASTING_SHADOW);
3432 }
3433 } else if (mode === this.Mode_.SEAMED || mode === this.Mode_.SCROLL) {
3434 this.header_.classList.remove(this.CssClasses_.CASTING_SHADOW);
3435 if (this.tabBar_) {
3436 this.tabBar_.classList.remove(this.CssClasses_.CASTING_SHADOW);
3437 }
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
3441 // the header.
3442 this.content_.addEventListener('scroll', this.contentScrollHandler_.bind(this));
3443 this.contentScrollHandler_();
3444 }
3445 }
3446 // Add drawer toggling button to our layout, if we have an openable drawer.
3447 if (this.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);
3459 }
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);
3466 }
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
3471 // not be present.
3472 this.element_.classList.add(this.CssClasses_.HAS_DRAWER);
3473 // If we have a fixed header, add the button to the header rather than
3474 // the layout.
3475 if (this.element_.classList.contains(this.CssClasses_.FIXED_HEADER)) {
3476 this.header_.insertBefore(drawerButton, this.header_.firstChild);
3477 } else {
3478 this.element_.insertBefore(drawerButton, this.content_);
3479 }
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');
3487 }
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;
3509 }.bind(this));
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;
3519 }.bind(this));
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
3524 // window size.
3525 var tabUpdateHandler = function () {
3526 if (this.tabBar_.scrollLeft > 0) {
3527 leftButton.classList.add(this.CssClasses_.IS_ACTIVE);
3528 } else {
3529 leftButton.classList.remove(this.CssClasses_.IS_ACTIVE);
3530 }
3531 if (this.tabBar_.scrollLeft < this.tabBar_.scrollWidth - this.tabBar_.offsetWidth) {
3532 rightButton.classList.add(this.CssClasses_.IS_ACTIVE);
3533 } else {
3534 rightButton.classList.remove(this.CssClasses_.IS_ACTIVE);
3535 }
3536 }.bind(this);
3537 this.tabBar_.addEventListener('scroll', tabUpdateHandler);
3538 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_);
3544 }
3545 this.resizeTimeoutId_ = setTimeout(function () {
3546 tabUpdateHandler();
3547 this.resizeTimeoutId_ = null;
3548 }.bind(this), this.Constant_.RESIZE_TIMEOUT);
3549 }.bind(this);
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);
3553 }
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);
3560 }
3561 }
3562 this.element_.classList.add(this.CssClasses_.IS_UPGRADED);
3563 }
3564};
3565/**
3566 * Constructor for an individual tab.
3567 *
3568 * @constructor
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.
3573 */
3574function MaterialLayoutTab(tab, tabs, panels, layout) {
3575 /**
3576 * Auxiliary method to programmatically select a tab in the UI.
3577 */
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);
3585 }
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);
3594 }
3595 if (!layout.tabBar_.classList.contains(layout.CssClasses_.TAB_MANUAL_SWITCH)) {
3596 tab.addEventListener('click', function (e) {
3597 if (tab.getAttribute('href').charAt(0) === '#') {
3598 e.preventDefault();
3599 selectTab();
3600 }
3601 });
3602 }
3603 tab.show = selectTab;
3604}
3605window['MaterialLayoutTab'] = MaterialLayoutTab;
3606// The component registers itself. It can assume componentHandler is available
3607// in the global scope.
3608componentHandler.register({
3609 constructor: MaterialLayout,
3610 classAsString: 'MaterialLayout',
3611 cssClass: 'mdl-js-layout'
3612});
3613/**
3614 * @license
3615 * Copyright 2015 Google Inc. All Rights Reserved.
3616 *
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
3620 *
3621 * http://www.apache.org/licenses/LICENSE-2.0
3622 *
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.
3628 */
3629/**
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
3633 *
3634 * @constructor
3635 * @param {Element} element The element that will be upgraded.
3636 */
3637var MaterialDataTable = function MaterialDataTable(element) {
3638 this.element_ = element;
3639 // Initialize instance.
3640 this.init();
3641};
3642window['MaterialDataTable'] = MaterialDataTable;
3643/**
3644 * Store constants in one place so they can be updated easily.
3645 *
3646 * @enum {string | number}
3647 * @private
3648 */
3649MaterialDataTable.prototype.Constant_ = {};
3650/**
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.
3654 *
3655 * @enum {string}
3656 * @private
3657 */
3658MaterialDataTable.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'
3664};
3665/**
3666 * Generates and returns a function that toggles the selection state of a
3667 * single row (or multiple rows).
3668 *
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.
3672 * @private
3673 */
3674MaterialDataTable.prototype.selectRow_ = function (checkbox, row, opt_rows) {
3675 if (row) {
3676 return function () {
3677 if (checkbox.checked) {
3678 row.classList.add(this.CssClasses_.IS_SELECTED);
3679 } else {
3680 row.classList.remove(this.CssClasses_.IS_SELECTED);
3681 }
3682 }.bind(this);
3683 }
3684 if (opt_rows) {
3685 return function () {
3686 var i;
3687 var el;
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);
3693 }
3694 } else {
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);
3699 }
3700 }
3701 }.bind(this);
3702 }
3703};
3704/**
3705 * Creates a checkbox for a single or or multiple rows and hooks up the
3706 * event handling.
3707 *
3708 * @param {Element} row Row to toggle when checkbox changes.
3709 * @param {(Array<Object>|NodeList)=} opt_rows Rows to toggle when checkbox changes.
3710 * @private
3711 */
3712MaterialDataTable.prototype.createCheckbox_ = function (row, opt_rows) {
3713 var label = document.createElement('label');
3714 var labelClasses = [
3715 'mdl-checkbox',
3716 'mdl-js-checkbox',
3717 'mdl-js-ripple-effect',
3718 this.CssClasses_.SELECT_ELEMENT
3719 ];
3720 label.className = labelClasses.join(' ');
3721 var checkbox = document.createElement('input');
3722 checkbox.type = 'checkbox';
3723 checkbox.classList.add('mdl-checkbox__input');
3724 if (row) {
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));
3729 }
3730 label.appendChild(checkbox);
3731 componentHandler.upgradeElement(label, 'MaterialCheckbox');
3732 return label;
3733};
3734/**
3735 * Initialize element.
3736 */
3737MaterialDataTable.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');
3750 if (firstCell) {
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);
3755 }
3756 rows[i].insertBefore(td, firstCell);
3757 }
3758 }
3759 this.element_.classList.add(this.CssClasses_.IS_UPGRADED);
3760 }
3761 }
3762};
3763// The component registers itself. It can assume componentHandler is available
3764// in the global scope.
3765componentHandler.register({
3766 constructor: MaterialDataTable,
3767 classAsString: 'MaterialDataTable',
3768 cssClass: 'mdl-js-data-table'
3769});
3770/**
3771 * @license
3772 * Copyright 2015 Google Inc. All Rights Reserved.
3773 *
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
3777 *
3778 * http://www.apache.org/licenses/LICENSE-2.0
3779 *
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.
3785 */
3786/**
3787 * Class constructor for Ripple MDL component.
3788 * Implements MDL component design pattern defined at:
3789 * https://github.com/jasonmayes/mdl-component-design-pattern
3790 *
3791 * @constructor
3792 * @param {HTMLElement} element The element that will be upgraded.
3793 */
3794var MaterialRipple = function MaterialRipple(element) {
3795 this.element_ = element;
3796 // Initialize instance.
3797 this.init();
3798};
3799window['MaterialRipple'] = MaterialRipple;
3800/**
3801 * Store constants in one place so they can be updated easily.
3802 *
3803 * @enum {string | number}
3804 * @private
3805 */
3806MaterialRipple.prototype.Constant_ = {
3807 INITIAL_SCALE: 'scale(0.0001, 0.0001)',
3808 INITIAL_SIZE: '1px',
3809 INITIAL_OPACITY: '0.4',
3810 FINAL_OPACITY: '0',
3811 FINAL_SCALE: ''
3812};
3813/**
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.
3817 *
3818 * @enum {string}
3819 * @private
3820 */
3821MaterialRipple.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'
3827};
3828/**
3829 * Handle mouse / finger down on element.
3830 *
3831 * @param {Event} event The event that fired.
3832 * @private
3833 */
3834MaterialRipple.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';
3842 }
3843 this.rippleElement_.classList.add(this.CssClasses_.IS_VISIBLE);
3844 if (event.type === 'mousedown' && this.ignoringMouseDown_) {
3845 this.ignoringMouseDown_ = false;
3846 } else {
3847 if (event.type === 'touchstart') {
3848 this.ignoringMouseDown_ = true;
3849 }
3850 var frameCount = this.getFrameCount();
3851 if (frameCount > 0) {
3852 return;
3853 }
3854 this.setFrameCount(1);
3855 var bound = event.currentTarget.getBoundingClientRect();
3856 var x;
3857 var y;
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);
3862 } else {
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);
3867 }
3868 this.setRippleXY(x, y);
3869 this.setRippleStyles(true);
3870 window.requestAnimationFrame(this.animFrameHandler.bind(this));
3871 }
3872};
3873/**
3874 * Handle mouse / finger up on element.
3875 *
3876 * @param {Event} event The event that fired.
3877 * @private
3878 */
3879MaterialRipple.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
3884 // mousedown.
3885 window.setTimeout(function () {
3886 this.rippleElement_.classList.remove(this.CssClasses_.IS_VISIBLE);
3887 }.bind(this), 0);
3888 }
3889};
3890/**
3891 * Initialize element.
3892 */
3893MaterialRipple.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;
3900 this.x_ = 0;
3901 this.y_ = 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);
3914 /**
3915 * Getter for frameCount_.
3916 * @return {number} the frame count.
3917 */
3918 this.getFrameCount = function () {
3919 return this.frameCount_;
3920 };
3921 /**
3922 * Setter for frameCount_.
3923 * @param {number} fC the frame count.
3924 */
3925 this.setFrameCount = function (fC) {
3926 this.frameCount_ = fC;
3927 };
3928 /**
3929 * Getter for rippleElement_.
3930 * @return {Element} the ripple element.
3931 */
3932 this.getRippleElement = function () {
3933 return this.rippleElement_;
3934 };
3935 /**
3936 * Sets the ripple X and Y coordinates.
3937 * @param {number} newX the new X coordinate
3938 * @param {number} newY the new Y coordinate
3939 */
3940 this.setRippleXY = function (newX, newY) {
3941 this.x_ = newX;
3942 this.y_ = newY;
3943 };
3944 /**
3945 * Sets the ripple styles.
3946 * @param {boolean} start whether or not this is the start frame.
3947 */
3948 this.setRippleStyles = function (start) {
3949 if (this.rippleElement_ !== null) {
3950 var transformString;
3951 var scale;
3952 var size;
3953 var offset = 'translate(' + this.x_ + 'px, ' + this.y_ + 'px)';
3954 if (start) {
3955 scale = this.Constant_.INITIAL_SCALE;
3956 size = this.Constant_.INITIAL_SIZE;
3957 } else {
3958 scale = this.Constant_.FINAL_SCALE;
3959 size = this.rippleSize_ + 'px';
3960 if (recentering) {
3961 offset = 'translate(' + this.boundWidth / 2 + 'px, ' + this.boundHeight / 2 + 'px)';
3962 }
3963 }
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;
3968 if (start) {
3969 this.rippleElement_.classList.remove(this.CssClasses_.IS_ANIMATING);
3970 } else {
3971 this.rippleElement_.classList.add(this.CssClasses_.IS_ANIMATING);
3972 }
3973 }
3974 };
3975 /**
3976 * Handles an animation frame.
3977 */
3978 this.animFrameHandler = function () {
3979 if (this.frameCount_-- > 0) {
3980 window.requestAnimationFrame(this.animFrameHandler.bind(this));
3981 } else {
3982 this.setRippleStyles(false);
3983 }
3984 };
3985 }
3986 }
3987};
3988// The component registers itself. It can assume componentHandler is available
3989// in the global scope.
3990componentHandler.register({
3991 constructor: MaterialRipple,
3992 classAsString: 'MaterialRipple',
3993 cssClass: 'mdl-js-ripple-effect',
3994 widget: false
3995});
3996}());