blob: cd63c922152404a67ebb2f9512fea52e12dd619a [file] [log] [blame]
Skip Wonnell2c977e22018-03-01 08:30:15 -06001;(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.
32 var 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 /**
45 * Upgrades a specific element rather than all in the DOM.
46 *
47 * @param {!Element} element The element we wish to upgrade.
48 * @param {string=} optJsClass Optional name of the class we want to upgrade
49 * the element to.
50 */
51 upgradeElement: function (element, optJsClass) {
52 },
53 /**
54 * Upgrades a specific list of elements rather than all in the DOM.
55 *
56 * @param {!Element|!Array<!Element>|!NodeList|!HTMLCollection} elements
57 * The elements we wish to upgrade.
58 */
59 upgradeElements: function (elements) {
60 },
61 /**
62 * Upgrades all registered components found in the current DOM. This is
63 * automatically called on window load.
64 */
65 upgradeAllRegistered: function () {
66 },
67 /**
68 * Allows user to be alerted to any upgrades that are performed for a given
69 * component type
70 *
71 * @param {string} jsClass The class name of the MDL component we wish
72 * to hook into for any upgrades performed.
73 * @param {function(!HTMLElement)} callback The function to call upon an
74 * upgrade. This function should expect 1 parameter - the HTMLElement which
75 * got upgraded.
76 */
77 registerUpgradedCallback: function (jsClass, callback) {
78 },
79 /**
80 * Registers a class for future use and attempts to upgrade existing DOM.
81 *
82 * @param {componentHandler.ComponentConfigPublic} config the registration configuration
83 */
84 register: function (config) {
85 },
86 /**
87 * Downgrade either a given node, an array of nodes, or a NodeList.
88 *
89 * @param {!Node|!Array<!Node>|!NodeList} nodes
90 */
91 downgradeElements: function (nodes) {
92 }
93 };
94
95 componentHandler = (function () {
96 'use strict';
97
98 /** @type {!Array<componentHandler.ComponentConfig>} */
99 var registeredComponents_ = [];
100
101 /** @type {!Array<componentHandler.Component>} */
102 var createdComponents_ = [];
103
104 var componentConfigProperty_ = 'mdlComponentConfigInternal_';
105
106 /**
107 * Searches registered components for a class we are interested in using.
108 * Optionally replaces a match with passed object if specified.
109 *
110 * @param {string} name The name of a class we want to use.
111 * @param {componentHandler.ComponentConfig=} optReplace Optional object to replace match with.
112 * @return {!Object|boolean}
113 * @private
114 */
115 function findRegisteredClass_(name, optReplace) {
116 for (var i = 0; i < registeredComponents_.length; i++) {
117 if (registeredComponents_[i].className === name) {
118 if (typeof optReplace !== 'undefined') {
119 registeredComponents_[i] = optReplace;
120 }
121 return registeredComponents_[i];
122 }
123 }
124 return false;
125 }
126
127 /**
128 * Returns an array of the classNames of the upgraded classes on the element.
129 *
130 * @param {!Element} element The element to fetch data from.
131 * @return {!Array<string>}
132 * @private
133 */
134 function getUpgradedListOfElement_(element) {
135 var dataUpgraded = element.getAttribute('data-upgraded');
136 // Use `['']` as default value to conform the `,name,name...` style.
137 return dataUpgraded === null ? [''] : dataUpgraded.split(',');
138 }
139
140 /**
141 * Returns true if the given element has already been upgraded for the given
142 * class.
143 *
144 * @param {!Element} element The element we want to check.
145 * @param {string} jsClass The class to check for.
146 * @returns {boolean}
147 * @private
148 */
149 function isElementUpgraded_(element, jsClass) {
150 var upgradedList = getUpgradedListOfElement_(element);
151 return upgradedList.indexOf(jsClass) !== -1;
152 }
153
154 /**
155 * Create an event object.
156 *
157 * @param {string} eventType The type name of the event.
158 * @param {boolean} bubbles Whether the event should bubble up the DOM.
159 * @param {boolean} cancelable Whether the event can be canceled.
160 * @returns {!Event}
161 */
162 function createEvent_(eventType, bubbles, cancelable) {
163 if ('CustomEvent' in window && typeof window.CustomEvent === 'function') {
164 return new CustomEvent(eventType, {
165 bubbles: bubbles,
166 cancelable: cancelable
167 });
168 } else {
169 var ev = document.createEvent('Events');
170 ev.initEvent(eventType, bubbles, cancelable);
171 return ev;
172 }
173 }
174
175 /**
176 * Searches existing DOM for elements of our component type and upgrades them
177 * if they have not already been upgraded.
178 *
179 * @param {string=} optJsClass the programatic name of the element class we
180 * need to create a new instance of.
181 * @param {string=} optCssClass the name of the CSS class elements of this
182 * type will have.
183 */
184 function upgradeDomInternal(optJsClass, optCssClass) {
185 if (typeof optJsClass === 'undefined' &&
186 typeof optCssClass === 'undefined') {
187 for (var i = 0; i < registeredComponents_.length; i++) {
188 upgradeDomInternal(registeredComponents_[i].className,
189 registeredComponents_[i].cssClass);
190 }
191 } else {
192 var jsClass = /** @type {string} */ (optJsClass);
193 if (typeof optCssClass === 'undefined') {
194 var registeredClass = findRegisteredClass_(jsClass);
195 if (registeredClass) {
196 optCssClass = registeredClass.cssClass;
197 }
198 }
199
200 var elements = document.querySelectorAll('.' + optCssClass);
201 for (var n = 0; n < elements.length; n++) {
202 upgradeElementInternal(elements[n], jsClass);
203 }
204 }
205 }
206
207 /**
208 * Upgrades a specific element rather than all in the DOM.
209 *
210 * @param {!Element} element The element we wish to upgrade.
211 * @param {string=} optJsClass Optional name of the class we want to upgrade
212 * the element to.
213 */
214 function upgradeElementInternal(element, optJsClass) {
215 // Verify argument type.
216 if (!(typeof element === 'object' && element instanceof Element)) {
217 throw new Error('Invalid argument provided to upgrade MDL element.');
218 }
219 // Allow upgrade to be canceled by canceling emitted event.
220 var upgradingEv = createEvent_('mdl-componentupgrading', true, true);
221 element.dispatchEvent(upgradingEv);
222 if (upgradingEv.defaultPrevented) {
223 return;
224 }
225
226 var upgradedList = getUpgradedListOfElement_(element);
227 var classesToUpgrade = [];
228 // If jsClass is not provided scan the registered components to find the
229 // ones matching the element's CSS classList.
230 if (!optJsClass) {
231 var classList = element.classList;
232 registeredComponents_.forEach(function (component) {
233 // Match CSS & Not to be upgraded & Not upgraded.
234 if (classList.contains(component.cssClass) &&
235 classesToUpgrade.indexOf(component) === -1 &&
236 !isElementUpgraded_(element, component.className)) {
237 classesToUpgrade.push(component);
238 }
239 });
240 } else if (!isElementUpgraded_(element, optJsClass)) {
241 classesToUpgrade.push(findRegisteredClass_(optJsClass));
242 }
243
244 // Upgrade the element for each classes.
245 for (var i = 0, n = classesToUpgrade.length, registeredClass; i < n; i++) {
246 registeredClass = classesToUpgrade[i];
247 if (registeredClass) {
248 // Mark element as upgraded.
249 upgradedList.push(registeredClass.className);
250 element.setAttribute('data-upgraded', upgradedList.join(','));
251 var instance = new registeredClass.classConstructor(element);
252 instance[componentConfigProperty_] = registeredClass;
253 createdComponents_.push(instance);
254 // Call any callbacks the user has registered with this component type.
255 for (var j = 0, m = registeredClass.callbacks.length; j < m; j++) {
256 registeredClass.callbacks[j](element);
257 }
258
259 if (registeredClass.widget) {
260 // Assign per element instance for control over API
261 element[registeredClass.className] = instance;
262 }
263 } else {
264 throw new Error(
265 'Unable to find a registered component for the given class.');
266 }
267
268 var upgradedEv = createEvent_('mdl-componentupgraded', true, false);
269 element.dispatchEvent(upgradedEv);
270 }
271 }
272
273 /**
274 * Upgrades a specific list of elements rather than all in the DOM.
275 *
276 * @param {!Element|!Array<!Element>|!NodeList|!HTMLCollection} elements
277 * The elements we wish to upgrade.
278 */
279 function upgradeElementsInternal(elements) {
280 if (!Array.isArray(elements)) {
281 if (elements instanceof Element) {
282 elements = [elements];
283 } else {
284 elements = Array.prototype.slice.call(elements);
285 }
286 }
287 for (var i = 0, n = elements.length, element; i < n; i++) {
288 element = elements[i];
289 if (element instanceof HTMLElement) {
290 upgradeElementInternal(element);
291 if (element.children.length > 0) {
292 upgradeElementsInternal(element.children);
293 }
294 }
295 }
296 }
297
298 /**
299 * Registers a class for future use and attempts to upgrade existing DOM.
300 *
301 * @param {componentHandler.ComponentConfigPublic} config
302 */
303 function registerInternal(config) {
304 // In order to support both Closure-compiled and uncompiled code accessing
305 // this method, we need to allow for both the dot and array syntax for
306 // property access. You'll therefore see the `foo.bar || foo['bar']`
307 // pattern repeated across this method.
308 var widgetMissing = (typeof config.widget === 'undefined' &&
309 typeof config['widget'] === 'undefined');
310 var widget = true;
311
312 if (!widgetMissing) {
313 widget = config.widget || config['widget'];
314 }
315
316 var newConfig = /** @type {componentHandler.ComponentConfig} */ ({
317 classConstructor: config.constructor || config['constructor'],
318 className: config.classAsString || config['classAsString'],
319 cssClass: config.cssClass || config['cssClass'],
320 widget: widget,
321 callbacks: []
322 });
323
324 registeredComponents_.forEach(function (item) {
325 if (item.cssClass === newConfig.cssClass) {
326 throw new Error('The provided cssClass has already been registered: ' + item.cssClass);
327 }
328 if (item.className === newConfig.className) {
329 throw new Error('The provided className has already been registered');
330 }
331 });
332
333 if (config.constructor.prototype
334 .hasOwnProperty(componentConfigProperty_)) {
335 throw new Error(
336 'MDL component classes must not have ' + componentConfigProperty_ +
337 ' defined as a property.');
338 }
339
340 var found = findRegisteredClass_(config.classAsString, newConfig);
341
342 if (!found) {
343 registeredComponents_.push(newConfig);
344 }
345 }
346
347 /**
348 * Allows user to be alerted to any upgrades that are performed for a given
349 * component type
350 *
351 * @param {string} jsClass The class name of the MDL component we wish
352 * to hook into for any upgrades performed.
353 * @param {function(!HTMLElement)} callback The function to call upon an
354 * upgrade. This function should expect 1 parameter - the HTMLElement which
355 * got upgraded.
356 */
357 function registerUpgradedCallbackInternal(jsClass, callback) {
358 var regClass = findRegisteredClass_(jsClass);
359 if (regClass) {
360 regClass.callbacks.push(callback);
361 }
362 }
363
364 /**
365 * Upgrades all registered components found in the current DOM. This is
366 * automatically called on window load.
367 */
368 function upgradeAllRegisteredInternal() {
369 for (var n = 0; n < registeredComponents_.length; n++) {
370 upgradeDomInternal(registeredComponents_[n].className);
371 }
372 }
373
374 /**
375 * Check the component for the downgrade method.
376 * Execute if found.
377 * Remove component from createdComponents list.
378 *
379 * @param {?componentHandler.Component} component
380 */
381 function deconstructComponentInternal(component) {
382 if (component) {
383 var componentIndex = createdComponents_.indexOf(component);
384 createdComponents_.splice(componentIndex, 1);
385
386 var upgrades = component.element_.getAttribute('data-upgraded').split(',');
387 var componentPlace = upgrades.indexOf(component[componentConfigProperty_].classAsString);
388 upgrades.splice(componentPlace, 1);
389 component.element_.setAttribute('data-upgraded', upgrades.join(','));
390
391 var ev = createEvent_('mdl-componentdowngraded', true, false);
392 component.element_.dispatchEvent(ev);
393 }
394 }
395
396 /**
397 * Downgrade either a given node, an array of nodes, or a NodeList.
398 *
399 * @param {!Node|!Array<!Node>|!NodeList} nodes
400 */
401 function downgradeNodesInternal(nodes) {
402 /**
403 * Auxiliary function to downgrade a single node.
404 * @param {!Node} node the node to be downgraded
405 */
406 var downgradeNode = function (node) {
407 createdComponents_.filter(function (item) {
408 return item.element_ === node;
409 }).forEach(deconstructComponentInternal);
410 };
411 if (nodes instanceof Array || nodes instanceof NodeList) {
412 for (var n = 0; n < nodes.length; n++) {
413 downgradeNode(nodes[n]);
414 }
415 } else if (nodes instanceof Node) {
416 downgradeNode(nodes);
417 } else {
418 throw new Error('Invalid argument provided to downgrade MDL nodes.');
419 }
420 }
421
422 // Now return the functions that should be made public with their publicly
423 // facing names...
424 return {
425 upgradeDom: upgradeDomInternal,
426 upgradeElement: upgradeElementInternal,
427 upgradeElements: upgradeElementsInternal,
428 upgradeAllRegistered: upgradeAllRegisteredInternal,
429 registerUpgradedCallback: registerUpgradedCallbackInternal,
430 register: registerInternal,
431 downgradeElements: downgradeNodesInternal
432 };
433 })();
434
435 /**
436 * Describes the type of a registered component type managed by
437 * componentHandler. Provided for benefit of the Closure compiler.
438 *
439 * @typedef {{
440 * constructor: Function,
441 * classAsString: string,
442 * cssClass: string,
443 * widget: (string|boolean|undefined)
444 * }}
445 */
446 componentHandler.ComponentConfigPublic; // jshint ignore:line
447
448 /**
449 * Describes the type of a registered component type managed by
450 * componentHandler. Provided for benefit of the Closure compiler.
451 *
452 * @typedef {{
453 * constructor: !Function,
454 * className: string,
455 * cssClass: string,
456 * widget: (string|boolean),
457 * callbacks: !Array<function(!HTMLElement)>
458 * }}
459 */
460 componentHandler.ComponentConfig; // jshint ignore:line
461
462 /**
463 * Created component (i.e., upgraded element) type as managed by
464 * componentHandler. Provided for benefit of the Closure compiler.
465 *
466 * @typedef {{
467 * element_: !HTMLElement,
468 * className: string,
469 * classAsString: string,
470 * cssClass: string,
471 * widget: string
472 * }}
473 */
474 componentHandler.Component; // jshint ignore:line
475
476// Export all symbols, for the benefit of Closure compiler.
477// No effect on uncompiled code.
478 componentHandler['upgradeDom'] = componentHandler.upgradeDom;
479 componentHandler['upgradeElement'] = componentHandler.upgradeElement;
480 componentHandler['upgradeElements'] = componentHandler.upgradeElements;
481 componentHandler['upgradeAllRegistered'] =
482 componentHandler.upgradeAllRegistered;
483 componentHandler['registerUpgradedCallback'] =
484 componentHandler.registerUpgradedCallback;
485 componentHandler['register'] = componentHandler.register;
486 componentHandler['downgradeElements'] = componentHandler.downgradeElements;
487 window.componentHandler = componentHandler;
488 window['componentHandler'] = componentHandler;
489
490 window.addEventListener('load', function () {
491 'use strict';
492
493 /**
494 * Performs a "Cutting the mustard" test. If the browser supports the features
495 * tested, adds a mdl-js class to the <html> element. It then upgrades all MDL
496 * components requiring JavaScript.
497 */
498 if ('classList' in document.createElement('div') &&
499 'querySelector' in document &&
500 'addEventListener' in window && Array.prototype.forEach) {
501 document.documentElement.classList.add('mdl-js');
502 componentHandler.upgradeAllRegistered();
503 } else {
504 /**
505 * Dummy function to avoid JS errors.
506 */
507 componentHandler.upgradeElement = function () {
508 };
509 /**
510 * Dummy function to avoid JS errors.
511 */
512 componentHandler.register = function () {
513 };
514 }
515 });
516
517// Source: https://github.com/darius/requestAnimationFrame/blob/master/requestAnimationFrame.js
518// Adapted from https://gist.github.com/paulirish/1579671 which derived from
519// http://paulirish.com/2011/requestanimationframe-for-smart-animating/
520// http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating
521// requestAnimationFrame polyfill by Erik Möller.
522// Fixes from Paul Irish, Tino Zijdel, Andrew Mao, Klemen Slavič, Darius Bacon
523// MIT license
524 if (!Date.now) {
525 /**
526 * Date.now polyfill.
527 * @return {number} the current Date
528 */
529 Date.now = function () {
530 return new Date().getTime();
531 };
532 Date['now'] = Date.now;
533 }
534 var vendors = [
535 'webkit',
536 'moz'
537 ];
538 for (var i = 0; i < vendors.length && !window.requestAnimationFrame; ++i) {
539 var vp = vendors[i];
540 window.requestAnimationFrame = window[vp + 'RequestAnimationFrame'];
541 window.cancelAnimationFrame = window[vp + 'CancelAnimationFrame'] || window[vp + 'CancelRequestAnimationFrame'];
542 window['requestAnimationFrame'] = window.requestAnimationFrame;
543 window['cancelAnimationFrame'] = window.cancelAnimationFrame;
544 }
545 if (/iP(ad|hone|od).*OS 6/.test(window.navigator.userAgent) || !window.requestAnimationFrame || !window.cancelAnimationFrame) {
546 var lastTime = 0;
547 /**
548 * requestAnimationFrame polyfill.
549 * @param {!Function} callback the callback function.
550 */
551 window.requestAnimationFrame = function (callback) {
552 var now = Date.now();
553 var nextTime = Math.max(lastTime + 16, now);
554 return setTimeout(function () {
555 callback(lastTime = nextTime);
556 }, nextTime - now);
557 };
558 window.cancelAnimationFrame = clearTimeout;
559 window['requestAnimationFrame'] = window.requestAnimationFrame;
560 window['cancelAnimationFrame'] = window.cancelAnimationFrame;
561 }
562 /**
563 * @license
564 * Copyright 2015 Google Inc. All Rights Reserved.
565 *
566 * Licensed under the Apache License, Version 2.0 (the "License");
567 * you may not use this file except in compliance with the License.
568 * You may obtain a copy of the License at
569 *
570 * http://www.apache.org/licenses/LICENSE-2.0
571 *
572 * Unless required by applicable law or agreed to in writing, software
573 * distributed under the License is distributed on an "AS IS" BASIS,
574 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
575 * See the License for the specific language governing permissions and
576 * limitations under the License.
577 */
578 /**
579 * Class constructor for Button MDL component.
580 * Implements MDL component design pattern defined at:
581 * https://github.com/jasonmayes/mdl-component-design-pattern
582 *
583 * @param {HTMLElement} element The element that will be upgraded.
584 */
585 var MaterialButton = function MaterialButton(element) {
586 this.element_ = element;
587 // Initialize instance.
588 this.init();
589 };
590 window['MaterialButton'] = MaterialButton;
591 /**
592 * Store constants in one place so they can be updated easily.
593 *
594 * @enum {string | number}
595 * @private
596 */
597 MaterialButton.prototype.Constant_ = {};
598 /**
599 * Store strings for class names defined by this component that are used in
600 * JavaScript. This allows us to simply change it in one place should we
601 * decide to modify at a later date.
602 *
603 * @enum {string}
604 * @private
605 */
606 MaterialButton.prototype.CssClasses_ = {
607 RIPPLE_EFFECT: 'mdl-js-ripple-effect',
608 RIPPLE_CONTAINER: 'mdl-button__ripple-container',
609 RIPPLE: 'mdl-ripple'
610 };
611 /**
612 * Handle blur of element.
613 *
614 * @param {Event} event The event that fired.
615 * @private
616 */
617 MaterialButton.prototype.blurHandler_ = function (event) {
618 if (event) {
619 this.element_.blur();
620 }
621 };
622// Public methods.
623 /**
624 * Disable button.
625 *
626 * @public
627 */
628 MaterialButton.prototype.disable = function () {
629 this.element_.disabled = true;
630 };
631 MaterialButton.prototype['disable'] = MaterialButton.prototype.disable;
632 /**
633 * Enable button.
634 *
635 * @public
636 */
637 MaterialButton.prototype.enable = function () {
638 this.element_.disabled = false;
639 };
640 MaterialButton.prototype['enable'] = MaterialButton.prototype.enable;
641 /**
642 * Initialize element.
643 */
644 MaterialButton.prototype.init = function () {
645 if (this.element_) {
646 if (this.element_.classList.contains(this.CssClasses_.RIPPLE_EFFECT)) {
647 var rippleContainer = document.createElement('span');
648 rippleContainer.classList.add(this.CssClasses_.RIPPLE_CONTAINER);
649 this.rippleElement_ = document.createElement('span');
650 this.rippleElement_.classList.add(this.CssClasses_.RIPPLE);
651 rippleContainer.appendChild(this.rippleElement_);
652 this.boundRippleBlurHandler = this.blurHandler_.bind(this);
653 this.rippleElement_.addEventListener('mouseup', this.boundRippleBlurHandler);
654 this.element_.appendChild(rippleContainer);
655 }
656 this.boundButtonBlurHandler = this.blurHandler_.bind(this);
657 this.element_.addEventListener('mouseup', this.boundButtonBlurHandler);
658 this.element_.addEventListener('mouseleave', this.boundButtonBlurHandler);
659 }
660 };
661// The component registers itself. It can assume componentHandler is available
662// in the global scope.
663 componentHandler.register({
664 constructor: MaterialButton,
665 classAsString: 'MaterialButton',
666 cssClass: 'mdl-js-button',
667 widget: true
668 });
669 /**
670 * @license
671 * Copyright 2015 Google Inc. All Rights Reserved.
672 *
673 * Licensed under the Apache License, Version 2.0 (the "License");
674 * you may not use this file except in compliance with the License.
675 * You may obtain a copy of the License at
676 *
677 * http://www.apache.org/licenses/LICENSE-2.0
678 *
679 * Unless required by applicable law or agreed to in writing, software
680 * distributed under the License is distributed on an "AS IS" BASIS,
681 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
682 * See the License for the specific language governing permissions and
683 * limitations under the License.
684 */
685 /**
686 * Class constructor for Checkbox MDL component.
687 * Implements MDL component design pattern defined at:
688 * https://github.com/jasonmayes/mdl-component-design-pattern
689 *
690 * @constructor
691 * @param {HTMLElement} element The element that will be upgraded.
692 */
693 var MaterialCheckbox = function MaterialCheckbox(element) {
694 this.element_ = element;
695 // Initialize instance.
696 this.init();
697 };
698 window['MaterialCheckbox'] = MaterialCheckbox;
699 /**
700 * Store constants in one place so they can be updated easily.
701 *
702 * @enum {string | number}
703 * @private
704 */
705 MaterialCheckbox.prototype.Constant_ = {TINY_TIMEOUT: 0.001};
706 /**
707 * Store strings for class names defined by this component that are used in
708 * JavaScript. This allows us to simply change it in one place should we
709 * decide to modify at a later date.
710 *
711 * @enum {string}
712 * @private
713 */
714 MaterialCheckbox.prototype.CssClasses_ = {
715 INPUT: 'mdl-checkbox__input',
716 BOX_OUTLINE: 'mdl-checkbox__box-outline',
717 FOCUS_HELPER: 'mdl-checkbox__focus-helper',
718 TICK_OUTLINE: 'mdl-checkbox__tick-outline',
719 RIPPLE_EFFECT: 'mdl-js-ripple-effect',
720 RIPPLE_IGNORE_EVENTS: 'mdl-js-ripple-effect--ignore-events',
721 RIPPLE_CONTAINER: 'mdl-checkbox__ripple-container',
722 RIPPLE_CENTER: 'mdl-ripple--center',
723 RIPPLE: 'mdl-ripple',
724 IS_FOCUSED: 'is-focused',
725 IS_DISABLED: 'is-disabled',
726 IS_CHECKED: 'is-checked',
727 IS_UPGRADED: 'is-upgraded'
728 };
729 /**
730 * Handle change of state.
731 *
732 * @param {Event} event The event that fired.
733 * @private
734 */
735 MaterialCheckbox.prototype.onChange_ = function (event) {
736 this.updateClasses_();
737 };
738 /**
739 * Handle focus of element.
740 *
741 * @param {Event} event The event that fired.
742 * @private
743 */
744 MaterialCheckbox.prototype.onFocus_ = function (event) {
745 this.element_.classList.add(this.CssClasses_.IS_FOCUSED);
746 };
747 /**
748 * Handle lost focus of element.
749 *
750 * @param {Event} event The event that fired.
751 * @private
752 */
753 MaterialCheckbox.prototype.onBlur_ = function (event) {
754 this.element_.classList.remove(this.CssClasses_.IS_FOCUSED);
755 };
756 /**
757 * Handle mouseup.
758 *
759 * @param {Event} event The event that fired.
760 * @private
761 */
762 MaterialCheckbox.prototype.onMouseUp_ = function (event) {
763 this.blur_();
764 };
765 /**
766 * Handle class updates.
767 *
768 * @private
769 */
770 MaterialCheckbox.prototype.updateClasses_ = function () {
771 this.checkDisabled();
772 this.checkToggleState();
773 };
774 /**
775 * Add blur.
776 *
777 * @private
778 */
779 MaterialCheckbox.prototype.blur_ = function () {
780 // TODO: figure out why there's a focus event being fired after our blur,
781 // so that we can avoid this hack.
782 window.setTimeout(function () {
783 this.inputElement_.blur();
784 }.bind(this), this.Constant_.TINY_TIMEOUT);
785 };
786// Public methods.
787 /**
788 * Check the inputs toggle state and update display.
789 *
790 * @public
791 */
792 MaterialCheckbox.prototype.checkToggleState = function () {
793 if (this.inputElement_.checked) {
794 this.element_.classList.add(this.CssClasses_.IS_CHECKED);
795 } else {
796 this.element_.classList.remove(this.CssClasses_.IS_CHECKED);
797 }
798 };
799 MaterialCheckbox.prototype['checkToggleState'] = MaterialCheckbox.prototype.checkToggleState;
800 /**
801 * Check the inputs disabled state and update display.
802 *
803 * @public
804 */
805 MaterialCheckbox.prototype.checkDisabled = function () {
806 if (this.inputElement_.disabled) {
807 this.element_.classList.add(this.CssClasses_.IS_DISABLED);
808 } else {
809 this.element_.classList.remove(this.CssClasses_.IS_DISABLED);
810 }
811 };
812 MaterialCheckbox.prototype['checkDisabled'] = MaterialCheckbox.prototype.checkDisabled;
813 /**
814 * Disable checkbox.
815 *
816 * @public
817 */
818 MaterialCheckbox.prototype.disable = function () {
819 this.inputElement_.disabled = true;
820 this.updateClasses_();
821 };
822 MaterialCheckbox.prototype['disable'] = MaterialCheckbox.prototype.disable;
823 /**
824 * Enable checkbox.
825 *
826 * @public
827 */
828 MaterialCheckbox.prototype.enable = function () {
829 this.inputElement_.disabled = false;
830 this.updateClasses_();
831 };
832 MaterialCheckbox.prototype['enable'] = MaterialCheckbox.prototype.enable;
833 /**
834 * Check checkbox.
835 *
836 * @public
837 */
838 MaterialCheckbox.prototype.check = function () {
839 this.inputElement_.checked = true;
840 this.updateClasses_();
841 };
842 MaterialCheckbox.prototype['check'] = MaterialCheckbox.prototype.check;
843 /**
844 * Uncheck checkbox.
845 *
846 * @public
847 */
848 MaterialCheckbox.prototype.uncheck = function () {
849 this.inputElement_.checked = false;
850 this.updateClasses_();
851 };
852 MaterialCheckbox.prototype['uncheck'] = MaterialCheckbox.prototype.uncheck;
853 /**
854 * Initialize element.
855 */
856 MaterialCheckbox.prototype.init = function () {
857 if (this.element_) {
858 this.inputElement_ = this.element_.querySelector('.' + this.CssClasses_.INPUT);
859 var boxOutline = document.createElement('span');
860 boxOutline.classList.add(this.CssClasses_.BOX_OUTLINE);
861 var tickContainer = document.createElement('span');
862 tickContainer.classList.add(this.CssClasses_.FOCUS_HELPER);
863 var tickOutline = document.createElement('span');
864 tickOutline.classList.add(this.CssClasses_.TICK_OUTLINE);
865 boxOutline.appendChild(tickOutline);
866 this.element_.appendChild(tickContainer);
867 this.element_.appendChild(boxOutline);
868 if (this.element_.classList.contains(this.CssClasses_.RIPPLE_EFFECT)) {
869 this.element_.classList.add(this.CssClasses_.RIPPLE_IGNORE_EVENTS);
870 this.rippleContainerElement_ = document.createElement('span');
871 this.rippleContainerElement_.classList.add(this.CssClasses_.RIPPLE_CONTAINER);
872 this.rippleContainerElement_.classList.add(this.CssClasses_.RIPPLE_EFFECT);
873 this.rippleContainerElement_.classList.add(this.CssClasses_.RIPPLE_CENTER);
874 this.boundRippleMouseUp = this.onMouseUp_.bind(this);
875 this.rippleContainerElement_.addEventListener('mouseup', this.boundRippleMouseUp);
876 var ripple = document.createElement('span');
877 ripple.classList.add(this.CssClasses_.RIPPLE);
878 this.rippleContainerElement_.appendChild(ripple);
879 this.element_.appendChild(this.rippleContainerElement_);
880 }
881 this.boundInputOnChange = this.onChange_.bind(this);
882 this.boundInputOnFocus = this.onFocus_.bind(this);
883 this.boundInputOnBlur = this.onBlur_.bind(this);
884 this.boundElementMouseUp = this.onMouseUp_.bind(this);
885 this.inputElement_.addEventListener('change', this.boundInputOnChange);
886 this.inputElement_.addEventListener('focus', this.boundInputOnFocus);
887 this.inputElement_.addEventListener('blur', this.boundInputOnBlur);
888 this.element_.addEventListener('mouseup', this.boundElementMouseUp);
889 this.updateClasses_();
890 this.element_.classList.add(this.CssClasses_.IS_UPGRADED);
891 }
892 };
893// The component registers itself. It can assume componentHandler is available
894// in the global scope.
895 componentHandler.register({
896 constructor: MaterialCheckbox,
897 classAsString: 'MaterialCheckbox',
898 cssClass: 'mdl-js-checkbox',
899 widget: true
900 });
901 /**
902 * @license
903 * Copyright 2015 Google Inc. All Rights Reserved.
904 *
905 * Licensed under the Apache License, Version 2.0 (the "License");
906 * you may not use this file except in compliance with the License.
907 * You may obtain a copy of the License at
908 *
909 * http://www.apache.org/licenses/LICENSE-2.0
910 *
911 * Unless required by applicable law or agreed to in writing, software
912 * distributed under the License is distributed on an "AS IS" BASIS,
913 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
914 * See the License for the specific language governing permissions and
915 * limitations under the License.
916 */
917 /**
918 * Class constructor for icon toggle MDL component.
919 * Implements MDL component design pattern defined at:
920 * https://github.com/jasonmayes/mdl-component-design-pattern
921 *
922 * @constructor
923 * @param {HTMLElement} element The element that will be upgraded.
924 */
925 var MaterialIconToggle = function MaterialIconToggle(element) {
926 this.element_ = element;
927 // Initialize instance.
928 this.init();
929 };
930 window['MaterialIconToggle'] = MaterialIconToggle;
931 /**
932 * Store constants in one place so they can be updated easily.
933 *
934 * @enum {string | number}
935 * @private
936 */
937 MaterialIconToggle.prototype.Constant_ = {TINY_TIMEOUT: 0.001};
938 /**
939 * Store strings for class names defined by this component that are used in
940 * JavaScript. This allows us to simply change it in one place should we
941 * decide to modify at a later date.
942 *
943 * @enum {string}
944 * @private
945 */
946 MaterialIconToggle.prototype.CssClasses_ = {
947 INPUT: 'mdl-icon-toggle__input',
948 JS_RIPPLE_EFFECT: 'mdl-js-ripple-effect',
949 RIPPLE_IGNORE_EVENTS: 'mdl-js-ripple-effect--ignore-events',
950 RIPPLE_CONTAINER: 'mdl-icon-toggle__ripple-container',
951 RIPPLE_CENTER: 'mdl-ripple--center',
952 RIPPLE: 'mdl-ripple',
953 IS_FOCUSED: 'is-focused',
954 IS_DISABLED: 'is-disabled',
955 IS_CHECKED: 'is-checked'
956 };
957 /**
958 * Handle change of state.
959 *
960 * @param {Event} event The event that fired.
961 * @private
962 */
963 MaterialIconToggle.prototype.onChange_ = function (event) {
964 this.updateClasses_();
965 };
966 /**
967 * Handle focus of element.
968 *
969 * @param {Event} event The event that fired.
970 * @private
971 */
972 MaterialIconToggle.prototype.onFocus_ = function (event) {
973 this.element_.classList.add(this.CssClasses_.IS_FOCUSED);
974 };
975 /**
976 * Handle lost focus of element.
977 *
978 * @param {Event} event The event that fired.
979 * @private
980 */
981 MaterialIconToggle.prototype.onBlur_ = function (event) {
982 this.element_.classList.remove(this.CssClasses_.IS_FOCUSED);
983 };
984 /**
985 * Handle mouseup.
986 *
987 * @param {Event} event The event that fired.
988 * @private
989 */
990 MaterialIconToggle.prototype.onMouseUp_ = function (event) {
991 this.blur_();
992 };
993 /**
994 * Handle class updates.
995 *
996 * @private
997 */
998 MaterialIconToggle.prototype.updateClasses_ = function () {
999 this.checkDisabled();
1000 this.checkToggleState();
1001 };
1002 /**
1003 * Add blur.
1004 *
1005 * @private
1006 */
1007 MaterialIconToggle.prototype.blur_ = function () {
1008 // TODO: figure out why there's a focus event being fired after our blur,
1009 // so that we can avoid this hack.
1010 window.setTimeout(function () {
1011 this.inputElement_.blur();
1012 }.bind(this), this.Constant_.TINY_TIMEOUT);
1013 };
1014// Public methods.
1015 /**
1016 * Check the inputs toggle state and update display.
1017 *
1018 * @public
1019 */
1020 MaterialIconToggle.prototype.checkToggleState = function () {
1021 if (this.inputElement_.checked) {
1022 this.element_.classList.add(this.CssClasses_.IS_CHECKED);
1023 } else {
1024 this.element_.classList.remove(this.CssClasses_.IS_CHECKED);
1025 }
1026 };
1027 MaterialIconToggle.prototype['checkToggleState'] = MaterialIconToggle.prototype.checkToggleState;
1028 /**
1029 * Check the inputs disabled state and update display.
1030 *
1031 * @public
1032 */
1033 MaterialIconToggle.prototype.checkDisabled = function () {
1034 if (this.inputElement_.disabled) {
1035 this.element_.classList.add(this.CssClasses_.IS_DISABLED);
1036 } else {
1037 this.element_.classList.remove(this.CssClasses_.IS_DISABLED);
1038 }
1039 };
1040 MaterialIconToggle.prototype['checkDisabled'] = MaterialIconToggle.prototype.checkDisabled;
1041 /**
1042 * Disable icon toggle.
1043 *
1044 * @public
1045 */
1046 MaterialIconToggle.prototype.disable = function () {
1047 this.inputElement_.disabled = true;
1048 this.updateClasses_();
1049 };
1050 MaterialIconToggle.prototype['disable'] = MaterialIconToggle.prototype.disable;
1051 /**
1052 * Enable icon toggle.
1053 *
1054 * @public
1055 */
1056 MaterialIconToggle.prototype.enable = function () {
1057 this.inputElement_.disabled = false;
1058 this.updateClasses_();
1059 };
1060 MaterialIconToggle.prototype['enable'] = MaterialIconToggle.prototype.enable;
1061 /**
1062 * Check icon toggle.
1063 *
1064 * @public
1065 */
1066 MaterialIconToggle.prototype.check = function () {
1067 this.inputElement_.checked = true;
1068 this.updateClasses_();
1069 };
1070 MaterialIconToggle.prototype['check'] = MaterialIconToggle.prototype.check;
1071 /**
1072 * Uncheck icon toggle.
1073 *
1074 * @public
1075 */
1076 MaterialIconToggle.prototype.uncheck = function () {
1077 this.inputElement_.checked = false;
1078 this.updateClasses_();
1079 };
1080 MaterialIconToggle.prototype['uncheck'] = MaterialIconToggle.prototype.uncheck;
1081 /**
1082 * Initialize element.
1083 */
1084 MaterialIconToggle.prototype.init = function () {
1085 if (this.element_) {
1086 this.inputElement_ = this.element_.querySelector('.' + this.CssClasses_.INPUT);
1087 if (this.element_.classList.contains(this.CssClasses_.JS_RIPPLE_EFFECT)) {
1088 this.element_.classList.add(this.CssClasses_.RIPPLE_IGNORE_EVENTS);
1089 this.rippleContainerElement_ = document.createElement('span');
1090 this.rippleContainerElement_.classList.add(this.CssClasses_.RIPPLE_CONTAINER);
1091 this.rippleContainerElement_.classList.add(this.CssClasses_.JS_RIPPLE_EFFECT);
1092 this.rippleContainerElement_.classList.add(this.CssClasses_.RIPPLE_CENTER);
1093 this.boundRippleMouseUp = this.onMouseUp_.bind(this);
1094 this.rippleContainerElement_.addEventListener('mouseup', this.boundRippleMouseUp);
1095 var ripple = document.createElement('span');
1096 ripple.classList.add(this.CssClasses_.RIPPLE);
1097 this.rippleContainerElement_.appendChild(ripple);
1098 this.element_.appendChild(this.rippleContainerElement_);
1099 }
1100 this.boundInputOnChange = this.onChange_.bind(this);
1101 this.boundInputOnFocus = this.onFocus_.bind(this);
1102 this.boundInputOnBlur = this.onBlur_.bind(this);
1103 this.boundElementOnMouseUp = this.onMouseUp_.bind(this);
1104 this.inputElement_.addEventListener('change', this.boundInputOnChange);
1105 this.inputElement_.addEventListener('focus', this.boundInputOnFocus);
1106 this.inputElement_.addEventListener('blur', this.boundInputOnBlur);
1107 this.element_.addEventListener('mouseup', this.boundElementOnMouseUp);
1108 this.updateClasses_();
1109 this.element_.classList.add('is-upgraded');
1110 }
1111 };
1112// The component registers itself. It can assume componentHandler is available
1113// in the global scope.
1114 componentHandler.register({
1115 constructor: MaterialIconToggle,
1116 classAsString: 'MaterialIconToggle',
1117 cssClass: 'mdl-js-icon-toggle',
1118 widget: true
1119 });
1120 /**
1121 * @license
1122 * Copyright 2015 Google Inc. All Rights Reserved.
1123 *
1124 * Licensed under the Apache License, Version 2.0 (the "License");
1125 * you may not use this file except in compliance with the License.
1126 * You may obtain a copy of the License at
1127 *
1128 * http://www.apache.org/licenses/LICENSE-2.0
1129 *
1130 * Unless required by applicable law or agreed to in writing, software
1131 * distributed under the License is distributed on an "AS IS" BASIS,
1132 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1133 * See the License for the specific language governing permissions and
1134 * limitations under the License.
1135 */
1136 /**
1137 * Class constructor for dropdown MDL component.
1138 * Implements MDL component design pattern defined at:
1139 * https://github.com/jasonmayes/mdl-component-design-pattern
1140 *
1141 * @constructor
1142 * @param {HTMLElement} element The element that will be upgraded.
1143 */
1144 var MaterialMenu = function MaterialMenu(element) {
1145 this.element_ = element;
1146 // Initialize instance.
1147 this.init();
1148 };
1149 window['MaterialMenu'] = MaterialMenu;
1150 /**
1151 * Store constants in one place so they can be updated easily.
1152 *
1153 * @enum {string | number}
1154 * @private
1155 */
1156 MaterialMenu.prototype.Constant_ = {
1157 // Total duration of the menu animation.
1158 TRANSITION_DURATION_SECONDS: 0.3,
1159 // The fraction of the total duration we want to use for menu item animations.
1160 TRANSITION_DURATION_FRACTION: 0.8,
1161 // How long the menu stays open after choosing an option (so the user can see
1162 // the ripple).
1163 CLOSE_TIMEOUT: 150
1164 };
1165 /**
1166 * Keycodes, for code readability.
1167 *
1168 * @enum {number}
1169 * @private
1170 */
1171 MaterialMenu.prototype.Keycodes_ = {
1172 ENTER: 13,
1173 ESCAPE: 27,
1174 SPACE: 32,
1175 UP_ARROW: 38,
1176 DOWN_ARROW: 40
1177 };
1178 /**
1179 * Store strings for class names defined by this component that are used in
1180 * JavaScript. This allows us to simply change it in one place should we
1181 * decide to modify at a later date.
1182 *
1183 * @enum {string}
1184 * @private
1185 */
1186 MaterialMenu.prototype.CssClasses_ = {
1187 CONTAINER: 'mdl-menu__container',
1188 OUTLINE: 'mdl-menu__outline',
1189 ITEM: 'mdl-menu__item',
1190 ITEM_RIPPLE_CONTAINER: 'mdl-menu__item-ripple-container',
1191 RIPPLE_EFFECT: 'mdl-js-ripple-effect',
1192 RIPPLE_IGNORE_EVENTS: 'mdl-js-ripple-effect--ignore-events',
1193 RIPPLE: 'mdl-ripple',
1194 // Statuses
1195 IS_UPGRADED: 'is-upgraded',
1196 IS_VISIBLE: 'is-visible',
1197 IS_ANIMATING: 'is-animating',
1198 // Alignment options
1199 BOTTOM_LEFT: 'mdl-menu--bottom-left',
1200 // This is the default.
1201 BOTTOM_RIGHT: 'mdl-menu--bottom-right',
1202 TOP_LEFT: 'mdl-menu--top-left',
1203 TOP_RIGHT: 'mdl-menu--top-right',
1204 UNALIGNED: 'mdl-menu--unaligned'
1205 };
1206 /**
1207 * Initialize element.
1208 */
1209 MaterialMenu.prototype.init = function () {
1210 if (this.element_) {
1211 // Create container for the menu.
1212 var container = document.createElement('div');
1213 container.classList.add(this.CssClasses_.CONTAINER);
1214 this.element_.parentElement.insertBefore(container, this.element_);
1215 this.element_.parentElement.removeChild(this.element_);
1216 container.appendChild(this.element_);
1217 this.container_ = container;
1218 // Create outline for the menu (shadow and background).
1219 var outline = document.createElement('div');
1220 outline.classList.add(this.CssClasses_.OUTLINE);
1221 this.outline_ = outline;
1222 container.insertBefore(outline, this.element_);
1223 // Find the "for" element and bind events to it.
1224 var forElId = this.element_.getAttribute('for') || this.element_.getAttribute('data-mdl-for');
1225 var forEl = null;
1226 if (forElId) {
1227 forEl = document.getElementById(forElId);
1228 if (forEl) {
1229 this.forElement_ = forEl;
1230 forEl.addEventListener('click', this.handleForClick_.bind(this));
1231 forEl.addEventListener('keydown', this.handleForKeyboardEvent_.bind(this));
1232 }
1233 }
1234 var items = this.element_.querySelectorAll('.' + this.CssClasses_.ITEM);
1235 this.boundItemKeydown_ = this.handleItemKeyboardEvent_.bind(this);
1236 this.boundItemClick_ = this.handleItemClick_.bind(this);
1237 for (var i = 0; i < items.length; i++) {
1238 // Add a listener to each menu item.
1239 items[i].addEventListener('click', this.boundItemClick_);
1240 // Add a tab index to each menu item.
1241 items[i].tabIndex = '-1';
1242 // Add a keyboard listener to each menu item.
1243 items[i].addEventListener('keydown', this.boundItemKeydown_);
1244 }
1245 // Add ripple classes to each item, if the user has enabled ripples.
1246 if (this.element_.classList.contains(this.CssClasses_.RIPPLE_EFFECT)) {
1247 this.element_.classList.add(this.CssClasses_.RIPPLE_IGNORE_EVENTS);
1248 for (i = 0; i < items.length; i++) {
1249 var item = items[i];
1250 var rippleContainer = document.createElement('span');
1251 rippleContainer.classList.add(this.CssClasses_.ITEM_RIPPLE_CONTAINER);
1252 var ripple = document.createElement('span');
1253 ripple.classList.add(this.CssClasses_.RIPPLE);
1254 rippleContainer.appendChild(ripple);
1255 item.appendChild(rippleContainer);
1256 item.classList.add(this.CssClasses_.RIPPLE_EFFECT);
1257 }
1258 }
1259 // Copy alignment classes to the container, so the outline can use them.
1260 if (this.element_.classList.contains(this.CssClasses_.BOTTOM_LEFT)) {
1261 this.outline_.classList.add(this.CssClasses_.BOTTOM_LEFT);
1262 }
1263 if (this.element_.classList.contains(this.CssClasses_.BOTTOM_RIGHT)) {
1264 this.outline_.classList.add(this.CssClasses_.BOTTOM_RIGHT);
1265 }
1266 if (this.element_.classList.contains(this.CssClasses_.TOP_LEFT)) {
1267 this.outline_.classList.add(this.CssClasses_.TOP_LEFT);
1268 }
1269 if (this.element_.classList.contains(this.CssClasses_.TOP_RIGHT)) {
1270 this.outline_.classList.add(this.CssClasses_.TOP_RIGHT);
1271 }
1272 if (this.element_.classList.contains(this.CssClasses_.UNALIGNED)) {
1273 this.outline_.classList.add(this.CssClasses_.UNALIGNED);
1274 }
1275 container.classList.add(this.CssClasses_.IS_UPGRADED);
1276 }
1277 };
1278 /**
1279 * Handles a click on the "for" element, by positioning the menu and then
1280 * toggling it.
1281 *
1282 * @param {Event} evt The event that fired.
1283 * @private
1284 */
1285 MaterialMenu.prototype.handleForClick_ = function (evt) {
1286 if (this.element_ && this.forElement_) {
1287 var rect = this.forElement_.getBoundingClientRect();
1288 var forRect = this.forElement_.parentElement.getBoundingClientRect();
1289 if (this.element_.classList.contains(this.CssClasses_.UNALIGNED)) {
1290 } else if (this.element_.classList.contains(this.CssClasses_.BOTTOM_RIGHT)) {
1291 // Position below the "for" element, aligned to its right.
1292 this.container_.style.right = forRect.right - rect.right + 'px';
1293 this.container_.style.top = this.forElement_.offsetTop + this.forElement_.offsetHeight + 'px';
1294 } else if (this.element_.classList.contains(this.CssClasses_.TOP_LEFT)) {
1295 // Position above the "for" element, aligned to its left.
1296 this.container_.style.left = this.forElement_.offsetLeft + 'px';
1297 this.container_.style.bottom = forRect.bottom - rect.top + 'px';
1298 } else if (this.element_.classList.contains(this.CssClasses_.TOP_RIGHT)) {
1299 // Position above the "for" element, aligned to its right.
1300 this.container_.style.right = forRect.right - rect.right + 'px';
1301 this.container_.style.bottom = forRect.bottom - rect.top + 'px';
1302 } else {
1303 // Default: position below the "for" element, aligned to its left.
1304 this.container_.style.left = this.forElement_.offsetLeft + 'px';
1305 this.container_.style.top = this.forElement_.offsetTop + this.forElement_.offsetHeight + 'px';
1306 }
1307 }
1308 this.toggle(evt);
1309 };
1310 /**
1311 * Handles a keyboard event on the "for" element.
1312 *
1313 * @param {Event} evt The event that fired.
1314 * @private
1315 */
1316 MaterialMenu.prototype.handleForKeyboardEvent_ = function (evt) {
1317 if (this.element_ && this.container_ && this.forElement_) {
1318 var items = this.element_.querySelectorAll('.' + this.CssClasses_.ITEM + ':not([disabled])');
1319 if (items && items.length > 0 && this.container_.classList.contains(this.CssClasses_.IS_VISIBLE)) {
1320 if (evt.keyCode === this.Keycodes_.UP_ARROW) {
1321 evt.preventDefault();
1322 items[items.length - 1].focus();
1323 } else if (evt.keyCode === this.Keycodes_.DOWN_ARROW) {
1324 evt.preventDefault();
1325 items[0].focus();
1326 }
1327 }
1328 }
1329 };
1330 /**
1331 * Handles a keyboard event on an item.
1332 *
1333 * @param {Event} evt The event that fired.
1334 * @private
1335 */
1336 MaterialMenu.prototype.handleItemKeyboardEvent_ = function (evt) {
1337 if (this.element_ && this.container_) {
1338 var items = this.element_.querySelectorAll('.' + this.CssClasses_.ITEM + ':not([disabled])');
1339 if (items && items.length > 0 && this.container_.classList.contains(this.CssClasses_.IS_VISIBLE)) {
1340 var currentIndex = Array.prototype.slice.call(items).indexOf(evt.target);
1341 if (evt.keyCode === this.Keycodes_.UP_ARROW) {
1342 evt.preventDefault();
1343 if (currentIndex > 0) {
1344 items[currentIndex - 1].focus();
1345 } else {
1346 items[items.length - 1].focus();
1347 }
1348 } else if (evt.keyCode === this.Keycodes_.DOWN_ARROW) {
1349 evt.preventDefault();
1350 if (items.length > currentIndex + 1) {
1351 items[currentIndex + 1].focus();
1352 } else {
1353 items[0].focus();
1354 }
1355 } else if (evt.keyCode === this.Keycodes_.SPACE || evt.keyCode === this.Keycodes_.ENTER) {
1356 evt.preventDefault();
1357 // Send mousedown and mouseup to trigger ripple.
1358 var e = new MouseEvent('mousedown');
1359 evt.target.dispatchEvent(e);
1360 e = new MouseEvent('mouseup');
1361 evt.target.dispatchEvent(e);
1362 // Send click.
1363 evt.target.click();
1364 } else if (evt.keyCode === this.Keycodes_.ESCAPE) {
1365 evt.preventDefault();
1366 this.hide();
1367 }
1368 }
1369 }
1370 };
1371 /**
1372 * Handles a click event on an item.
1373 *
1374 * @param {Event} evt The event that fired.
1375 * @private
1376 */
1377 MaterialMenu.prototype.handleItemClick_ = function (evt) {
1378 if (evt.target.hasAttribute('disabled')) {
1379 evt.stopPropagation();
1380 } else {
1381 // Wait some time before closing menu, so the user can see the ripple.
1382 this.closing_ = true;
1383 window.setTimeout(function (evt) {
1384 this.hide();
1385 this.closing_ = false;
1386 }.bind(this), this.Constant_.CLOSE_TIMEOUT);
1387 }
1388 };
1389 /**
1390 * Calculates the initial clip (for opening the menu) or final clip (for closing
1391 * it), and applies it. This allows us to animate from or to the correct point,
1392 * that is, the point it's aligned to in the "for" element.
1393 *
1394 * @param {number} height Height of the clip rectangle
1395 * @param {number} width Width of the clip rectangle
1396 * @private
1397 */
1398 MaterialMenu.prototype.applyClip_ = function (height, width) {
1399 if (this.element_.classList.contains(this.CssClasses_.UNALIGNED)) {
1400 // Do not clip.
1401 this.element_.style.clip = '';
1402 } else if (this.element_.classList.contains(this.CssClasses_.BOTTOM_RIGHT)) {
1403 // Clip to the top right corner of the menu.
1404 this.element_.style.clip = 'rect(0 ' + width + 'px ' + '0 ' + width + 'px)';
1405 } else if (this.element_.classList.contains(this.CssClasses_.TOP_LEFT)) {
1406 // Clip to the bottom left corner of the menu.
1407 this.element_.style.clip = 'rect(' + height + 'px 0 ' + height + 'px 0)';
1408 } else if (this.element_.classList.contains(this.CssClasses_.TOP_RIGHT)) {
1409 // Clip to the bottom right corner of the menu.
1410 this.element_.style.clip = 'rect(' + height + 'px ' + width + 'px ' + height + 'px ' + width + 'px)';
1411 } else {
1412 // Default: do not clip (same as clipping to the top left corner).
1413 this.element_.style.clip = '';
1414 }
1415 };
1416 /**
1417 * Cleanup function to remove animation listeners.
1418 *
1419 * @param {Event} evt
1420 * @private
1421 */
1422 MaterialMenu.prototype.removeAnimationEndListener_ = function (evt) {
1423 evt.target.classList.remove(MaterialMenu.prototype.CssClasses_.IS_ANIMATING);
1424 };
1425 /**
1426 * Adds an event listener to clean up after the animation ends.
1427 *
1428 * @private
1429 */
1430 MaterialMenu.prototype.addAnimationEndListener_ = function () {
1431 this.element_.addEventListener('transitionend', this.removeAnimationEndListener_);
1432 this.element_.addEventListener('webkitTransitionEnd', this.removeAnimationEndListener_);
1433 };
1434 /**
1435 * Displays the menu.
1436 *
1437 * @public
1438 */
1439 MaterialMenu.prototype.show = function (evt) {
1440 if (this.element_ && this.container_ && this.outline_) {
1441 // Measure the inner element.
1442 var height = this.element_.getBoundingClientRect().height;
1443 var width = this.element_.getBoundingClientRect().width;
1444 // Apply the inner element's size to the container and outline.
1445 this.container_.style.width = width + 'px';
1446 this.container_.style.height = height + 'px';
1447 this.outline_.style.width = width + 'px';
1448 this.outline_.style.height = height + 'px';
1449 var transitionDuration = this.Constant_.TRANSITION_DURATION_SECONDS * this.Constant_.TRANSITION_DURATION_FRACTION;
1450 // Calculate transition delays for individual menu items, so that they fade
1451 // in one at a time.
1452 var items = this.element_.querySelectorAll('.' + this.CssClasses_.ITEM);
1453 for (var i = 0; i < items.length; i++) {
1454 var itemDelay = null;
1455 if (this.element_.classList.contains(this.CssClasses_.TOP_LEFT) || this.element_.classList.contains(this.CssClasses_.TOP_RIGHT)) {
1456 itemDelay = (height - items[i].offsetTop - items[i].offsetHeight) / height * transitionDuration + 's';
1457 } else {
1458 itemDelay = items[i].offsetTop / height * transitionDuration + 's';
1459 }
1460 items[i].style.transitionDelay = itemDelay;
1461 }
1462 // Apply the initial clip to the text before we start animating.
1463 this.applyClip_(height, width);
1464 // Wait for the next frame, turn on animation, and apply the final clip.
1465 // Also make it visible. This triggers the transitions.
1466 window.requestAnimationFrame(function () {
1467 this.element_.classList.add(this.CssClasses_.IS_ANIMATING);
1468 this.element_.style.clip = 'rect(0 ' + width + 'px ' + height + 'px 0)';
1469 this.container_.classList.add(this.CssClasses_.IS_VISIBLE);
1470 }.bind(this));
1471 // Clean up after the animation is complete.
1472 this.addAnimationEndListener_();
1473 // Add a click listener to the document, to close the menu.
1474 var callback = function (e) {
1475 // Check to see if the document is processing the same event that
1476 // displayed the menu in the first place. If so, do nothing.
1477 // Also check to see if the menu is in the process of closing itself, and
1478 // do nothing in that case.
1479 // Also check if the clicked element is a menu item
1480 // if so, do nothing.
1481 if (e !== evt && !this.closing_ && e.target.parentNode !== this.element_) {
1482 document.removeEventListener('click', callback);
1483 this.hide();
1484 }
1485 }.bind(this);
1486 document.addEventListener('click', callback);
1487 }
1488 };
1489 MaterialMenu.prototype['show'] = MaterialMenu.prototype.show;
1490 /**
1491 * Hides the menu.
1492 *
1493 * @public
1494 */
1495 MaterialMenu.prototype.hide = function () {
1496 if (this.element_ && this.container_ && this.outline_) {
1497 var items = this.element_.querySelectorAll('.' + this.CssClasses_.ITEM);
1498 // Remove all transition delays; menu items fade out concurrently.
1499 for (var i = 0; i < items.length; i++) {
1500 items[i].style.removeProperty('transition-delay');
1501 }
1502 // Measure the inner element.
1503 var rect = this.element_.getBoundingClientRect();
1504 var height = rect.height;
1505 var width = rect.width;
1506 // Turn on animation, and apply the final clip. Also make invisible.
1507 // This triggers the transitions.
1508 this.element_.classList.add(this.CssClasses_.IS_ANIMATING);
1509 this.applyClip_(height, width);
1510 this.container_.classList.remove(this.CssClasses_.IS_VISIBLE);
1511 // Clean up after the animation is complete.
1512 this.addAnimationEndListener_();
1513 }
1514 };
1515 MaterialMenu.prototype['hide'] = MaterialMenu.prototype.hide;
1516 /**
1517 * Displays or hides the menu, depending on current state.
1518 *
1519 * @public
1520 */
1521 MaterialMenu.prototype.toggle = function (evt) {
1522 if (this.container_.classList.contains(this.CssClasses_.IS_VISIBLE)) {
1523 this.hide();
1524 } else {
1525 this.show(evt);
1526 }
1527 };
1528 MaterialMenu.prototype['toggle'] = MaterialMenu.prototype.toggle;
1529// The component registers itself. It can assume componentHandler is available
1530// in the global scope.
1531 componentHandler.register({
1532 constructor: MaterialMenu,
1533 classAsString: 'MaterialMenu',
1534 cssClass: 'mdl-js-menu',
1535 widget: true
1536 });
1537 /**
1538 * @license
1539 * Copyright 2015 Google Inc. All Rights Reserved.
1540 *
1541 * Licensed under the Apache License, Version 2.0 (the "License");
1542 * you may not use this file except in compliance with the License.
1543 * You may obtain a copy of the License at
1544 *
1545 * http://www.apache.org/licenses/LICENSE-2.0
1546 *
1547 * Unless required by applicable law or agreed to in writing, software
1548 * distributed under the License is distributed on an "AS IS" BASIS,
1549 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1550 * See the License for the specific language governing permissions and
1551 * limitations under the License.
1552 */
1553 /**
1554 * Class constructor for Progress MDL component.
1555 * Implements MDL component design pattern defined at:
1556 * https://github.com/jasonmayes/mdl-component-design-pattern
1557 *
1558 * @constructor
1559 * @param {HTMLElement} element The element that will be upgraded.
1560 */
1561 var MaterialProgress = function MaterialProgress(element) {
1562 this.element_ = element;
1563 // Initialize instance.
1564 this.init();
1565 };
1566 window['MaterialProgress'] = MaterialProgress;
1567 /**
1568 * Store constants in one place so they can be updated easily.
1569 *
1570 * @enum {string | number}
1571 * @private
1572 */
1573 MaterialProgress.prototype.Constant_ = {};
1574 /**
1575 * Store strings for class names defined by this component that are used in
1576 * JavaScript. This allows us to simply change it in one place should we
1577 * decide to modify at a later date.
1578 *
1579 * @enum {string}
1580 * @private
1581 */
1582 MaterialProgress.prototype.CssClasses_ = {INDETERMINATE_CLASS: 'mdl-progress__indeterminate'};
1583 /**
1584 * Set the current progress of the progressbar.
1585 *
1586 * @param {number} p Percentage of the progress (0-100)
1587 * @public
1588 */
1589 MaterialProgress.prototype.setProgress = function (p) {
1590 if (this.element_.classList.contains(this.CssClasses_.INDETERMINATE_CLASS)) {
1591 return;
1592 }
1593 this.progressbar_.style.width = p + '%';
1594 };
1595 MaterialProgress.prototype['setProgress'] = MaterialProgress.prototype.setProgress;
1596 /**
1597 * Set the current progress of the buffer.
1598 *
1599 * @param {number} p Percentage of the buffer (0-100)
1600 * @public
1601 */
1602 MaterialProgress.prototype.setBuffer = function (p) {
1603 this.bufferbar_.style.width = p + '%';
1604 this.auxbar_.style.width = 100 - p + '%';
1605 };
1606 MaterialProgress.prototype['setBuffer'] = MaterialProgress.prototype.setBuffer;
1607 /**
1608 * Initialize element.
1609 */
1610 MaterialProgress.prototype.init = function () {
1611 if (this.element_) {
1612 var el = document.createElement('div');
1613 el.className = 'progressbar bar bar1';
1614 this.element_.appendChild(el);
1615 this.progressbar_ = el;
1616 el = document.createElement('div');
1617 el.className = 'bufferbar bar bar2';
1618 this.element_.appendChild(el);
1619 this.bufferbar_ = el;
1620 el = document.createElement('div');
1621 el.className = 'auxbar bar bar3';
1622 this.element_.appendChild(el);
1623 this.auxbar_ = el;
1624 this.progressbar_.style.width = '0%';
1625 this.bufferbar_.style.width = '100%';
1626 this.auxbar_.style.width = '0%';
1627 this.element_.classList.add('is-upgraded');
1628 }
1629 };
1630// The component registers itself. It can assume componentHandler is available
1631// in the global scope.
1632 componentHandler.register({
1633 constructor: MaterialProgress,
1634 classAsString: 'MaterialProgress',
1635 cssClass: 'mdl-js-progress',
1636 widget: true
1637 });
1638 /**
1639 * @license
1640 * Copyright 2015 Google Inc. All Rights Reserved.
1641 *
1642 * Licensed under the Apache License, Version 2.0 (the "License");
1643 * you may not use this file except in compliance with the License.
1644 * You may obtain a copy of the License at
1645 *
1646 * http://www.apache.org/licenses/LICENSE-2.0
1647 *
1648 * Unless required by applicable law or agreed to in writing, software
1649 * distributed under the License is distributed on an "AS IS" BASIS,
1650 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1651 * See the License for the specific language governing permissions and
1652 * limitations under the License.
1653 */
1654 /**
1655 * Class constructor for Radio MDL component.
1656 * Implements MDL component design pattern defined at:
1657 * https://github.com/jasonmayes/mdl-component-design-pattern
1658 *
1659 * @constructor
1660 * @param {HTMLElement} element The element that will be upgraded.
1661 */
1662 var MaterialRadio = function MaterialRadio(element) {
1663 this.element_ = element;
1664 // Initialize instance.
1665 this.init();
1666 };
1667 window['MaterialRadio'] = MaterialRadio;
1668 /**
1669 * Store constants in one place so they can be updated easily.
1670 *
1671 * @enum {string | number}
1672 * @private
1673 */
1674 MaterialRadio.prototype.Constant_ = {TINY_TIMEOUT: 0.001};
1675 /**
1676 * Store strings for class names defined by this component that are used in
1677 * JavaScript. This allows us to simply change it in one place should we
1678 * decide to modify at a later date.
1679 *
1680 * @enum {string}
1681 * @private
1682 */
1683 MaterialRadio.prototype.CssClasses_ = {
1684 IS_FOCUSED: 'is-focused',
1685 IS_DISABLED: 'is-disabled',
1686 IS_CHECKED: 'is-checked',
1687 IS_UPGRADED: 'is-upgraded',
1688 JS_RADIO: 'mdl-js-radio',
1689 RADIO_BTN: 'mdl-radio__button',
1690 RADIO_OUTER_CIRCLE: 'mdl-radio__outer-circle',
1691 RADIO_INNER_CIRCLE: 'mdl-radio__inner-circle',
1692 RIPPLE_EFFECT: 'mdl-js-ripple-effect',
1693 RIPPLE_IGNORE_EVENTS: 'mdl-js-ripple-effect--ignore-events',
1694 RIPPLE_CONTAINER: 'mdl-radio__ripple-container',
1695 RIPPLE_CENTER: 'mdl-ripple--center',
1696 RIPPLE: 'mdl-ripple'
1697 };
1698 /**
1699 * Handle change of state.
1700 *
1701 * @param {Event} event The event that fired.
1702 * @private
1703 */
1704 MaterialRadio.prototype.onChange_ = function (event) {
1705 // Since other radio buttons don't get change events, we need to look for
1706 // them to update their classes.
1707 var radios = document.getElementsByClassName(this.CssClasses_.JS_RADIO);
1708 for (var i = 0; i < radios.length; i++) {
1709 var button = radios[i].querySelector('.' + this.CssClasses_.RADIO_BTN);
1710 // Different name == different group, so no point updating those.
1711 if (button.getAttribute('name') === this.btnElement_.getAttribute('name')) {
1712 if (typeof radios[i]['MaterialRadio'] !== 'undefined') {
1713 radios[i]['MaterialRadio'].updateClasses_();
1714 }
1715 }
1716 }
1717 };
1718 /**
1719 * Handle focus.
1720 *
1721 * @param {Event} event The event that fired.
1722 * @private
1723 */
1724 MaterialRadio.prototype.onFocus_ = function (event) {
1725 this.element_.classList.add(this.CssClasses_.IS_FOCUSED);
1726 };
1727 /**
1728 * Handle lost focus.
1729 *
1730 * @param {Event} event The event that fired.
1731 * @private
1732 */
1733 MaterialRadio.prototype.onBlur_ = function (event) {
1734 this.element_.classList.remove(this.CssClasses_.IS_FOCUSED);
1735 };
1736 /**
1737 * Handle mouseup.
1738 *
1739 * @param {Event} event The event that fired.
1740 * @private
1741 */
1742 MaterialRadio.prototype.onMouseup_ = function (event) {
1743 this.blur_();
1744 };
1745 /**
1746 * Update classes.
1747 *
1748 * @private
1749 */
1750 MaterialRadio.prototype.updateClasses_ = function () {
1751 this.checkDisabled();
1752 this.checkToggleState();
1753 };
1754 /**
1755 * Add blur.
1756 *
1757 * @private
1758 */
1759 MaterialRadio.prototype.blur_ = function () {
1760 // TODO: figure out why there's a focus event being fired after our blur,
1761 // so that we can avoid this hack.
1762 window.setTimeout(function () {
1763 this.btnElement_.blur();
1764 }.bind(this), this.Constant_.TINY_TIMEOUT);
1765 };
1766// Public methods.
1767 /**
1768 * Check the components disabled state.
1769 *
1770 * @public
1771 */
1772 MaterialRadio.prototype.checkDisabled = function () {
1773 if (this.btnElement_.disabled) {
1774 this.element_.classList.add(this.CssClasses_.IS_DISABLED);
1775 } else {
1776 this.element_.classList.remove(this.CssClasses_.IS_DISABLED);
1777 }
1778 };
1779 MaterialRadio.prototype['checkDisabled'] = MaterialRadio.prototype.checkDisabled;
1780 /**
1781 * Check the components toggled state.
1782 *
1783 * @public
1784 */
1785 MaterialRadio.prototype.checkToggleState = function () {
1786 if (this.btnElement_.checked) {
1787 this.element_.classList.add(this.CssClasses_.IS_CHECKED);
1788 } else {
1789 this.element_.classList.remove(this.CssClasses_.IS_CHECKED);
1790 }
1791 };
1792 MaterialRadio.prototype['checkToggleState'] = MaterialRadio.prototype.checkToggleState;
1793 /**
1794 * Disable radio.
1795 *
1796 * @public
1797 */
1798 MaterialRadio.prototype.disable = function () {
1799 this.btnElement_.disabled = true;
1800 this.updateClasses_();
1801 };
1802 MaterialRadio.prototype['disable'] = MaterialRadio.prototype.disable;
1803 /**
1804 * Enable radio.
1805 *
1806 * @public
1807 */
1808 MaterialRadio.prototype.enable = function () {
1809 this.btnElement_.disabled = false;
1810 this.updateClasses_();
1811 };
1812 MaterialRadio.prototype['enable'] = MaterialRadio.prototype.enable;
1813 /**
1814 * Check radio.
1815 *
1816 * @public
1817 */
1818 MaterialRadio.prototype.check = function () {
1819 this.btnElement_.checked = true;
1820 this.onChange_(null);
1821 };
1822 MaterialRadio.prototype['check'] = MaterialRadio.prototype.check;
1823 /**
1824 * Uncheck radio.
1825 *
1826 * @public
1827 */
1828 MaterialRadio.prototype.uncheck = function () {
1829 this.btnElement_.checked = false;
1830 this.onChange_(null);
1831 };
1832 MaterialRadio.prototype['uncheck'] = MaterialRadio.prototype.uncheck;
1833 /**
1834 * Initialize element.
1835 */
1836 MaterialRadio.prototype.init = function () {
1837 if (this.element_) {
1838 this.btnElement_ = this.element_.querySelector('.' + this.CssClasses_.RADIO_BTN);
1839 this.boundChangeHandler_ = this.onChange_.bind(this);
1840 this.boundFocusHandler_ = this.onChange_.bind(this);
1841 this.boundBlurHandler_ = this.onBlur_.bind(this);
1842 this.boundMouseUpHandler_ = this.onMouseup_.bind(this);
1843 var outerCircle = document.createElement('span');
1844 outerCircle.classList.add(this.CssClasses_.RADIO_OUTER_CIRCLE);
1845 var innerCircle = document.createElement('span');
1846 innerCircle.classList.add(this.CssClasses_.RADIO_INNER_CIRCLE);
1847 this.element_.appendChild(outerCircle);
1848 this.element_.appendChild(innerCircle);
1849 var rippleContainer;
1850 if (this.element_.classList.contains(this.CssClasses_.RIPPLE_EFFECT)) {
1851 this.element_.classList.add(this.CssClasses_.RIPPLE_IGNORE_EVENTS);
1852 rippleContainer = document.createElement('span');
1853 rippleContainer.classList.add(this.CssClasses_.RIPPLE_CONTAINER);
1854 rippleContainer.classList.add(this.CssClasses_.RIPPLE_EFFECT);
1855 rippleContainer.classList.add(this.CssClasses_.RIPPLE_CENTER);
1856 rippleContainer.addEventListener('mouseup', this.boundMouseUpHandler_);
1857 var ripple = document.createElement('span');
1858 ripple.classList.add(this.CssClasses_.RIPPLE);
1859 rippleContainer.appendChild(ripple);
1860 this.element_.appendChild(rippleContainer);
1861 }
1862 this.btnElement_.addEventListener('change', this.boundChangeHandler_);
1863 this.btnElement_.addEventListener('focus', this.boundFocusHandler_);
1864 this.btnElement_.addEventListener('blur', this.boundBlurHandler_);
1865 this.element_.addEventListener('mouseup', this.boundMouseUpHandler_);
1866 this.updateClasses_();
1867 this.element_.classList.add(this.CssClasses_.IS_UPGRADED);
1868 }
1869 };
1870// The component registers itself. It can assume componentHandler is available
1871// in the global scope.
1872 componentHandler.register({
1873 constructor: MaterialRadio,
1874 classAsString: 'MaterialRadio',
1875 cssClass: 'mdl-js-radio',
1876 widget: true
1877 });
1878 /**
1879 * @license
1880 * Copyright 2015 Google Inc. All Rights Reserved.
1881 *
1882 * Licensed under the Apache License, Version 2.0 (the "License");
1883 * you may not use this file except in compliance with the License.
1884 * You may obtain a copy of the License at
1885 *
1886 * http://www.apache.org/licenses/LICENSE-2.0
1887 *
1888 * Unless required by applicable law or agreed to in writing, software
1889 * distributed under the License is distributed on an "AS IS" BASIS,
1890 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1891 * See the License for the specific language governing permissions and
1892 * limitations under the License.
1893 */
1894 /**
1895 * Class constructor for Slider MDL component.
1896 * Implements MDL component design pattern defined at:
1897 * https://github.com/jasonmayes/mdl-component-design-pattern
1898 *
1899 * @constructor
1900 * @param {HTMLElement} element The element that will be upgraded.
1901 */
1902 var MaterialSlider = function MaterialSlider(element) {
1903 this.element_ = element;
1904 // Browser feature detection.
1905 this.isIE_ = window.navigator.msPointerEnabled;
1906 // Initialize instance.
1907 this.init();
1908 };
1909 window['MaterialSlider'] = MaterialSlider;
1910 /**
1911 * Store constants in one place so they can be updated easily.
1912 *
1913 * @enum {string | number}
1914 * @private
1915 */
1916 MaterialSlider.prototype.Constant_ = {};
1917 /**
1918 * Store strings for class names defined by this component that are used in
1919 * JavaScript. This allows us to simply change it in one place should we
1920 * decide to modify at a later date.
1921 *
1922 * @enum {string}
1923 * @private
1924 */
1925 MaterialSlider.prototype.CssClasses_ = {
1926 IE_CONTAINER: 'mdl-slider__ie-container',
1927 SLIDER_CONTAINER: 'mdl-slider__container',
1928 BACKGROUND_FLEX: 'mdl-slider__background-flex',
1929 BACKGROUND_LOWER: 'mdl-slider__background-lower',
1930 BACKGROUND_UPPER: 'mdl-slider__background-upper',
1931 IS_LOWEST_VALUE: 'is-lowest-value',
1932 IS_UPGRADED: 'is-upgraded'
1933 };
1934 /**
1935 * Handle input on element.
1936 *
1937 * @param {Event} event The event that fired.
1938 * @private
1939 */
1940 MaterialSlider.prototype.onInput_ = function (event) {
1941 this.updateValueStyles_();
1942 };
1943 /**
1944 * Handle change on element.
1945 *
1946 * @param {Event} event The event that fired.
1947 * @private
1948 */
1949 MaterialSlider.prototype.onChange_ = function (event) {
1950 this.updateValueStyles_();
1951 };
1952 /**
1953 * Handle mouseup on element.
1954 *
1955 * @param {Event} event The event that fired.
1956 * @private
1957 */
1958 MaterialSlider.prototype.onMouseUp_ = function (event) {
1959 event.target.blur();
1960 };
1961 /**
1962 * Handle mousedown on container element.
1963 * This handler is purpose is to not require the use to click
1964 * exactly on the 2px slider element, as FireFox seems to be very
1965 * strict about this.
1966 *
1967 * @param {Event} event The event that fired.
1968 * @private
1969 * @suppress {missingProperties}
1970 */
1971 MaterialSlider.prototype.onContainerMouseDown_ = function (event) {
1972 // If this click is not on the parent element (but rather some child)
1973 // ignore. It may still bubble up.
1974 if (event.target !== this.element_.parentElement) {
1975 return;
1976 }
1977 // Discard the original event and create a new event that
1978 // is on the slider element.
1979 event.preventDefault();
1980 var newEvent = new MouseEvent('mousedown', {
1981 target: event.target,
1982 buttons: event.buttons,
1983 clientX: event.clientX,
1984 clientY: this.element_.getBoundingClientRect().y
1985 });
1986 this.element_.dispatchEvent(newEvent);
1987 };
1988 /**
1989 * Handle updating of values.
1990 *
1991 * @private
1992 */
1993 MaterialSlider.prototype.updateValueStyles_ = function () {
1994 // Calculate and apply percentages to div structure behind slider.
1995 var fraction = (this.element_.value - this.element_.min) / (this.element_.max - this.element_.min);
1996 if (fraction === 0) {
1997 this.element_.classList.add(this.CssClasses_.IS_LOWEST_VALUE);
1998 } else {
1999 this.element_.classList.remove(this.CssClasses_.IS_LOWEST_VALUE);
2000 }
2001 if (!this.isIE_) {
2002 this.backgroundLower_.style.flex = fraction;
2003 this.backgroundLower_.style.webkitFlex = fraction;
2004 this.backgroundUpper_.style.flex = 1 - fraction;
2005 this.backgroundUpper_.style.webkitFlex = 1 - fraction;
2006 }
2007 };
2008// Public methods.
2009 /**
2010 * Disable slider.
2011 *
2012 * @public
2013 */
2014 MaterialSlider.prototype.disable = function () {
2015 this.element_.disabled = true;
2016 };
2017 MaterialSlider.prototype['disable'] = MaterialSlider.prototype.disable;
2018 /**
2019 * Enable slider.
2020 *
2021 * @public
2022 */
2023 MaterialSlider.prototype.enable = function () {
2024 this.element_.disabled = false;
2025 };
2026 MaterialSlider.prototype['enable'] = MaterialSlider.prototype.enable;
2027 /**
2028 * Update slider value.
2029 *
2030 * @param {number} value The value to which to set the control (optional).
2031 * @public
2032 */
2033 MaterialSlider.prototype.change = function (value) {
2034 if (typeof value !== 'undefined') {
2035 this.element_.value = value;
2036 }
2037 this.updateValueStyles_();
2038 };
2039 MaterialSlider.prototype['change'] = MaterialSlider.prototype.change;
2040 /**
2041 * Initialize element.
2042 */
2043 MaterialSlider.prototype.init = function () {
2044 if (this.element_) {
2045 if (this.isIE_) {
2046 // Since we need to specify a very large height in IE due to
2047 // implementation limitations, we add a parent here that trims it down to
2048 // a reasonable size.
2049 var containerIE = document.createElement('div');
2050 containerIE.classList.add(this.CssClasses_.IE_CONTAINER);
2051 this.element_.parentElement.insertBefore(containerIE, this.element_);
2052 this.element_.parentElement.removeChild(this.element_);
2053 containerIE.appendChild(this.element_);
2054 } else {
2055 // For non-IE browsers, we need a div structure that sits behind the
2056 // slider and allows us to style the left and right sides of it with
2057 // different colors.
2058 var container = document.createElement('div');
2059 container.classList.add(this.CssClasses_.SLIDER_CONTAINER);
2060 this.element_.parentElement.insertBefore(container, this.element_);
2061 this.element_.parentElement.removeChild(this.element_);
2062 container.appendChild(this.element_);
2063 var backgroundFlex = document.createElement('div');
2064 backgroundFlex.classList.add(this.CssClasses_.BACKGROUND_FLEX);
2065 container.appendChild(backgroundFlex);
2066 this.backgroundLower_ = document.createElement('div');
2067 this.backgroundLower_.classList.add(this.CssClasses_.BACKGROUND_LOWER);
2068 backgroundFlex.appendChild(this.backgroundLower_);
2069 this.backgroundUpper_ = document.createElement('div');
2070 this.backgroundUpper_.classList.add(this.CssClasses_.BACKGROUND_UPPER);
2071 backgroundFlex.appendChild(this.backgroundUpper_);
2072 }
2073 this.boundInputHandler = this.onInput_.bind(this);
2074 this.boundChangeHandler = this.onChange_.bind(this);
2075 this.boundMouseUpHandler = this.onMouseUp_.bind(this);
2076 this.boundContainerMouseDownHandler = this.onContainerMouseDown_.bind(this);
2077 this.element_.addEventListener('input', this.boundInputHandler);
2078 this.element_.addEventListener('change', this.boundChangeHandler);
2079 this.element_.addEventListener('mouseup', this.boundMouseUpHandler);
2080 this.element_.parentElement.addEventListener('mousedown', this.boundContainerMouseDownHandler);
2081 this.updateValueStyles_();
2082 this.element_.classList.add(this.CssClasses_.IS_UPGRADED);
2083 }
2084 };
2085// The component registers itself. It can assume componentHandler is available
2086// in the global scope.
2087 componentHandler.register({
2088 constructor: MaterialSlider,
2089 classAsString: 'MaterialSlider',
2090 cssClass: 'mdl-js-slider',
2091 widget: true
2092 });
2093 /**
2094 * Copyright 2015 Google Inc. All Rights Reserved.
2095 *
2096 * Licensed under the Apache License, Version 2.0 (the "License");
2097 * you may not use this file except in compliance with the License.
2098 * You may obtain a copy of the License at
2099 *
2100 * http://www.apache.org/licenses/LICENSE-2.0
2101 *
2102 * Unless required by applicable law or agreed to in writing, software
2103 * distributed under the License is distributed on an "AS IS" BASIS,
2104 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2105 * See the License for the specific language governing permissions and
2106 * limitations under the License.
2107 */
2108 /**
2109 * Class constructor for Snackbar MDL component.
2110 * Implements MDL component design pattern defined at:
2111 * https://github.com/jasonmayes/mdl-component-design-pattern
2112 *
2113 * @constructor
2114 * @param {HTMLElement} element The element that will be upgraded.
2115 */
2116 var MaterialSnackbar = function MaterialSnackbar(element) {
2117 this.element_ = element;
2118 this.textElement_ = this.element_.querySelector('.' + this.cssClasses_.MESSAGE);
2119 this.actionElement_ = this.element_.querySelector('.' + this.cssClasses_.ACTION);
2120 if (!this.textElement_) {
2121 throw new Error('There must be a message element for a snackbar.');
2122 }
2123 if (!this.actionElement_) {
2124 throw new Error('There must be an action element for a snackbar.');
2125 }
2126 this.active = false;
2127 this.actionHandler_ = undefined;
2128 this.message_ = undefined;
2129 this.actionText_ = undefined;
2130 this.queuedNotifications_ = [];
2131 this.setActionHidden_(true);
2132 };
2133 window['MaterialSnackbar'] = MaterialSnackbar;
2134 /**
2135 * Store constants in one place so they can be updated easily.
2136 *
2137 * @enum {string | number}
2138 * @private
2139 */
2140 MaterialSnackbar.prototype.Constant_ = {
2141 // The duration of the snackbar show/hide animation, in ms.
2142 ANIMATION_LENGTH: 250
2143 };
2144 /**
2145 * Store strings for class names defined by this component that are used in
2146 * JavaScript. This allows us to simply change it in one place should we
2147 * decide to modify at a later date.
2148 *
2149 * @enum {string}
2150 * @private
2151 */
2152 MaterialSnackbar.prototype.cssClasses_ = {
2153 SNACKBAR: 'mdl-snackbar',
2154 MESSAGE: 'mdl-snackbar__text',
2155 ACTION: 'mdl-snackbar__action',
2156 ACTIVE: 'mdl-snackbar--active'
2157 };
2158 /**
2159 * Display the snackbar.
2160 *
2161 * @private
2162 */
2163 MaterialSnackbar.prototype.displaySnackbar_ = function () {
2164 this.element_.setAttribute('aria-hidden', 'true');
2165 if (this.actionHandler_) {
2166 this.actionElement_.textContent = this.actionText_;
2167 this.actionElement_.addEventListener('click', this.actionHandler_);
2168 this.setActionHidden_(false);
2169 }
2170 this.textElement_.textContent = this.message_;
2171 this.element_.classList.add(this.cssClasses_.ACTIVE);
2172 this.element_.setAttribute('aria-hidden', 'false');
2173 setTimeout(this.cleanup_.bind(this), this.timeout_);
2174 };
2175 /**
2176 * Show the snackbar.
2177 *
2178 * @param {Object} data The data for the notification.
2179 * @public
2180 */
2181 MaterialSnackbar.prototype.showSnackbar = function (data) {
2182 if (data === undefined) {
2183 throw new Error('Please provide a data object with at least a message to display.');
2184 }
2185 if (data['message'] === undefined) {
2186 throw new Error('Please provide a message to be displayed.');
2187 }
2188 if (data['actionHandler'] && !data['actionText']) {
2189 throw new Error('Please provide action text with the handler.');
2190 }
2191 if (this.active) {
2192 this.queuedNotifications_.push(data);
2193 } else {
2194 this.active = true;
2195 this.message_ = data['message'];
2196 if (data['timeout']) {
2197 this.timeout_ = data['timeout'];
2198 } else {
2199 this.timeout_ = 2750;
2200 }
2201 if (data['actionHandler']) {
2202 this.actionHandler_ = data['actionHandler'];
2203 }
2204 if (data['actionText']) {
2205 this.actionText_ = data['actionText'];
2206 }
2207 this.displaySnackbar_();
2208 }
2209 };
2210 MaterialSnackbar.prototype['showSnackbar'] = MaterialSnackbar.prototype.showSnackbar;
2211 /**
2212 * Check if the queue has items within it.
2213 * If it does, display the next entry.
2214 *
2215 * @private
2216 */
2217 MaterialSnackbar.prototype.checkQueue_ = function () {
2218 if (this.queuedNotifications_.length > 0) {
2219 this.showSnackbar(this.queuedNotifications_.shift());
2220 }
2221 };
2222 /**
2223 * Cleanup the snackbar event listeners and accessiblity attributes.
2224 *
2225 * @private
2226 */
2227 MaterialSnackbar.prototype.cleanup_ = function () {
2228 this.element_.classList.remove(this.cssClasses_.ACTIVE);
2229 setTimeout(function () {
2230 this.element_.setAttribute('aria-hidden', 'true');
2231 this.textElement_.textContent = '';
2232 if (!Boolean(this.actionElement_.getAttribute('aria-hidden'))) {
2233 this.setActionHidden_(true);
2234 this.actionElement_.textContent = '';
2235 this.actionElement_.removeEventListener('click', this.actionHandler_);
2236 }
2237 this.actionHandler_ = undefined;
2238 this.message_ = undefined;
2239 this.actionText_ = undefined;
2240 this.active = false;
2241 this.checkQueue_();
2242 }.bind(this), this.Constant_.ANIMATION_LENGTH);
2243 };
2244 /**
2245 * Set the action handler hidden state.
2246 *
2247 * @param {boolean} value
2248 * @private
2249 */
2250 MaterialSnackbar.prototype.setActionHidden_ = function (value) {
2251 if (value) {
2252 this.actionElement_.setAttribute('aria-hidden', 'true');
2253 } else {
2254 this.actionElement_.removeAttribute('aria-hidden');
2255 }
2256 };
2257// The component registers itself. It can assume componentHandler is available
2258// in the global scope.
2259 componentHandler.register({
2260 constructor: MaterialSnackbar,
2261 classAsString: 'MaterialSnackbar',
2262 cssClass: 'mdl-js-snackbar',
2263 widget: true
2264 });
2265 /**
2266 * @license
2267 * Copyright 2015 Google Inc. All Rights Reserved.
2268 *
2269 * Licensed under the Apache License, Version 2.0 (the "License");
2270 * you may not use this file except in compliance with the License.
2271 * You may obtain a copy of the License at
2272 *
2273 * http://www.apache.org/licenses/LICENSE-2.0
2274 *
2275 * Unless required by applicable law or agreed to in writing, software
2276 * distributed under the License is distributed on an "AS IS" BASIS,
2277 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2278 * See the License for the specific language governing permissions and
2279 * limitations under the License.
2280 */
2281 /**
2282 * Class constructor for Spinner MDL component.
2283 * Implements MDL component design pattern defined at:
2284 * https://github.com/jasonmayes/mdl-component-design-pattern
2285 *
2286 * @param {HTMLElement} element The element that will be upgraded.
2287 * @constructor
2288 */
2289 var MaterialSpinner = function MaterialSpinner(element) {
2290 this.element_ = element;
2291 // Initialize instance.
2292 this.init();
2293 };
2294 window['MaterialSpinner'] = MaterialSpinner;
2295 /**
2296 * Store constants in one place so they can be updated easily.
2297 *
2298 * @enum {string | number}
2299 * @private
2300 */
2301 MaterialSpinner.prototype.Constant_ = {MDL_SPINNER_LAYER_COUNT: 4};
2302 /**
2303 * Store strings for class names defined by this component that are used in
2304 * JavaScript. This allows us to simply change it in one place should we
2305 * decide to modify at a later date.
2306 *
2307 * @enum {string}
2308 * @private
2309 */
2310 MaterialSpinner.prototype.CssClasses_ = {
2311 MDL_SPINNER_LAYER: 'mdl-spinner__layer',
2312 MDL_SPINNER_CIRCLE_CLIPPER: 'mdl-spinner__circle-clipper',
2313 MDL_SPINNER_CIRCLE: 'mdl-spinner__circle',
2314 MDL_SPINNER_GAP_PATCH: 'mdl-spinner__gap-patch',
2315 MDL_SPINNER_LEFT: 'mdl-spinner__left',
2316 MDL_SPINNER_RIGHT: 'mdl-spinner__right'
2317 };
2318 /**
2319 * Auxiliary method to create a spinner layer.
2320 *
2321 * @param {number} index Index of the layer to be created.
2322 * @public
2323 */
2324 MaterialSpinner.prototype.createLayer = function (index) {
2325 var layer = document.createElement('div');
2326 layer.classList.add(this.CssClasses_.MDL_SPINNER_LAYER);
2327 layer.classList.add(this.CssClasses_.MDL_SPINNER_LAYER + '-' + index);
2328 var leftClipper = document.createElement('div');
2329 leftClipper.classList.add(this.CssClasses_.MDL_SPINNER_CIRCLE_CLIPPER);
2330 leftClipper.classList.add(this.CssClasses_.MDL_SPINNER_LEFT);
2331 var gapPatch = document.createElement('div');
2332 gapPatch.classList.add(this.CssClasses_.MDL_SPINNER_GAP_PATCH);
2333 var rightClipper = document.createElement('div');
2334 rightClipper.classList.add(this.CssClasses_.MDL_SPINNER_CIRCLE_CLIPPER);
2335 rightClipper.classList.add(this.CssClasses_.MDL_SPINNER_RIGHT);
2336 var circleOwners = [
2337 leftClipper,
2338 gapPatch,
2339 rightClipper
2340 ];
2341 for (var i = 0; i < circleOwners.length; i++) {
2342 var circle = document.createElement('div');
2343 circle.classList.add(this.CssClasses_.MDL_SPINNER_CIRCLE);
2344 circleOwners[i].appendChild(circle);
2345 }
2346 layer.appendChild(leftClipper);
2347 layer.appendChild(gapPatch);
2348 layer.appendChild(rightClipper);
2349 this.element_.appendChild(layer);
2350 };
2351 MaterialSpinner.prototype['createLayer'] = MaterialSpinner.prototype.createLayer;
2352 /**
2353 * Stops the spinner animation.
2354 * Public method for users who need to stop the spinner for any reason.
2355 *
2356 * @public
2357 */
2358 MaterialSpinner.prototype.stop = function () {
2359 this.element_.classList.remove('is-active');
2360 };
2361 MaterialSpinner.prototype['stop'] = MaterialSpinner.prototype.stop;
2362 /**
2363 * Starts the spinner animation.
2364 * Public method for users who need to manually start the spinner for any reason
2365 * (instead of just adding the 'is-active' class to their markup).
2366 *
2367 * @public
2368 */
2369 MaterialSpinner.prototype.start = function () {
2370 this.element_.classList.add('is-active');
2371 };
2372 MaterialSpinner.prototype['start'] = MaterialSpinner.prototype.start;
2373 /**
2374 * Initialize element.
2375 */
2376 MaterialSpinner.prototype.init = function () {
2377 if (this.element_) {
2378 for (var i = 1; i <= this.Constant_.MDL_SPINNER_LAYER_COUNT; i++) {
2379 this.createLayer(i);
2380 }
2381 this.element_.classList.add('is-upgraded');
2382 }
2383 };
2384// The component registers itself. It can assume componentHandler is available
2385// in the global scope.
2386 componentHandler.register({
2387 constructor: MaterialSpinner,
2388 classAsString: 'MaterialSpinner',
2389 cssClass: 'mdl-js-spinner',
2390 widget: true
2391 });
2392 /**
2393 * @license
2394 * Copyright 2015 Google Inc. All Rights Reserved.
2395 *
2396 * Licensed under the Apache License, Version 2.0 (the "License");
2397 * you may not use this file except in compliance with the License.
2398 * You may obtain a copy of the License at
2399 *
2400 * http://www.apache.org/licenses/LICENSE-2.0
2401 *
2402 * Unless required by applicable law or agreed to in writing, software
2403 * distributed under the License is distributed on an "AS IS" BASIS,
2404 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2405 * See the License for the specific language governing permissions and
2406 * limitations under the License.
2407 */
2408 /**
2409 * Class constructor for Checkbox MDL component.
2410 * Implements MDL component design pattern defined at:
2411 * https://github.com/jasonmayes/mdl-component-design-pattern
2412 *
2413 * @constructor
2414 * @param {HTMLElement} element The element that will be upgraded.
2415 */
2416 var MaterialSwitch = function MaterialSwitch(element) {
2417 this.element_ = element;
2418 // Initialize instance.
2419 this.init();
2420 };
2421 window['MaterialSwitch'] = MaterialSwitch;
2422 /**
2423 * Store constants in one place so they can be updated easily.
2424 *
2425 * @enum {string | number}
2426 * @private
2427 */
2428 MaterialSwitch.prototype.Constant_ = {TINY_TIMEOUT: 0.001};
2429 /**
2430 * Store strings for class names defined by this component that are used in
2431 * JavaScript. This allows us to simply change it in one place should we
2432 * decide to modify at a later date.
2433 *
2434 * @enum {string}
2435 * @private
2436 */
2437 MaterialSwitch.prototype.CssClasses_ = {
2438 INPUT: 'mdl-switch__input',
2439 TRACK: 'mdl-switch__track',
2440 THUMB: 'mdl-switch__thumb',
2441 FOCUS_HELPER: 'mdl-switch__focus-helper',
2442 RIPPLE_EFFECT: 'mdl-js-ripple-effect',
2443 RIPPLE_IGNORE_EVENTS: 'mdl-js-ripple-effect--ignore-events',
2444 RIPPLE_CONTAINER: 'mdl-switch__ripple-container',
2445 RIPPLE_CENTER: 'mdl-ripple--center',
2446 RIPPLE: 'mdl-ripple',
2447 IS_FOCUSED: 'is-focused',
2448 IS_DISABLED: 'is-disabled',
2449 IS_CHECKED: 'is-checked'
2450 };
2451 /**
2452 * Handle change of state.
2453 *
2454 * @param {Event} event The event that fired.
2455 * @private
2456 */
2457 MaterialSwitch.prototype.onChange_ = function (event) {
2458 this.updateClasses_();
2459 };
2460 /**
2461 * Handle focus of element.
2462 *
2463 * @param {Event} event The event that fired.
2464 * @private
2465 */
2466 MaterialSwitch.prototype.onFocus_ = function (event) {
2467 this.element_.classList.add(this.CssClasses_.IS_FOCUSED);
2468 };
2469 /**
2470 * Handle lost focus of element.
2471 *
2472 * @param {Event} event The event that fired.
2473 * @private
2474 */
2475 MaterialSwitch.prototype.onBlur_ = function (event) {
2476 this.element_.classList.remove(this.CssClasses_.IS_FOCUSED);
2477 };
2478 /**
2479 * Handle mouseup.
2480 *
2481 * @param {Event} event The event that fired.
2482 * @private
2483 */
2484 MaterialSwitch.prototype.onMouseUp_ = function (event) {
2485 this.blur_();
2486 };
2487 /**
2488 * Handle class updates.
2489 *
2490 * @private
2491 */
2492 MaterialSwitch.prototype.updateClasses_ = function () {
2493 this.checkDisabled();
2494 this.checkToggleState();
2495 };
2496 /**
2497 * Add blur.
2498 *
2499 * @private
2500 */
2501 MaterialSwitch.prototype.blur_ = function () {
2502 // TODO: figure out why there's a focus event being fired after our blur,
2503 // so that we can avoid this hack.
2504 window.setTimeout(function () {
2505 this.inputElement_.blur();
2506 }.bind(this), this.Constant_.TINY_TIMEOUT);
2507 };
2508// Public methods.
2509 /**
2510 * Check the components disabled state.
2511 *
2512 * @public
2513 */
2514 MaterialSwitch.prototype.checkDisabled = function () {
2515 if (this.inputElement_.disabled) {
2516 this.element_.classList.add(this.CssClasses_.IS_DISABLED);
2517 } else {
2518 this.element_.classList.remove(this.CssClasses_.IS_DISABLED);
2519 }
2520 };
2521 MaterialSwitch.prototype['checkDisabled'] = MaterialSwitch.prototype.checkDisabled;
2522 /**
2523 * Check the components toggled state.
2524 *
2525 * @public
2526 */
2527 MaterialSwitch.prototype.checkToggleState = function () {
2528 if (this.inputElement_.checked) {
2529 this.element_.classList.add(this.CssClasses_.IS_CHECKED);
2530 } else {
2531 this.element_.classList.remove(this.CssClasses_.IS_CHECKED);
2532 }
2533 };
2534 MaterialSwitch.prototype['checkToggleState'] = MaterialSwitch.prototype.checkToggleState;
2535 /**
2536 * Disable switch.
2537 *
2538 * @public
2539 */
2540 MaterialSwitch.prototype.disable = function () {
2541 this.inputElement_.disabled = true;
2542 this.updateClasses_();
2543 };
2544 MaterialSwitch.prototype['disable'] = MaterialSwitch.prototype.disable;
2545 /**
2546 * Enable switch.
2547 *
2548 * @public
2549 */
2550 MaterialSwitch.prototype.enable = function () {
2551 this.inputElement_.disabled = false;
2552 this.updateClasses_();
2553 };
2554 MaterialSwitch.prototype['enable'] = MaterialSwitch.prototype.enable;
2555 /**
2556 * Activate switch.
2557 *
2558 * @public
2559 */
2560 MaterialSwitch.prototype.on = function () {
2561 this.inputElement_.checked = true;
2562 this.updateClasses_();
2563 };
2564 MaterialSwitch.prototype['on'] = MaterialSwitch.prototype.on;
2565 /**
2566 * Deactivate switch.
2567 *
2568 * @public
2569 */
2570 MaterialSwitch.prototype.off = function () {
2571 this.inputElement_.checked = false;
2572 this.updateClasses_();
2573 };
2574 MaterialSwitch.prototype['off'] = MaterialSwitch.prototype.off;
2575 /**
2576 * Initialize element.
2577 */
2578 MaterialSwitch.prototype.init = function () {
2579 if (this.element_) {
2580 this.inputElement_ = this.element_.querySelector('.' + this.CssClasses_.INPUT);
2581 var track = document.createElement('div');
2582 track.classList.add(this.CssClasses_.TRACK);
2583 var thumb = document.createElement('div');
2584 thumb.classList.add(this.CssClasses_.THUMB);
2585 var focusHelper = document.createElement('span');
2586 focusHelper.classList.add(this.CssClasses_.FOCUS_HELPER);
2587 thumb.appendChild(focusHelper);
2588 this.element_.appendChild(track);
2589 this.element_.appendChild(thumb);
2590 this.boundMouseUpHandler = this.onMouseUp_.bind(this);
2591 if (this.element_.classList.contains(this.CssClasses_.RIPPLE_EFFECT)) {
2592 this.element_.classList.add(this.CssClasses_.RIPPLE_IGNORE_EVENTS);
2593 this.rippleContainerElement_ = document.createElement('span');
2594 this.rippleContainerElement_.classList.add(this.CssClasses_.RIPPLE_CONTAINER);
2595 this.rippleContainerElement_.classList.add(this.CssClasses_.RIPPLE_EFFECT);
2596 this.rippleContainerElement_.classList.add(this.CssClasses_.RIPPLE_CENTER);
2597 this.rippleContainerElement_.addEventListener('mouseup', this.boundMouseUpHandler);
2598 var ripple = document.createElement('span');
2599 ripple.classList.add(this.CssClasses_.RIPPLE);
2600 this.rippleContainerElement_.appendChild(ripple);
2601 this.element_.appendChild(this.rippleContainerElement_);
2602 }
2603 this.boundChangeHandler = this.onChange_.bind(this);
2604 this.boundFocusHandler = this.onFocus_.bind(this);
2605 this.boundBlurHandler = this.onBlur_.bind(this);
2606 this.inputElement_.addEventListener('change', this.boundChangeHandler);
2607 this.inputElement_.addEventListener('focus', this.boundFocusHandler);
2608 this.inputElement_.addEventListener('blur', this.boundBlurHandler);
2609 this.element_.addEventListener('mouseup', this.boundMouseUpHandler);
2610 this.updateClasses_();
2611 this.element_.classList.add('is-upgraded');
2612 }
2613 };
2614// The component registers itself. It can assume componentHandler is available
2615// in the global scope.
2616 componentHandler.register({
2617 constructor: MaterialSwitch,
2618 classAsString: 'MaterialSwitch',
2619 cssClass: 'mdl-js-switch',
2620 widget: true
2621 });
2622 /**
2623 * @license
2624 * Copyright 2015 Google Inc. All Rights Reserved.
2625 *
2626 * Licensed under the Apache License, Version 2.0 (the "License");
2627 * you may not use this file except in compliance with the License.
2628 * You may obtain a copy of the License at
2629 *
2630 * http://www.apache.org/licenses/LICENSE-2.0
2631 *
2632 * Unless required by applicable law or agreed to in writing, software
2633 * distributed under the License is distributed on an "AS IS" BASIS,
2634 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2635 * See the License for the specific language governing permissions and
2636 * limitations under the License.
2637 */
2638 /**
2639 * Class constructor for Tabs MDL component.
2640 * Implements MDL component design pattern defined at:
2641 * https://github.com/jasonmayes/mdl-component-design-pattern
2642 *
2643 * @constructor
2644 * @param {Element} element The element that will be upgraded.
2645 */
2646 var MaterialTabs = function MaterialTabs(element) {
2647 // Stores the HTML element.
2648 this.element_ = element;
2649 // Initialize instance.
2650 this.init();
2651 };
2652 window['MaterialTabs'] = MaterialTabs;
2653 /**
2654 * Store constants in one place so they can be updated easily.
2655 *
2656 * @enum {string}
2657 * @private
2658 */
2659 MaterialTabs.prototype.Constant_ = {};
2660 /**
2661 * Store strings for class names defined by this component that are used in
2662 * JavaScript. This allows us to simply change it in one place should we
2663 * decide to modify at a later date.
2664 *
2665 * @enum {string}
2666 * @private
2667 */
2668 MaterialTabs.prototype.CssClasses_ = {
2669 TAB_CLASS: 'mdl-tabs__tab',
2670 PANEL_CLASS: 'mdl-tabs__panel',
2671 ACTIVE_CLASS: 'is-active',
2672 UPGRADED_CLASS: 'is-upgraded',
2673 MDL_JS_RIPPLE_EFFECT: 'mdl-js-ripple-effect',
2674 MDL_RIPPLE_CONTAINER: 'mdl-tabs__ripple-container',
2675 MDL_RIPPLE: 'mdl-ripple',
2676 MDL_JS_RIPPLE_EFFECT_IGNORE_EVENTS: 'mdl-js-ripple-effect--ignore-events'
2677 };
2678 /**
2679 * Handle clicks to a tabs component
2680 *
2681 * @private
2682 */
2683 MaterialTabs.prototype.initTabs_ = function () {
2684 if (this.element_.classList.contains(this.CssClasses_.MDL_JS_RIPPLE_EFFECT)) {
2685 this.element_.classList.add(this.CssClasses_.MDL_JS_RIPPLE_EFFECT_IGNORE_EVENTS);
2686 }
2687 // Select element tabs, document panels
2688 this.tabs_ = this.element_.querySelectorAll('.' + this.CssClasses_.TAB_CLASS);
2689 this.panels_ = this.element_.querySelectorAll('.' + this.CssClasses_.PANEL_CLASS);
2690 // Create new tabs for each tab element
2691 for (var i = 0; i < this.tabs_.length; i++) {
2692 new MaterialTab(this.tabs_[i], this);
2693 }
2694 this.element_.classList.add(this.CssClasses_.UPGRADED_CLASS);
2695 };
2696 /**
2697 * Reset tab state, dropping active classes
2698 *
2699 * @private
2700 */
2701 MaterialTabs.prototype.resetTabState_ = function () {
2702 for (var k = 0; k < this.tabs_.length; k++) {
2703 this.tabs_[k].classList.remove(this.CssClasses_.ACTIVE_CLASS);
2704 }
2705 };
2706 /**
2707 * Reset panel state, droping active classes
2708 *
2709 * @private
2710 */
2711 MaterialTabs.prototype.resetPanelState_ = function () {
2712 for (var j = 0; j < this.panels_.length; j++) {
2713 this.panels_[j].classList.remove(this.CssClasses_.ACTIVE_CLASS);
2714 }
2715 };
2716 /**
2717 * Initialize element.
2718 */
2719 MaterialTabs.prototype.init = function () {
2720 if (this.element_) {
2721 this.initTabs_();
2722 }
2723 };
2724
2725 /**
2726 * Constructor for an individual tab.
2727 *
2728 * @constructor
2729 * @param {Element} tab The HTML element for the tab.
2730 * @param {MaterialTabs} ctx The MaterialTabs object that owns the tab.
2731 */
2732 function MaterialTab(tab, ctx) {
2733 if (tab) {
2734 if (ctx.element_.classList.contains(ctx.CssClasses_.MDL_JS_RIPPLE_EFFECT)) {
2735 var rippleContainer = document.createElement('span');
2736 rippleContainer.classList.add(ctx.CssClasses_.MDL_RIPPLE_CONTAINER);
2737 rippleContainer.classList.add(ctx.CssClasses_.MDL_JS_RIPPLE_EFFECT);
2738 var ripple = document.createElement('span');
2739 ripple.classList.add(ctx.CssClasses_.MDL_RIPPLE);
2740 rippleContainer.appendChild(ripple);
2741 tab.appendChild(rippleContainer);
2742 }
2743 tab.addEventListener('click', function (e) {
2744 if (tab.getAttribute('href').charAt(0) === '#') {
2745 e.preventDefault();
2746 var href = tab.href.split('#')[1];
2747 var panel = ctx.element_.querySelector('#' + href);
2748 ctx.resetTabState_();
2749 ctx.resetPanelState_();
2750 tab.classList.add(ctx.CssClasses_.ACTIVE_CLASS);
2751 panel.classList.add(ctx.CssClasses_.ACTIVE_CLASS);
2752 }
2753 });
2754 }
2755 }
2756
2757// The component registers itself. It can assume componentHandler is available
2758// in the global scope.
2759 componentHandler.register({
2760 constructor: MaterialTabs,
2761 classAsString: 'MaterialTabs',
2762 cssClass: 'mdl-js-tabs'
2763 });
2764 /**
2765 * @license
2766 * Copyright 2015 Google Inc. All Rights Reserved.
2767 *
2768 * Licensed under the Apache License, Version 2.0 (the "License");
2769 * you may not use this file except in compliance with the License.
2770 * You may obtain a copy of the License at
2771 *
2772 * http://www.apache.org/licenses/LICENSE-2.0
2773 *
2774 * Unless required by applicable law or agreed to in writing, software
2775 * distributed under the License is distributed on an "AS IS" BASIS,
2776 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2777 * See the License for the specific language governing permissions and
2778 * limitations under the License.
2779 */
2780 /**
2781 * Class constructor for Textfield MDL component.
2782 * Implements MDL component design pattern defined at:
2783 * https://github.com/jasonmayes/mdl-component-design-pattern
2784 *
2785 * @constructor
2786 * @param {HTMLElement} element The element that will be upgraded.
2787 */
2788 var MaterialTextfield = function MaterialTextfield(element) {
2789 this.element_ = element;
2790 this.maxRows = this.Constant_.NO_MAX_ROWS;
2791 // Initialize instance.
2792 this.init();
2793 };
2794 window['MaterialTextfield'] = MaterialTextfield;
2795 /**
2796 * Store constants in one place so they can be updated easily.
2797 *
2798 * @enum {string | number}
2799 * @private
2800 */
2801 MaterialTextfield.prototype.Constant_ = {
2802 NO_MAX_ROWS: -1,
2803 MAX_ROWS_ATTRIBUTE: 'maxrows'
2804 };
2805 /**
2806 * Store strings for class names defined by this component that are used in
2807 * JavaScript. This allows us to simply change it in one place should we
2808 * decide to modify at a later date.
2809 *
2810 * @enum {string}
2811 * @private
2812 */
2813 MaterialTextfield.prototype.CssClasses_ = {
2814 LABEL: 'mdl-textfield__label',
2815 INPUT: 'mdl-textfield__input',
2816 IS_DIRTY: 'is-dirty',
2817 IS_FOCUSED: 'is-focused',
2818 IS_DISABLED: 'is-disabled',
2819 IS_INVALID: 'is-invalid',
2820 IS_UPGRADED: 'is-upgraded',
2821 HAS_PLACEHOLDER: 'has-placeholder'
2822 };
2823 /**
2824 * Handle input being entered.
2825 *
2826 * @param {Event} event The event that fired.
2827 * @private
2828 */
2829 MaterialTextfield.prototype.onKeyDown_ = function (event) {
2830 var currentRowCount = event.target.value.split('\n').length;
2831 if (event.keyCode === 13) {
2832 if (currentRowCount >= this.maxRows) {
2833 event.preventDefault();
2834 }
2835 }
2836 };
2837 /**
2838 * Handle focus.
2839 *
2840 * @param {Event} event The event that fired.
2841 * @private
2842 */
2843 MaterialTextfield.prototype.onFocus_ = function (event) {
2844 this.element_.classList.add(this.CssClasses_.IS_FOCUSED);
2845 };
2846 /**
2847 * Handle lost focus.
2848 *
2849 * @param {Event} event The event that fired.
2850 * @private
2851 */
2852 MaterialTextfield.prototype.onBlur_ = function (event) {
2853 this.element_.classList.remove(this.CssClasses_.IS_FOCUSED);
2854 };
2855 /**
2856 * Handle reset event from out side.
2857 *
2858 * @param {Event} event The event that fired.
2859 * @private
2860 */
2861 MaterialTextfield.prototype.onReset_ = function (event) {
2862 this.updateClasses_();
2863 };
2864 /**
2865 * Handle class updates.
2866 *
2867 * @private
2868 */
2869 MaterialTextfield.prototype.updateClasses_ = function () {
2870 this.checkDisabled();
2871 this.checkValidity();
2872 this.checkDirty();
2873 this.checkFocus();
2874 };
2875// Public methods.
2876 /**
2877 * Check the disabled state and update field accordingly.
2878 *
2879 * @public
2880 */
2881 MaterialTextfield.prototype.checkDisabled = function () {
2882 if (this.input_.disabled) {
2883 this.element_.classList.add(this.CssClasses_.IS_DISABLED);
2884 } else {
2885 this.element_.classList.remove(this.CssClasses_.IS_DISABLED);
2886 }
2887 };
2888 MaterialTextfield.prototype['checkDisabled'] = MaterialTextfield.prototype.checkDisabled;
2889 /**
2890 * Check the focus state and update field accordingly.
2891 *
2892 * @public
2893 */
2894 MaterialTextfield.prototype.checkFocus = function () {
2895 if (Boolean(this.element_.querySelector(':focus'))) {
2896 this.element_.classList.add(this.CssClasses_.IS_FOCUSED);
2897 } else {
2898 this.element_.classList.remove(this.CssClasses_.IS_FOCUSED);
2899 }
2900 };
2901 MaterialTextfield.prototype['checkFocus'] = MaterialTextfield.prototype.checkFocus;
2902 /**
2903 * Check the validity state and update field accordingly.
2904 *
2905 * @public
2906 */
2907 MaterialTextfield.prototype.checkValidity = function () {
2908 if (this.input_.validity) {
2909 if (this.input_.validity.valid) {
2910 this.element_.classList.remove(this.CssClasses_.IS_INVALID);
2911 } else {
2912 this.element_.classList.add(this.CssClasses_.IS_INVALID);
2913 }
2914 }
2915 };
2916 MaterialTextfield.prototype['checkValidity'] = MaterialTextfield.prototype.checkValidity;
2917 /**
2918 * Check the dirty state and update field accordingly.
2919 *
2920 * @public
2921 */
2922 MaterialTextfield.prototype.checkDirty = function () {
2923 if (this.input_.value && this.input_.value.length > 0) {
2924 this.element_.classList.add(this.CssClasses_.IS_DIRTY);
2925 } else {
2926 this.element_.classList.remove(this.CssClasses_.IS_DIRTY);
2927 }
2928 };
2929 MaterialTextfield.prototype['checkDirty'] = MaterialTextfield.prototype.checkDirty;
2930 /**
2931 * Disable text field.
2932 *
2933 * @public
2934 */
2935 MaterialTextfield.prototype.disable = function () {
2936 this.input_.disabled = true;
2937 this.updateClasses_();
2938 };
2939 MaterialTextfield.prototype['disable'] = MaterialTextfield.prototype.disable;
2940 /**
2941 * Enable text field.
2942 *
2943 * @public
2944 */
2945 MaterialTextfield.prototype.enable = function () {
2946 this.input_.disabled = false;
2947 this.updateClasses_();
2948 };
2949 MaterialTextfield.prototype['enable'] = MaterialTextfield.prototype.enable;
2950 /**
2951 * Update text field value.
2952 *
2953 * @param {string} value The value to which to set the control (optional).
2954 * @public
2955 */
2956 MaterialTextfield.prototype.change = function (value) {
2957 this.input_.value = value || '';
2958 this.updateClasses_();
2959 };
2960 MaterialTextfield.prototype['change'] = MaterialTextfield.prototype.change;
2961 /**
2962 * Initialize element.
2963 */
2964 MaterialTextfield.prototype.init = function () {
2965 if (this.element_) {
2966 this.label_ = this.element_.querySelector('.' + this.CssClasses_.LABEL);
2967 this.input_ = this.element_.querySelector('.' + this.CssClasses_.INPUT);
2968 if (this.input_) {
2969 if (this.input_.hasAttribute(this.Constant_.MAX_ROWS_ATTRIBUTE)) {
2970 this.maxRows = parseInt(this.input_.getAttribute(this.Constant_.MAX_ROWS_ATTRIBUTE), 10);
2971 if (isNaN(this.maxRows)) {
2972 this.maxRows = this.Constant_.NO_MAX_ROWS;
2973 }
2974 }
2975 if (this.input_.hasAttribute('placeholder')) {
2976 this.element_.classList.add(this.CssClasses_.HAS_PLACEHOLDER);
2977 }
2978 this.boundUpdateClassesHandler = this.updateClasses_.bind(this);
2979 this.boundFocusHandler = this.onFocus_.bind(this);
2980 this.boundBlurHandler = this.onBlur_.bind(this);
2981 this.boundResetHandler = this.onReset_.bind(this);
2982 this.input_.addEventListener('input', this.boundUpdateClassesHandler);
2983 this.input_.addEventListener('focus', this.boundFocusHandler);
2984 this.input_.addEventListener('blur', this.boundBlurHandler);
2985 this.input_.addEventListener('reset', this.boundResetHandler);
2986 if (this.maxRows !== this.Constant_.NO_MAX_ROWS) {
2987 // TODO: This should handle pasting multi line text.
2988 // Currently doesn't.
2989 this.boundKeyDownHandler = this.onKeyDown_.bind(this);
2990 this.input_.addEventListener('keydown', this.boundKeyDownHandler);
2991 }
2992 var invalid = this.element_.classList.contains(this.CssClasses_.IS_INVALID);
2993 this.updateClasses_();
2994 this.element_.classList.add(this.CssClasses_.IS_UPGRADED);
2995 if (invalid) {
2996 this.element_.classList.add(this.CssClasses_.IS_INVALID);
2997 }
2998 if (this.input_.hasAttribute('autofocus')) {
2999 this.element_.focus();
3000 this.checkFocus();
3001 }
3002 }
3003 }
3004 };
3005// The component registers itself. It can assume componentHandler is available
3006// in the global scope.
3007 componentHandler.register({
3008 constructor: MaterialTextfield,
3009 classAsString: 'MaterialTextfield',
3010 cssClass: 'mdl-js-textfield',
3011 widget: true
3012 });
3013 /**
3014 * @license
3015 * Copyright 2015 Google Inc. All Rights Reserved.
3016 *
3017 * Licensed under the Apache License, Version 2.0 (the "License");
3018 * you may not use this file except in compliance with the License.
3019 * You may obtain a copy of the License at
3020 *
3021 * http://www.apache.org/licenses/LICENSE-2.0
3022 *
3023 * Unless required by applicable law or agreed to in writing, software
3024 * distributed under the License is distributed on an "AS IS" BASIS,
3025 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
3026 * See the License for the specific language governing permissions and
3027 * limitations under the License.
3028 */
3029 /**
3030 * Class constructor for Tooltip MDL component.
3031 * Implements MDL component design pattern defined at:
3032 * https://github.com/jasonmayes/mdl-component-design-pattern
3033 *
3034 * @constructor
3035 * @param {HTMLElement} element The element that will be upgraded.
3036 */
3037 var MaterialTooltip = function MaterialTooltip(element) {
3038 this.element_ = element;
3039 // Initialize instance.
3040 this.init();
3041 };
3042 window['MaterialTooltip'] = MaterialTooltip;
3043 /**
3044 * Store constants in one place so they can be updated easily.
3045 *
3046 * @enum {string | number}
3047 * @private
3048 */
3049 MaterialTooltip.prototype.Constant_ = {};
3050 /**
3051 * Store strings for class names defined by this component that are used in
3052 * JavaScript. This allows us to simply change it in one place should we
3053 * decide to modify at a later date.
3054 *
3055 * @enum {string}
3056 * @private
3057 */
3058 MaterialTooltip.prototype.CssClasses_ = {
3059 IS_ACTIVE: 'is-active',
3060 BOTTOM: 'mdl-tooltip--bottom',
3061 LEFT: 'mdl-tooltip--left',
3062 RIGHT: 'mdl-tooltip--right',
3063 TOP: 'mdl-tooltip--top'
3064 };
3065 /**
3066 * Handle mouseenter for tooltip.
3067 *
3068 * @param {Event} event The event that fired.
3069 * @private
3070 */
3071 MaterialTooltip.prototype.handleMouseEnter_ = function (event) {
3072 var props = event.target.getBoundingClientRect();
3073 var left = props.left + props.width / 2;
3074 var top = props.top + props.height / 2;
3075 var marginLeft = -1 * (this.element_.offsetWidth / 2);
3076 var marginTop = -1 * (this.element_.offsetHeight / 2);
3077 if (this.element_.classList.contains(this.CssClasses_.LEFT) || this.element_.classList.contains(this.CssClasses_.RIGHT)) {
3078 left = props.width / 2;
3079 if (top + marginTop < 0) {
3080 this.element_.style.top = '0';
3081 this.element_.style.marginTop = '0';
3082 } else {
3083 this.element_.style.top = top + 'px';
3084 this.element_.style.marginTop = marginTop + 'px';
3085 }
3086 } else {
3087 if (left + marginLeft < 0) {
3088 this.element_.style.left = '0';
3089 this.element_.style.marginLeft = '0';
3090 } else {
3091 this.element_.style.left = left + 'px';
3092 this.element_.style.marginLeft = marginLeft + 'px';
3093 }
3094 }
3095 if (this.element_.classList.contains(this.CssClasses_.TOP)) {
3096 this.element_.style.top = props.top - this.element_.offsetHeight - 10 + 'px';
3097 } else if (this.element_.classList.contains(this.CssClasses_.RIGHT)) {
3098 this.element_.style.left = props.left + props.width + 10 + 'px';
3099 } else if (this.element_.classList.contains(this.CssClasses_.LEFT)) {
3100 this.element_.style.left = props.left - this.element_.offsetWidth - 10 + 'px';
3101 } else {
3102 this.element_.style.top = props.top + props.height + 10 + 'px';
3103 }
3104 this.element_.classList.add(this.CssClasses_.IS_ACTIVE);
3105 };
3106 /**
3107 * Hide tooltip on mouseleave or scroll
3108 *
3109 * @private
3110 */
3111 MaterialTooltip.prototype.hideTooltip_ = function () {
3112 this.element_.classList.remove(this.CssClasses_.IS_ACTIVE);
3113 };
3114 /**
3115 * Initialize element.
3116 */
3117 MaterialTooltip.prototype.init = function () {
3118 if (this.element_) {
3119 var forElId = this.element_.getAttribute('for') || this.element_.getAttribute('data-mdl-for');
3120 if (forElId) {
3121 this.forElement_ = document.getElementById(forElId);
3122 }
3123 if (this.forElement_) {
3124 // It's left here because it prevents accidental text selection on Android
3125 if (!this.forElement_.hasAttribute('tabindex')) {
3126 this.forElement_.setAttribute('tabindex', '0');
3127 }
3128 this.boundMouseEnterHandler = this.handleMouseEnter_.bind(this);
3129 this.boundMouseLeaveAndScrollHandler = this.hideTooltip_.bind(this);
3130 this.forElement_.addEventListener('mouseenter', this.boundMouseEnterHandler, false);
3131 this.forElement_.addEventListener('touchend', this.boundMouseEnterHandler, false);
3132 this.forElement_.addEventListener('mouseleave', this.boundMouseLeaveAndScrollHandler, false);
3133 window.addEventListener('scroll', this.boundMouseLeaveAndScrollHandler, true);
3134 window.addEventListener('touchstart', this.boundMouseLeaveAndScrollHandler);
3135 }
3136 }
3137 };
3138// The component registers itself. It can assume componentHandler is available
3139// in the global scope.
3140 componentHandler.register({
3141 constructor: MaterialTooltip,
3142 classAsString: 'MaterialTooltip',
3143 cssClass: 'mdl-tooltip'
3144 });
3145 /**
3146 * @license
3147 * Copyright 2015 Google Inc. All Rights Reserved.
3148 *
3149 * Licensed under the Apache License, Version 2.0 (the "License");
3150 * you may not use this file except in compliance with the License.
3151 * You may obtain a copy of the License at
3152 *
3153 * http://www.apache.org/licenses/LICENSE-2.0
3154 *
3155 * Unless required by applicable law or agreed to in writing, software
3156 * distributed under the License is distributed on an "AS IS" BASIS,
3157 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
3158 * See the License for the specific language governing permissions and
3159 * limitations under the License.
3160 */
3161 /**
3162 * Class constructor for Layout MDL component.
3163 * Implements MDL component design pattern defined at:
3164 * https://github.com/jasonmayes/mdl-component-design-pattern
3165 *
3166 * @constructor
3167 * @param {HTMLElement} element The element that will be upgraded.
3168 */
3169 var MaterialLayout = function MaterialLayout(element) {
3170 this.element_ = element;
3171 // Initialize instance.
3172 this.init();
3173 };
3174 window['MaterialLayout'] = MaterialLayout;
3175 /**
3176 * Store constants in one place so they can be updated easily.
3177 *
3178 * @enum {string | number}
3179 * @private
3180 */
3181 MaterialLayout.prototype.Constant_ = {
3182 MAX_WIDTH: '(max-width: 1024px)',
3183 TAB_SCROLL_PIXELS: 100,
3184 RESIZE_TIMEOUT: 100,
3185 MENU_ICON: '&#xE5D2;',
3186 CHEVRON_LEFT: 'chevron_left',
3187 CHEVRON_RIGHT: 'chevron_right'
3188 };
3189 /**
3190 * Keycodes, for code readability.
3191 *
3192 * @enum {number}
3193 * @private
3194 */
3195 MaterialLayout.prototype.Keycodes_ = {
3196 ENTER: 13,
3197 ESCAPE: 27,
3198 SPACE: 32
3199 };
3200 /**
3201 * Modes.
3202 *
3203 * @enum {number}
3204 * @private
3205 */
3206 MaterialLayout.prototype.Mode_ = {
3207 STANDARD: 0,
3208 SEAMED: 1,
3209 WATERFALL: 2,
3210 SCROLL: 3
3211 };
3212 /**
3213 * Store strings for class names defined by this component that are used in
3214 * JavaScript. This allows us to simply change it in one place should we
3215 * decide to modify at a later date.
3216 *
3217 * @enum {string}
3218 * @private
3219 */
3220 MaterialLayout.prototype.CssClasses_ = {
3221 CONTAINER: 'mdl-layout__container',
3222 HEADER: 'mdl-layout__header',
3223 DRAWER: 'mdl-layout__drawer',
3224 CONTENT: 'mdl-layout__content',
3225 DRAWER_BTN: 'mdl-layout__drawer-button',
3226 ICON: 'material-icons',
3227 JS_RIPPLE_EFFECT: 'mdl-js-ripple-effect',
3228 RIPPLE_CONTAINER: 'mdl-layout__tab-ripple-container',
3229 RIPPLE: 'mdl-ripple',
3230 RIPPLE_IGNORE_EVENTS: 'mdl-js-ripple-effect--ignore-events',
3231 HEADER_SEAMED: 'mdl-layout__header--seamed',
3232 HEADER_WATERFALL: 'mdl-layout__header--waterfall',
3233 HEADER_SCROLL: 'mdl-layout__header--scroll',
3234 FIXED_HEADER: 'mdl-layout--fixed-header',
3235 OBFUSCATOR: 'mdl-layout__obfuscator',
3236 TAB_BAR: 'mdl-layout__tab-bar',
3237 TAB_CONTAINER: 'mdl-layout__tab-bar-container',
3238 TAB: 'mdl-layout__tab',
3239 TAB_BAR_BUTTON: 'mdl-layout__tab-bar-button',
3240 TAB_BAR_LEFT_BUTTON: 'mdl-layout__tab-bar-left-button',
3241 TAB_BAR_RIGHT_BUTTON: 'mdl-layout__tab-bar-right-button',
3242 TAB_MANUAL_SWITCH: 'mdl-layout__tab-manual-switch',
3243 PANEL: 'mdl-layout__tab-panel',
3244 HAS_DRAWER: 'has-drawer',
3245 HAS_TABS: 'has-tabs',
3246 HAS_SCROLLING_HEADER: 'has-scrolling-header',
3247 CASTING_SHADOW: 'is-casting-shadow',
3248 IS_COMPACT: 'is-compact',
3249 IS_SMALL_SCREEN: 'is-small-screen',
3250 IS_DRAWER_OPEN: 'is-visible',
3251 IS_ACTIVE: 'is-active',
3252 IS_UPGRADED: 'is-upgraded',
3253 IS_ANIMATING: 'is-animating',
3254 ON_LARGE_SCREEN: 'mdl-layout--large-screen-only',
3255 ON_SMALL_SCREEN: 'mdl-layout--small-screen-only'
3256 };
3257 /**
3258 * Handles scrolling on the content.
3259 *
3260 * @private
3261 */
3262 MaterialLayout.prototype.contentScrollHandler_ = function () {
3263 if (this.header_.classList.contains(this.CssClasses_.IS_ANIMATING)) {
3264 return;
3265 }
3266 var headerVisible = !this.element_.classList.contains(this.CssClasses_.IS_SMALL_SCREEN) || this.element_.classList.contains(this.CssClasses_.FIXED_HEADER);
3267 if (this.content_.scrollTop > 0 && !this.header_.classList.contains(this.CssClasses_.IS_COMPACT)) {
3268 this.header_.classList.add(this.CssClasses_.CASTING_SHADOW);
3269 this.header_.classList.add(this.CssClasses_.IS_COMPACT);
3270 if (headerVisible) {
3271 this.header_.classList.add(this.CssClasses_.IS_ANIMATING);
3272 }
3273 } else if (this.content_.scrollTop <= 0 && this.header_.classList.contains(this.CssClasses_.IS_COMPACT)) {
3274 this.header_.classList.remove(this.CssClasses_.CASTING_SHADOW);
3275 this.header_.classList.remove(this.CssClasses_.IS_COMPACT);
3276 if (headerVisible) {
3277 this.header_.classList.add(this.CssClasses_.IS_ANIMATING);
3278 }
3279 }
3280 };
3281 /**
3282 * Handles a keyboard event on the drawer.
3283 *
3284 * @param {Event} evt The event that fired.
3285 * @private
3286 */
3287 MaterialLayout.prototype.keyboardEventHandler_ = function (evt) {
3288 // Only react when the drawer is open.
3289 if (evt.keyCode === this.Keycodes_.ESCAPE && this.drawer_.classList.contains(this.CssClasses_.IS_DRAWER_OPEN)) {
3290 this.toggleDrawer();
3291 }
3292 };
3293 /**
3294 * Handles changes in screen size.
3295 *
3296 * @private
3297 */
3298 MaterialLayout.prototype.screenSizeHandler_ = function () {
3299 if (this.screenSizeMediaQuery_.matches) {
3300 this.element_.classList.add(this.CssClasses_.IS_SMALL_SCREEN);
3301 } else {
3302 this.element_.classList.remove(this.CssClasses_.IS_SMALL_SCREEN);
3303 // Collapse drawer (if any) when moving to a large screen size.
3304 if (this.drawer_) {
3305 this.drawer_.classList.remove(this.CssClasses_.IS_DRAWER_OPEN);
3306 this.obfuscator_.classList.remove(this.CssClasses_.IS_DRAWER_OPEN);
3307 }
3308 }
3309 };
3310 /**
3311 * Handles events of drawer button.
3312 *
3313 * @param {Event} evt The event that fired.
3314 * @private
3315 */
3316 MaterialLayout.prototype.drawerToggleHandler_ = function (evt) {
3317 if (evt && evt.type === 'keydown') {
3318 if (evt.keyCode === this.Keycodes_.SPACE || evt.keyCode === this.Keycodes_.ENTER) {
3319 // prevent scrolling in drawer nav
3320 evt.preventDefault();
3321 } else {
3322 // prevent other keys
3323 return;
3324 }
3325 }
3326 this.toggleDrawer();
3327 };
3328 /**
3329 * Handles (un)setting the `is-animating` class
3330 *
3331 * @private
3332 */
3333 MaterialLayout.prototype.headerTransitionEndHandler_ = function () {
3334 this.header_.classList.remove(this.CssClasses_.IS_ANIMATING);
3335 };
3336 /**
3337 * Handles expanding the header on click
3338 *
3339 * @private
3340 */
3341 MaterialLayout.prototype.headerClickHandler_ = function () {
3342 if (this.header_.classList.contains(this.CssClasses_.IS_COMPACT)) {
3343 this.header_.classList.remove(this.CssClasses_.IS_COMPACT);
3344 this.header_.classList.add(this.CssClasses_.IS_ANIMATING);
3345 }
3346 };
3347 /**
3348 * Reset tab state, dropping active classes
3349 *
3350 * @private
3351 */
3352 MaterialLayout.prototype.resetTabState_ = function (tabBar) {
3353 for (var k = 0; k < tabBar.length; k++) {
3354 tabBar[k].classList.remove(this.CssClasses_.IS_ACTIVE);
3355 }
3356 };
3357 /**
3358 * Reset panel state, droping active classes
3359 *
3360 * @private
3361 */
3362 MaterialLayout.prototype.resetPanelState_ = function (panels) {
3363 for (var j = 0; j < panels.length; j++) {
3364 panels[j].classList.remove(this.CssClasses_.IS_ACTIVE);
3365 }
3366 };
3367 /**
3368 * Toggle drawer state
3369 *
3370 * @public
3371 */
3372 MaterialLayout.prototype.toggleDrawer = function () {
3373 var drawerButton = this.element_.querySelector('.' + this.CssClasses_.DRAWER_BTN);
3374 this.drawer_.classList.toggle(this.CssClasses_.IS_DRAWER_OPEN);
3375 this.obfuscator_.classList.toggle(this.CssClasses_.IS_DRAWER_OPEN);
3376 // Set accessibility properties.
3377 if (this.drawer_.classList.contains(this.CssClasses_.IS_DRAWER_OPEN)) {
3378 this.drawer_.setAttribute('aria-hidden', 'false');
3379 drawerButton.setAttribute('aria-expanded', 'true');
3380 } else {
3381 this.drawer_.setAttribute('aria-hidden', 'true');
3382 drawerButton.setAttribute('aria-expanded', 'false');
3383 }
3384 };
3385 MaterialLayout.prototype['toggleDrawer'] = MaterialLayout.prototype.toggleDrawer;
3386 /**
3387 * Initialize element.
3388 */
3389 MaterialLayout.prototype.init = function () {
3390 if (this.element_) {
3391 var container = document.createElement('div');
3392 container.classList.add(this.CssClasses_.CONTAINER);
3393 var focusedElement = this.element_.querySelector(':focus');
3394 this.element_.parentElement.insertBefore(container, this.element_);
3395 this.element_.parentElement.removeChild(this.element_);
3396 container.appendChild(this.element_);
3397 if (focusedElement) {
3398 focusedElement.focus();
3399 }
3400 var directChildren = this.element_.childNodes;
3401 var numChildren = directChildren.length;
3402 for (var c = 0; c < numChildren; c++) {
3403 var child = directChildren[c];
3404 if (child.classList && child.classList.contains(this.CssClasses_.HEADER)) {
3405 this.header_ = child;
3406 }
3407 if (child.classList && child.classList.contains(this.CssClasses_.DRAWER)) {
3408 this.drawer_ = child;
3409 }
3410 if (child.classList && child.classList.contains(this.CssClasses_.CONTENT)) {
3411 this.content_ = child;
3412 }
3413 }
3414 window.addEventListener('pageshow', function (e) {
3415 if (e.persisted) {
3416 // when page is loaded from back/forward cache
3417 // trigger repaint to let layout scroll in safari
3418 this.element_.style.overflowY = 'hidden';
3419 requestAnimationFrame(function () {
3420 this.element_.style.overflowY = '';
3421 }.bind(this));
3422 }
3423 }.bind(this), false);
3424 if (this.header_) {
3425 this.tabBar_ = this.header_.querySelector('.' + this.CssClasses_.TAB_BAR);
3426 }
3427 var mode = this.Mode_.STANDARD;
3428 if (this.header_) {
3429 if (this.header_.classList.contains(this.CssClasses_.HEADER_SEAMED)) {
3430 mode = this.Mode_.SEAMED;
3431 } else if (this.header_.classList.contains(this.CssClasses_.HEADER_WATERFALL)) {
3432 mode = this.Mode_.WATERFALL;
3433 this.header_.addEventListener('transitionend', this.headerTransitionEndHandler_.bind(this));
3434 this.header_.addEventListener('click', this.headerClickHandler_.bind(this));
3435 } else if (this.header_.classList.contains(this.CssClasses_.HEADER_SCROLL)) {
3436 mode = this.Mode_.SCROLL;
3437 container.classList.add(this.CssClasses_.HAS_SCROLLING_HEADER);
3438 }
3439 if (mode === this.Mode_.STANDARD) {
3440 this.header_.classList.add(this.CssClasses_.CASTING_SHADOW);
3441 if (this.tabBar_) {
3442 this.tabBar_.classList.add(this.CssClasses_.CASTING_SHADOW);
3443 }
3444 } else if (mode === this.Mode_.SEAMED || mode === this.Mode_.SCROLL) {
3445 this.header_.classList.remove(this.CssClasses_.CASTING_SHADOW);
3446 if (this.tabBar_) {
3447 this.tabBar_.classList.remove(this.CssClasses_.CASTING_SHADOW);
3448 }
3449 } else if (mode === this.Mode_.WATERFALL) {
3450 // Add and remove shadows depending on scroll position.
3451 // Also add/remove auxiliary class for styling of the compact version of
3452 // the header.
3453 this.content_.addEventListener('scroll', this.contentScrollHandler_.bind(this));
3454 this.contentScrollHandler_();
3455 }
3456 }
3457 // Add drawer toggling button to our layout, if we have an openable drawer.
3458 if (this.drawer_) {
3459 var drawerButton = this.element_.querySelector('.' + this.CssClasses_.DRAWER_BTN);
3460 if (!drawerButton) {
3461 drawerButton = document.createElement('div');
3462 drawerButton.setAttribute('aria-expanded', 'false');
3463 drawerButton.setAttribute('role', 'button');
3464 drawerButton.setAttribute('tabindex', '0');
3465 drawerButton.classList.add(this.CssClasses_.DRAWER_BTN);
3466 var drawerButtonIcon = document.createElement('i');
3467 drawerButtonIcon.classList.add(this.CssClasses_.ICON);
3468 drawerButtonIcon.innerHTML = this.Constant_.MENU_ICON;
3469 drawerButton.appendChild(drawerButtonIcon);
3470 }
3471 if (this.drawer_.classList.contains(this.CssClasses_.ON_LARGE_SCREEN)) {
3472 //If drawer has ON_LARGE_SCREEN class then add it to the drawer toggle button as well.
3473 drawerButton.classList.add(this.CssClasses_.ON_LARGE_SCREEN);
3474 } else if (this.drawer_.classList.contains(this.CssClasses_.ON_SMALL_SCREEN)) {
3475 //If drawer has ON_SMALL_SCREEN class then add it to the drawer toggle button as well.
3476 drawerButton.classList.add(this.CssClasses_.ON_SMALL_SCREEN);
3477 }
3478 drawerButton.addEventListener('click', this.drawerToggleHandler_.bind(this));
3479 drawerButton.addEventListener('keydown', this.drawerToggleHandler_.bind(this));
3480 // Add a class if the layout has a drawer, for altering the left padding.
3481 // Adds the HAS_DRAWER to the elements since this.header_ may or may
3482 // not be present.
3483 this.element_.classList.add(this.CssClasses_.HAS_DRAWER);
3484 // If we have a fixed header, add the button to the header rather than
3485 // the layout.
3486 if (this.element_.classList.contains(this.CssClasses_.FIXED_HEADER)) {
3487 this.header_.insertBefore(drawerButton, this.header_.firstChild);
3488 } else {
3489 this.element_.insertBefore(drawerButton, this.content_);
3490 }
3491 var obfuscator = document.createElement('div');
3492 obfuscator.classList.add(this.CssClasses_.OBFUSCATOR);
3493 this.element_.appendChild(obfuscator);
3494 obfuscator.addEventListener('click', this.drawerToggleHandler_.bind(this));
3495 this.obfuscator_ = obfuscator;
3496 this.drawer_.addEventListener('keydown', this.keyboardEventHandler_.bind(this));
3497 this.drawer_.setAttribute('aria-hidden', 'true');
3498 }
3499 // Keep an eye on screen size, and add/remove auxiliary class for styling
3500 // of small screens.
3501 this.screenSizeMediaQuery_ = window.matchMedia(this.Constant_.MAX_WIDTH);
3502 this.screenSizeMediaQuery_.addListener(this.screenSizeHandler_.bind(this));
3503 this.screenSizeHandler_();
3504 // Initialize tabs, if any.
3505 if (this.header_ && this.tabBar_) {
3506 this.element_.classList.add(this.CssClasses_.HAS_TABS);
3507 var tabContainer = document.createElement('div');
3508 tabContainer.classList.add(this.CssClasses_.TAB_CONTAINER);
3509 this.header_.insertBefore(tabContainer, this.tabBar_);
3510 this.header_.removeChild(this.tabBar_);
3511 var leftButton = document.createElement('div');
3512 leftButton.classList.add(this.CssClasses_.TAB_BAR_BUTTON);
3513 leftButton.classList.add(this.CssClasses_.TAB_BAR_LEFT_BUTTON);
3514 var leftButtonIcon = document.createElement('i');
3515 leftButtonIcon.classList.add(this.CssClasses_.ICON);
3516 leftButtonIcon.textContent = this.Constant_.CHEVRON_LEFT;
3517 leftButton.appendChild(leftButtonIcon);
3518 leftButton.addEventListener('click', function () {
3519 this.tabBar_.scrollLeft -= this.Constant_.TAB_SCROLL_PIXELS;
3520 }.bind(this));
3521 var rightButton = document.createElement('div');
3522 rightButton.classList.add(this.CssClasses_.TAB_BAR_BUTTON);
3523 rightButton.classList.add(this.CssClasses_.TAB_BAR_RIGHT_BUTTON);
3524 var rightButtonIcon = document.createElement('i');
3525 rightButtonIcon.classList.add(this.CssClasses_.ICON);
3526 rightButtonIcon.textContent = this.Constant_.CHEVRON_RIGHT;
3527 rightButton.appendChild(rightButtonIcon);
3528 rightButton.addEventListener('click', function () {
3529 this.tabBar_.scrollLeft += this.Constant_.TAB_SCROLL_PIXELS;
3530 }.bind(this));
3531 tabContainer.appendChild(leftButton);
3532 tabContainer.appendChild(this.tabBar_);
3533 tabContainer.appendChild(rightButton);
3534 // Add and remove tab buttons depending on scroll position and total
3535 // window size.
3536 var tabUpdateHandler = function () {
3537 if (this.tabBar_.scrollLeft > 0) {
3538 leftButton.classList.add(this.CssClasses_.IS_ACTIVE);
3539 } else {
3540 leftButton.classList.remove(this.CssClasses_.IS_ACTIVE);
3541 }
3542 if (this.tabBar_.scrollLeft < this.tabBar_.scrollWidth - this.tabBar_.offsetWidth) {
3543 rightButton.classList.add(this.CssClasses_.IS_ACTIVE);
3544 } else {
3545 rightButton.classList.remove(this.CssClasses_.IS_ACTIVE);
3546 }
3547 }.bind(this);
3548 this.tabBar_.addEventListener('scroll', tabUpdateHandler);
3549 tabUpdateHandler();
3550 // Update tabs when the window resizes.
3551 var windowResizeHandler = function () {
3552 // Use timeouts to make sure it doesn't happen too often.
3553 if (this.resizeTimeoutId_) {
3554 clearTimeout(this.resizeTimeoutId_);
3555 }
3556 this.resizeTimeoutId_ = setTimeout(function () {
3557 tabUpdateHandler();
3558 this.resizeTimeoutId_ = null;
3559 }.bind(this), this.Constant_.RESIZE_TIMEOUT);
3560 }.bind(this);
3561 window.addEventListener('resize', windowResizeHandler);
3562 if (this.tabBar_.classList.contains(this.CssClasses_.JS_RIPPLE_EFFECT)) {
3563 this.tabBar_.classList.add(this.CssClasses_.RIPPLE_IGNORE_EVENTS);
3564 }
3565 // Select element tabs, document panels
3566 var tabs = this.tabBar_.querySelectorAll('.' + this.CssClasses_.TAB);
3567 var panels = this.content_.querySelectorAll('.' + this.CssClasses_.PANEL);
3568 // Create new tabs for each tab element
3569 for (var i = 0; i < tabs.length; i++) {
3570 new MaterialLayoutTab(tabs[i], tabs, panels, this);
3571 }
3572 }
3573 this.element_.classList.add(this.CssClasses_.IS_UPGRADED);
3574 }
3575 };
3576
3577 /**
3578 * Constructor for an individual tab.
3579 *
3580 * @constructor
3581 * @param {HTMLElement} tab The HTML element for the tab.
3582 * @param {!Array<HTMLElement>} tabs Array with HTML elements for all tabs.
3583 * @param {!Array<HTMLElement>} panels Array with HTML elements for all panels.
3584 * @param {MaterialLayout} layout The MaterialLayout object that owns the tab.
3585 */
3586 function MaterialLayoutTab(tab, tabs, panels, layout) {
3587 /**
3588 * Auxiliary method to programmatically select a tab in the UI.
3589 */
3590 function selectTab() {
3591 var href = tab.href.split('#')[1];
3592 var panel = layout.content_.querySelector('#' + href);
3593 layout.resetTabState_(tabs);
3594 layout.resetPanelState_(panels);
3595 tab.classList.add(layout.CssClasses_.IS_ACTIVE);
3596 panel.classList.add(layout.CssClasses_.IS_ACTIVE);
3597 }
3598
3599 if (layout.tabBar_.classList.contains(layout.CssClasses_.JS_RIPPLE_EFFECT)) {
3600 var rippleContainer = document.createElement('span');
3601 rippleContainer.classList.add(layout.CssClasses_.RIPPLE_CONTAINER);
3602 rippleContainer.classList.add(layout.CssClasses_.JS_RIPPLE_EFFECT);
3603 var ripple = document.createElement('span');
3604 ripple.classList.add(layout.CssClasses_.RIPPLE);
3605 rippleContainer.appendChild(ripple);
3606 tab.appendChild(rippleContainer);
3607 }
3608 if (!layout.tabBar_.classList.contains(layout.CssClasses_.TAB_MANUAL_SWITCH)) {
3609 tab.addEventListener('click', function (e) {
3610 if (tab.getAttribute('href').charAt(0) === '#') {
3611 e.preventDefault();
3612 selectTab();
3613 }
3614 });
3615 }
3616 tab.show = selectTab;
3617 }
3618
3619 window['MaterialLayoutTab'] = MaterialLayoutTab;
3620// The component registers itself. It can assume componentHandler is available
3621// in the global scope.
3622 componentHandler.register({
3623 constructor: MaterialLayout,
3624 classAsString: 'MaterialLayout',
3625 cssClass: 'mdl-js-layout'
3626 });
3627 /**
3628 * @license
3629 * Copyright 2015 Google Inc. All Rights Reserved.
3630 *
3631 * Licensed under the Apache License, Version 2.0 (the "License");
3632 * you may not use this file except in compliance with the License.
3633 * You may obtain a copy of the License at
3634 *
3635 * http://www.apache.org/licenses/LICENSE-2.0
3636 *
3637 * Unless required by applicable law or agreed to in writing, software
3638 * distributed under the License is distributed on an "AS IS" BASIS,
3639 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
3640 * See the License for the specific language governing permissions and
3641 * limitations under the License.
3642 */
3643 /**
3644 * Class constructor for Data Table Card MDL component.
3645 * Implements MDL component design pattern defined at:
3646 * https://github.com/jasonmayes/mdl-component-design-pattern
3647 *
3648 * @constructor
3649 * @param {Element} element The element that will be upgraded.
3650 */
3651 var MaterialDataTable = function MaterialDataTable(element) {
3652 this.element_ = element;
3653 // Initialize instance.
3654 this.init();
3655 };
3656 window['MaterialDataTable'] = MaterialDataTable;
3657 /**
3658 * Store constants in one place so they can be updated easily.
3659 *
3660 * @enum {string | number}
3661 * @private
3662 */
3663 MaterialDataTable.prototype.Constant_ = {};
3664 /**
3665 * Store strings for class names defined by this component that are used in
3666 * JavaScript. This allows us to simply change it in one place should we
3667 * decide to modify at a later date.
3668 *
3669 * @enum {string}
3670 * @private
3671 */
3672 MaterialDataTable.prototype.CssClasses_ = {
3673 DATA_TABLE: 'mdl-data-table',
3674 SELECTABLE: 'mdl-data-table--selectable',
3675 SELECT_ELEMENT: 'mdl-data-table__select',
3676 IS_SELECTED: 'is-selected',
3677 IS_UPGRADED: 'is-upgraded'
3678 };
3679 /**
3680 * Generates and returns a function that toggles the selection state of a
3681 * single row (or multiple rows).
3682 *
3683 * @param {Element} checkbox Checkbox that toggles the selection state.
3684 * @param {Element} row Row to toggle when checkbox changes.
3685 * @param {(Array<Object>|NodeList)=} opt_rows Rows to toggle when checkbox changes.
3686 * @private
3687 */
3688 MaterialDataTable.prototype.selectRow_ = function (checkbox, row, opt_rows) {
3689 if (row) {
3690 return function () {
3691 if (checkbox.checked) {
3692 row.classList.add(this.CssClasses_.IS_SELECTED);
3693 } else {
3694 row.classList.remove(this.CssClasses_.IS_SELECTED);
3695 }
3696 }.bind(this);
3697 }
3698 if (opt_rows) {
3699 return function () {
3700 var i;
3701 var el;
3702 if (checkbox.checked) {
3703 for (i = 0; i < opt_rows.length; i++) {
3704 el = opt_rows[i].querySelector('td').querySelector('.mdl-checkbox');
3705 el['MaterialCheckbox'].check();
3706 opt_rows[i].classList.add(this.CssClasses_.IS_SELECTED);
3707 }
3708 } else {
3709 for (i = 0; i < opt_rows.length; i++) {
3710 el = opt_rows[i].querySelector('td').querySelector('.mdl-checkbox');
3711 el['MaterialCheckbox'].uncheck();
3712 opt_rows[i].classList.remove(this.CssClasses_.IS_SELECTED);
3713 }
3714 }
3715 }.bind(this);
3716 }
3717 };
3718 /**
3719 * Creates a checkbox for a single or or multiple rows and hooks up the
3720 * event handling.
3721 *
3722 * @param {Element} row Row to toggle when checkbox changes.
3723 * @param {(Array<Object>|NodeList)=} opt_rows Rows to toggle when checkbox changes.
3724 * @private
3725 */
3726 MaterialDataTable.prototype.createCheckbox_ = function (row, opt_rows) {
3727 var label = document.createElement('label');
3728 var labelClasses = [
3729 'mdl-checkbox',
3730 'mdl-js-checkbox',
3731 'mdl-js-ripple-effect',
3732 this.CssClasses_.SELECT_ELEMENT
3733 ];
3734 label.className = labelClasses.join(' ');
3735 var checkbox = document.createElement('input');
3736 checkbox.type = 'checkbox';
3737 checkbox.classList.add('mdl-checkbox__input');
3738 if (row) {
3739 checkbox.checked = row.classList.contains(this.CssClasses_.IS_SELECTED);
3740 checkbox.addEventListener('change', this.selectRow_(checkbox, row));
3741 } else if (opt_rows) {
3742 checkbox.addEventListener('change', this.selectRow_(checkbox, null, opt_rows));
3743 }
3744 label.appendChild(checkbox);
3745 componentHandler.upgradeElement(label, 'MaterialCheckbox');
3746 return label;
3747 };
3748 /**
3749 * Initialize element.
3750 */
3751 MaterialDataTable.prototype.init = function () {
3752 if (this.element_) {
3753 var firstHeader = this.element_.querySelector('th');
3754 var bodyRows = Array.prototype.slice.call(this.element_.querySelectorAll('tbody tr'));
3755 var footRows = Array.prototype.slice.call(this.element_.querySelectorAll('tfoot tr'));
3756 var rows = bodyRows.concat(footRows);
3757 if (this.element_.classList.contains(this.CssClasses_.SELECTABLE)) {
3758 var th = document.createElement('th');
3759 var headerCheckbox = this.createCheckbox_(null, rows);
3760 th.appendChild(headerCheckbox);
3761 firstHeader.parentElement.insertBefore(th, firstHeader);
3762 for (var i = 0; i < rows.length; i++) {
3763 var firstCell = rows[i].querySelector('td');
3764 if (firstCell) {
3765 var td = document.createElement('td');
3766 if (rows[i].parentNode.nodeName.toUpperCase() === 'TBODY') {
3767 var rowCheckbox = this.createCheckbox_(rows[i]);
3768 td.appendChild(rowCheckbox);
3769 }
3770 rows[i].insertBefore(td, firstCell);
3771 }
3772 }
3773 this.element_.classList.add(this.CssClasses_.IS_UPGRADED);
3774 }
3775 }
3776 };
3777// The component registers itself. It can assume componentHandler is available
3778// in the global scope.
3779 componentHandler.register({
3780 constructor: MaterialDataTable,
3781 classAsString: 'MaterialDataTable',
3782 cssClass: 'mdl-js-data-table'
3783 });
3784 /**
3785 * @license
3786 * Copyright 2015 Google Inc. All Rights Reserved.
3787 *
3788 * Licensed under the Apache License, Version 2.0 (the "License");
3789 * you may not use this file except in compliance with the License.
3790 * You may obtain a copy of the License at
3791 *
3792 * http://www.apache.org/licenses/LICENSE-2.0
3793 *
3794 * Unless required by applicable law or agreed to in writing, software
3795 * distributed under the License is distributed on an "AS IS" BASIS,
3796 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
3797 * See the License for the specific language governing permissions and
3798 * limitations under the License.
3799 */
3800 /**
3801 * Class constructor for Ripple MDL component.
3802 * Implements MDL component design pattern defined at:
3803 * https://github.com/jasonmayes/mdl-component-design-pattern
3804 *
3805 * @constructor
3806 * @param {HTMLElement} element The element that will be upgraded.
3807 */
3808 var MaterialRipple = function MaterialRipple(element) {
3809 this.element_ = element;
3810 // Initialize instance.
3811 this.init();
3812 };
3813 window['MaterialRipple'] = MaterialRipple;
3814 /**
3815 * Store constants in one place so they can be updated easily.
3816 *
3817 * @enum {string | number}
3818 * @private
3819 */
3820 MaterialRipple.prototype.Constant_ = {
3821 INITIAL_SCALE: 'scale(0.0001, 0.0001)',
3822 INITIAL_SIZE: '1px',
3823 INITIAL_OPACITY: '0.4',
3824 FINAL_OPACITY: '0',
3825 FINAL_SCALE: ''
3826 };
3827 /**
3828 * Store strings for class names defined by this component that are used in
3829 * JavaScript. This allows us to simply change it in one place should we
3830 * decide to modify at a later date.
3831 *
3832 * @enum {string}
3833 * @private
3834 */
3835 MaterialRipple.prototype.CssClasses_ = {
3836 RIPPLE_CENTER: 'mdl-ripple--center',
3837 RIPPLE_EFFECT_IGNORE_EVENTS: 'mdl-js-ripple-effect--ignore-events',
3838 RIPPLE: 'mdl-ripple',
3839 IS_ANIMATING: 'is-animating',
3840 IS_VISIBLE: 'is-visible'
3841 };
3842 /**
3843 * Handle mouse / finger down on element.
3844 *
3845 * @param {Event} event The event that fired.
3846 * @private
3847 */
3848 MaterialRipple.prototype.downHandler_ = function (event) {
3849 if (!this.rippleElement_.style.width && !this.rippleElement_.style.height) {
3850 var rect = this.element_.getBoundingClientRect();
3851 this.boundHeight = rect.height;
3852 this.boundWidth = rect.width;
3853 this.rippleSize_ = Math.sqrt(rect.width * rect.width + rect.height * rect.height) * 2 + 2;
3854 this.rippleElement_.style.width = this.rippleSize_ + 'px';
3855 this.rippleElement_.style.height = this.rippleSize_ + 'px';
3856 }
3857 this.rippleElement_.classList.add(this.CssClasses_.IS_VISIBLE);
3858 if (event.type === 'mousedown' && this.ignoringMouseDown_) {
3859 this.ignoringMouseDown_ = false;
3860 } else {
3861 if (event.type === 'touchstart') {
3862 this.ignoringMouseDown_ = true;
3863 }
3864 var frameCount = this.getFrameCount();
3865 if (frameCount > 0) {
3866 return;
3867 }
3868 this.setFrameCount(1);
3869 var bound = event.currentTarget.getBoundingClientRect();
3870 var x;
3871 var y;
3872 // Check if we are handling a keyboard click.
3873 if (event.clientX === 0 && event.clientY === 0) {
3874 x = Math.round(bound.width / 2);
3875 y = Math.round(bound.height / 2);
3876 } else {
3877 var clientX = event.clientX !== undefined ? event.clientX : event.touches[0].clientX;
3878 var clientY = event.clientY !== undefined ? event.clientY : event.touches[0].clientY;
3879 x = Math.round(clientX - bound.left);
3880 y = Math.round(clientY - bound.top);
3881 }
3882 this.setRippleXY(x, y);
3883 this.setRippleStyles(true);
3884 window.requestAnimationFrame(this.animFrameHandler.bind(this));
3885 }
3886 };
3887 /**
3888 * Handle mouse / finger up on element.
3889 *
3890 * @param {Event} event The event that fired.
3891 * @private
3892 */
3893 MaterialRipple.prototype.upHandler_ = function (event) {
3894 // Don't fire for the artificial "mouseup" generated by a double-click.
3895 if (event && event.detail !== 2) {
3896 // Allow a repaint to occur before removing this class, so the animation
3897 // shows for tap events, which seem to trigger a mouseup too soon after
3898 // mousedown.
3899 window.setTimeout(function () {
3900 this.rippleElement_.classList.remove(this.CssClasses_.IS_VISIBLE);
3901 }.bind(this), 0);
3902 }
3903 };
3904 /**
3905 * Initialize element.
3906 */
3907 MaterialRipple.prototype.init = function () {
3908 if (this.element_) {
3909 var recentering = this.element_.classList.contains(this.CssClasses_.RIPPLE_CENTER);
3910 if (!this.element_.classList.contains(this.CssClasses_.RIPPLE_EFFECT_IGNORE_EVENTS)) {
3911 this.rippleElement_ = this.element_.querySelector('.' + this.CssClasses_.RIPPLE);
3912 this.frameCount_ = 0;
3913 this.rippleSize_ = 0;
3914 this.x_ = 0;
3915 this.y_ = 0;
3916 // Touch start produces a compat mouse down event, which would cause a
3917 // second ripples. To avoid that, we use this property to ignore the first
3918 // mouse down after a touch start.
3919 this.ignoringMouseDown_ = false;
3920 this.boundDownHandler = this.downHandler_.bind(this);
3921 this.element_.addEventListener('mousedown', this.boundDownHandler);
3922 this.element_.addEventListener('touchstart', this.boundDownHandler);
3923 this.boundUpHandler = this.upHandler_.bind(this);
3924 this.element_.addEventListener('mouseup', this.boundUpHandler);
3925 this.element_.addEventListener('mouseleave', this.boundUpHandler);
3926 this.element_.addEventListener('touchend', this.boundUpHandler);
3927 this.element_.addEventListener('blur', this.boundUpHandler);
3928 /**
3929 * Getter for frameCount_.
3930 * @return {number} the frame count.
3931 */
3932 this.getFrameCount = function () {
3933 return this.frameCount_;
3934 };
3935 /**
3936 * Setter for frameCount_.
3937 * @param {number} fC the frame count.
3938 */
3939 this.setFrameCount = function (fC) {
3940 this.frameCount_ = fC;
3941 };
3942 /**
3943 * Getter for rippleElement_.
3944 * @return {Element} the ripple element.
3945 */
3946 this.getRippleElement = function () {
3947 return this.rippleElement_;
3948 };
3949 /**
3950 * Sets the ripple X and Y coordinates.
3951 * @param {number} newX the new X coordinate
3952 * @param {number} newY the new Y coordinate
3953 */
3954 this.setRippleXY = function (newX, newY) {
3955 this.x_ = newX;
3956 this.y_ = newY;
3957 };
3958 /**
3959 * Sets the ripple styles.
3960 * @param {boolean} start whether or not this is the start frame.
3961 */
3962 this.setRippleStyles = function (start) {
3963 if (this.rippleElement_ !== null) {
3964 var transformString;
3965 var scale;
3966 var size;
3967 var offset = 'translate(' + this.x_ + 'px, ' + this.y_ + 'px)';
3968 if (start) {
3969 scale = this.Constant_.INITIAL_SCALE;
3970 size = this.Constant_.INITIAL_SIZE;
3971 } else {
3972 scale = this.Constant_.FINAL_SCALE;
3973 size = this.rippleSize_ + 'px';
3974 if (recentering) {
3975 offset = 'translate(' + this.boundWidth / 2 + 'px, ' + this.boundHeight / 2 + 'px)';
3976 }
3977 }
3978 transformString = 'translate(-50%, -50%) ' + offset + scale;
3979 this.rippleElement_.style.webkitTransform = transformString;
3980 this.rippleElement_.style.msTransform = transformString;
3981 this.rippleElement_.style.transform = transformString;
3982 if (start) {
3983 this.rippleElement_.classList.remove(this.CssClasses_.IS_ANIMATING);
3984 } else {
3985 this.rippleElement_.classList.add(this.CssClasses_.IS_ANIMATING);
3986 }
3987 }
3988 };
3989 /**
3990 * Handles an animation frame.
3991 */
3992 this.animFrameHandler = function () {
3993 if (this.frameCount_-- > 0) {
3994 window.requestAnimationFrame(this.animFrameHandler.bind(this));
3995 } else {
3996 this.setRippleStyles(false);
3997 }
3998 };
3999 }
4000 }
4001 };
4002// The component registers itself. It can assume componentHandler is available
4003// in the global scope.
4004 componentHandler.register({
4005 constructor: MaterialRipple,
4006 classAsString: 'MaterialRipple',
4007 cssClass: 'mdl-js-ripple-effect',
4008 widget: false
4009 });
4010}());