liquid_feedback_frontend

view static/mdl/material.js @ 1800:b87997219042

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

Impressum / About Us