vendor/assets/javascripts/material.js in material_design_lite-rails-1.0.6 vs vendor/assets/javascripts/material.js in material_design_lite-rails-1.1.0
- old
+ new
@@ -92,11 +92,10 @@
var registeredComponents_ = [];
/** @type {!Array<componentHandler.Component>} */
var createdComponents_ = [];
- var downgradeMethod_ = 'mdlDowngrade';
var componentConfigProperty_ = 'mdlComponentConfigInternal_';
/**
* Searches registered components for a class we are interested in using.
* Optionally replaces a match with passed object if specified.
@@ -226,11 +225,11 @@
// Assign per element instance for control over API
element[registeredClass.className] = instance;
}
} else {
throw new Error(
- 'Unable to find a registered component for the given class.');
+ 'Unable to find a registered component for the given class.');
}
var ev = document.createEvent('Events');
ev.initEvent('mdl-componentupgraded', true, true);
element.dispatchEvent(ev);
@@ -337,50 +336,28 @@
upgradeDomInternal(registeredComponents_[n].className);
}
}
/**
- * Finds a created component by a given DOM node.
- *
- * @param {!Node} node
- * @return {*}
- */
- function findCreatedComponentByNodeInternal(node) {
- for (var n = 0; n < createdComponents_.length; n++) {
- var component = createdComponents_[n];
- if (component.element_ === node) {
- return component;
- }
- }
- }
-
- /**
* Check the component for the downgrade method.
* Execute if found.
* Remove component from createdComponents list.
*
- * @param {*} component
+ * @param {?componentHandler.Component} component
*/
function deconstructComponentInternal(component) {
- if (component &&
- component[componentConfigProperty_]
- .classConstructor.prototype
- .hasOwnProperty(downgradeMethod_)) {
- component[downgradeMethod_]();
- var componentIndex = createdComponents_.indexOf(component);
- createdComponents_.splice(componentIndex, 1);
+ var componentIndex = createdComponents_.indexOf(component);
+ createdComponents_.splice(componentIndex, 1);
- var upgrades = component.element_.getAttribute('data-upgraded').split(',');
- var componentPlace = upgrades.indexOf(
- component[componentConfigProperty_].classAsString);
- upgrades.splice(componentPlace, 1);
- component.element_.setAttribute('data-upgraded', upgrades.join(','));
+ var upgrades = component.element_.getAttribute('data-upgraded').split(',');
+ var componentPlace = upgrades.indexOf(component[componentConfigProperty_].classAsString);
+ upgrades.splice(componentPlace, 1);
+ component.element_.setAttribute('data-upgraded', upgrades.join(','));
- var ev = document.createEvent('Events');
- ev.initEvent('mdl-componentdowngraded', true, true);
- component.element_.dispatchEvent(ev);
- }
+ var ev = document.createEvent('Events');
+ ev.initEvent('mdl-componentdowngraded', true, true);
+ component.element_.dispatchEvent(ev);
}
/**
* Downgrade either a given node, an array of nodes, or a NodeList.
*
@@ -390,11 +367,13 @@
/**
* Auxiliary function to downgrade a single node.
* @param {!Node} node the node to be downgraded
*/
var downgradeNode = function(node) {
- deconstructComponentInternal(findCreatedComponentByNodeInternal(node));
+ createdComponents_.filter(function(item) {
+ return item.element_ === node;
+ }).forEach(deconstructComponentInternal);
};
if (nodes instanceof Array || nodes instanceof NodeList) {
for (var n = 0; n < nodes.length; n++) {
downgradeNode(nodes[n]);
}
@@ -640,29 +619,10 @@
this.boundButtonBlurHandler = this.blurHandler_.bind(this);
this.element_.addEventListener('mouseup', this.boundButtonBlurHandler);
this.element_.addEventListener('mouseleave', this.boundButtonBlurHandler);
}
};
-/**
- * Downgrade the element.
- *
- * @private
- */
-MaterialButton.prototype.mdlDowngrade_ = function () {
- if (this.rippleElement_) {
- this.rippleElement_.removeEventListener('mouseup', this.boundRippleBlurHandler);
- }
- this.element_.removeEventListener('mouseup', this.boundButtonBlurHandler);
- this.element_.removeEventListener('mouseleave', this.boundButtonBlurHandler);
-};
-/**
- * Public alias for the downgrade method.
- *
- * @public
- */
-MaterialButton.prototype.mdlDowngrade = MaterialButton.prototype.mdlDowngrade_;
-MaterialButton.prototype['mdlDowngrade'] = MaterialButton.prototype.mdlDowngrade;
// The component registers itself. It can assume componentHandler is available
// in the global scope.
componentHandler.register({
constructor: MaterialButton,
classAsString: 'MaterialButton',
@@ -891,31 +851,10 @@
this.element_.addEventListener('mouseup', this.boundElementMouseUp);
this.updateClasses_();
this.element_.classList.add(this.CssClasses_.IS_UPGRADED);
}
};
-/**
- * Downgrade the component.
- *
- * @private
- */
-MaterialCheckbox.prototype.mdlDowngrade_ = function () {
- if (this.rippleContainerElement_) {
- this.rippleContainerElement_.removeEventListener('mouseup', this.boundRippleMouseUp);
- }
- this.inputElement_.removeEventListener('change', this.boundInputOnChange);
- this.inputElement_.removeEventListener('focus', this.boundInputOnFocus);
- this.inputElement_.removeEventListener('blur', this.boundInputOnBlur);
- this.element_.removeEventListener('mouseup', this.boundElementMouseUp);
-};
-/**
- * Public alias for the downgrade method.
- *
- * @public
- */
-MaterialCheckbox.prototype.mdlDowngrade = MaterialCheckbox.prototype.mdlDowngrade_;
-MaterialCheckbox.prototype['mdlDowngrade'] = MaterialCheckbox.prototype.mdlDowngrade;
// The component registers itself. It can assume componentHandler is available
// in the global scope.
componentHandler.register({
constructor: MaterialCheckbox,
classAsString: 'MaterialCheckbox',
@@ -1131,31 +1070,10 @@
this.element_.addEventListener('mouseup', this.boundElementOnMouseUp);
this.updateClasses_();
this.element_.classList.add('is-upgraded');
}
};
-/**
- * Downgrade the component
- *
- * @private
- */
-MaterialIconToggle.prototype.mdlDowngrade_ = function () {
- if (this.rippleContainerElement_) {
- this.rippleContainerElement_.removeEventListener('mouseup', this.boundRippleMouseUp);
- }
- this.inputElement_.removeEventListener('change', this.boundInputOnChange);
- this.inputElement_.removeEventListener('focus', this.boundInputOnFocus);
- this.inputElement_.removeEventListener('blur', this.boundInputOnBlur);
- this.element_.removeEventListener('mouseup', this.boundElementOnMouseUp);
-};
-/**
- * Public alias for the downgrade method.
- *
- * @public
- */
-MaterialIconToggle.prototype.mdlDowngrade = MaterialIconToggle.prototype.mdlDowngrade_;
-MaterialIconToggle.prototype['mdlDowngrade'] = MaterialIconToggle.prototype.mdlDowngrade;
// The component registers itself. It can assume componentHandler is available
// in the global scope.
componentHandler.register({
constructor: MaterialIconToggle,
classAsString: 'MaterialIconToggle',
@@ -1264,11 +1182,11 @@
var outline = document.createElement('div');
outline.classList.add(this.CssClasses_.OUTLINE);
this.outline_ = outline;
container.insertBefore(outline, this.element_);
// Find the "for" element and bind events to it.
- var forElId = this.element_.getAttribute('for');
+ var forElId = this.element_.getAttribute('for') || this.element_.getAttribute('data-mdl-for');
var forEl = null;
if (forElId) {
forEl = document.getElementById(forElId);
if (forEl) {
this.forElement_ = forEl;
@@ -1457,23 +1375,26 @@
// Default: do not clip (same as clipping to the top left corner).
this.element_.style.clip = '';
}
};
/**
+ * Cleanup function to remove animation listeners.
+ *
+ * @param {Event} evt
+ * @private
+ */
+MaterialMenu.prototype.removeAnimationEndListener_ = function (evt) {
+ evt.target.classList.remove(MaterialMenu.prototype.CssClasses_.IS_ANIMATING);
+};
+/**
* Adds an event listener to clean up after the animation ends.
*
* @private
*/
MaterialMenu.prototype.addAnimationEndListener_ = function () {
- var cleanup = function () {
- this.element_.removeEventListener('transitionend', cleanup);
- this.element_.removeEventListener('webkitTransitionEnd', cleanup);
- this.element_.classList.remove(this.CssClasses_.IS_ANIMATING);
- }.bind(this);
- // Remove animation class once the transition is done.
- this.element_.addEventListener('transitionend', cleanup);
- this.element_.addEventListener('webkitTransitionEnd', cleanup);
+ this.element_.addEventListener('transitionend', this.removeAnimationEndListener_);
+ this.element_.addEventListener('webkitTransitionEnd', this.removeAnimationEndListener_);
};
/**
* Displays the menu.
*
* @public
@@ -1537,11 +1458,11 @@
MaterialMenu.prototype.hide = function () {
if (this.element_ && this.container_ && this.outline_) {
var items = this.element_.querySelectorAll('.' + this.CssClasses_.ITEM);
// Remove all transition delays; menu items fade out concurrently.
for (var i = 0; i < items.length; i++) {
- items[i].style.transitionDelay = null;
+ items[i].style.removeProperty('transition-delay');
}
// Measure the inner element.
var rect = this.element_.getBoundingClientRect();
var height = rect.height;
var width = rect.width;
@@ -1566,29 +1487,10 @@
} else {
this.show(evt);
}
};
MaterialMenu.prototype['toggle'] = MaterialMenu.prototype.toggle;
-/**
- * Downgrade the component.
- *
- * @private
- */
-MaterialMenu.prototype.mdlDowngrade_ = function () {
- var items = this.element_.querySelectorAll('.' + this.CssClasses_.ITEM);
- for (var i = 0; i < items.length; i++) {
- items[i].removeEventListener('click', this.boundItemClick_);
- items[i].removeEventListener('keydown', this.boundItemKeydown_);
- }
-};
-/**
- * Public alias for the downgrade method.
- *
- * @public
- */
-MaterialMenu.prototype.mdlDowngrade = MaterialMenu.prototype.mdlDowngrade_;
-MaterialMenu.prototype['mdlDowngrade'] = MaterialMenu.prototype.mdlDowngrade;
// The component registers itself. It can assume componentHandler is available
// in the global scope.
componentHandler.register({
constructor: MaterialMenu,
classAsString: 'MaterialMenu',
@@ -1686,27 +1588,10 @@
this.bufferbar_.style.width = '100%';
this.auxbar_.style.width = '0%';
this.element_.classList.add('is-upgraded');
}
};
-/**
- * Downgrade the component
- *
- * @private
- */
-MaterialProgress.prototype.mdlDowngrade_ = function () {
- while (this.element_.firstChild) {
- this.element_.removeChild(this.element_.firstChild);
- }
-};
-/**
- * Public alias for the downgrade method.
- *
- * @public
- */
-MaterialProgress.prototype.mdlDowngrade = MaterialProgress.prototype.mdlDowngrade_;
-MaterialProgress.prototype['mdlDowngrade'] = MaterialProgress.prototype.mdlDowngrade;
// The component registers itself. It can assume componentHandler is available
// in the global scope.
componentHandler.register({
constructor: MaterialProgress,
classAsString: 'MaterialProgress',
@@ -1941,33 +1826,10 @@
this.element_.addEventListener('mouseup', this.boundMouseUpHandler_);
this.updateClasses_();
this.element_.classList.add(this.CssClasses_.IS_UPGRADED);
}
};
-/**
- * Downgrade the element.
- *
- * @private
- */
-MaterialRadio.prototype.mdlDowngrade_ = function () {
- var rippleContainer = this.element_.querySelector('.' + this.CssClasses_.RIPPLE_CONTAINER);
- this.btnElement_.removeEventListener('change', this.boundChangeHandler_);
- this.btnElement_.removeEventListener('focus', this.boundFocusHandler_);
- this.btnElement_.removeEventListener('blur', this.boundBlurHandler_);
- this.element_.removeEventListener('mouseup', this.boundMouseUpHandler_);
- if (rippleContainer) {
- rippleContainer.removeEventListener('mouseup', this.boundMouseUpHandler_);
- this.element_.removeChild(rippleContainer);
- }
-};
-/**
- * Public alias for the downgrade method.
- *
- * @public
- */
-MaterialRadio.prototype.mdlDowngrade = MaterialRadio.prototype.mdlDowngrade_;
-MaterialRadio.prototype['mdlDowngrade'] = MaterialRadio.prototype.mdlDowngrade;
// The component registers itself. It can assume componentHandler is available
// in the global scope.
componentHandler.register({
constructor: MaterialRadio,
classAsString: 'MaterialRadio',
@@ -2179,34 +2041,188 @@
this.element_.parentElement.addEventListener('mousedown', this.boundContainerMouseDownHandler);
this.updateValueStyles_();
this.element_.classList.add(this.CssClasses_.IS_UPGRADED);
}
};
+// The component registers itself. It can assume componentHandler is available
+// in the global scope.
+componentHandler.register({
+ constructor: MaterialSlider,
+ classAsString: 'MaterialSlider',
+ cssClass: 'mdl-js-slider',
+ widget: true
+});
/**
- * Downgrade the component
+ * Copyright 2015 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/**
+ * Class constructor for Snackbar MDL component.
+ * Implements MDL component design pattern defined at:
+ * https://github.com/jasonmayes/mdl-component-design-pattern
*
+ * @constructor
+ * @param {HTMLElement} element The element that will be upgraded.
+ */
+var MaterialSnackbar = function MaterialSnackbar(element) {
+ this.element_ = element;
+ this.textElement_ = this.element_.querySelector('.' + this.cssClasses_.MESSAGE);
+ this.actionElement_ = this.element_.querySelector('.' + this.cssClasses_.ACTION);
+ if (!this.textElement_) {
+ throw new Error('There must be a message element for a snackbar.');
+ }
+ if (!this.actionElement_) {
+ throw new Error('There must be an action element for a snackbar.');
+ }
+ this.active = false;
+ this.actionHandler_ = undefined;
+ this.message_ = undefined;
+ this.actionText_ = undefined;
+ this.queuedNotifications_ = [];
+ this.setActionHidden_(true);
+};
+window['MaterialSnackbar'] = MaterialSnackbar;
+/**
+ * Store constants in one place so they can be updated easily.
+ *
+ * @enum {string | number}
* @private
*/
-MaterialSlider.prototype.mdlDowngrade_ = function () {
- this.element_.removeEventListener('input', this.boundInputHandler);
- this.element_.removeEventListener('change', this.boundChangeHandler);
- this.element_.removeEventListener('mouseup', this.boundMouseUpHandler);
- this.element_.parentElement.removeEventListener('mousedown', this.boundContainerMouseDownHandler);
+MaterialSnackbar.prototype.Constant_ = {
+ // The duration of the snackbar show/hide animation, in ms.
+ ANIMATION_LENGTH: 250
};
/**
- * Public alias for the downgrade method.
+ * Store strings for class names defined by this component that are used in
+ * JavaScript. This allows us to simply change it in one place should we
+ * decide to modify at a later date.
*
+ * @enum {string}
+ * @private
+ */
+MaterialSnackbar.prototype.cssClasses_ = {
+ SNACKBAR: 'mdl-snackbar',
+ MESSAGE: 'mdl-snackbar__text',
+ ACTION: 'mdl-snackbar__action',
+ ACTIVE: 'mdl-snackbar--active'
+};
+/**
+ * Display the snackbar.
+ *
+ * @private
+ */
+MaterialSnackbar.prototype.displaySnackbar_ = function () {
+ this.element_.setAttribute('aria-hidden', 'true');
+ if (this.actionHandler_) {
+ this.actionElement_.textContent = this.actionText_;
+ this.actionElement_.addEventListener('click', this.actionHandler_);
+ this.setActionHidden_(false);
+ }
+ this.textElement_.textContent = this.message_;
+ this.element_.classList.add(this.cssClasses_.ACTIVE);
+ this.element_.setAttribute('aria-hidden', 'false');
+ setTimeout(this.cleanup_.bind(this), this.timeout_);
+};
+/**
+ * Show the snackbar.
+ *
+ * @param {Object} data The data for the notification.
* @public
*/
-MaterialSlider.prototype.mdlDowngrade = MaterialSlider.prototype.mdlDowngrade_;
-MaterialSlider.prototype['mdlDowngrade'] = MaterialSlider.prototype.mdlDowngrade;
+MaterialSnackbar.prototype.showSnackbar = function (data) {
+ if (data === undefined) {
+ throw new Error('Please provide a data object with at least a message to display.');
+ }
+ if (data['message'] === undefined) {
+ throw new Error('Please provide a message to be displayed.');
+ }
+ if (data['actionHandler'] && !data['actionText']) {
+ throw new Error('Please provide action text with the handler.');
+ }
+ if (this.active) {
+ this.queuedNotifications_.push(data);
+ } else {
+ this.active = true;
+ this.message_ = data['message'];
+ if (data['timeout']) {
+ this.timeout_ = data['timeout'];
+ } else {
+ this.timeout_ = 2750;
+ }
+ if (data['actionHandler']) {
+ this.actionHandler_ = data['actionHandler'];
+ }
+ if (data['actionText']) {
+ this.actionText_ = data['actionText'];
+ }
+ this.displaySnackbar_();
+ }
+};
+MaterialSnackbar.prototype['showSnackbar'] = MaterialSnackbar.prototype.showSnackbar;
+/**
+ * Check if the queue has items within it.
+ * If it does, display the next entry.
+ *
+ * @private
+ */
+MaterialSnackbar.prototype.checkQueue_ = function () {
+ if (this.queuedNotifications_.length > 0) {
+ this.showSnackbar(this.queuedNotifications_.shift());
+ }
+};
+/**
+ * Cleanup the snackbar event listeners and accessiblity attributes.
+ *
+ * @private
+ */
+MaterialSnackbar.prototype.cleanup_ = function () {
+ this.element_.classList.remove(this.cssClasses_.ACTIVE);
+ setTimeout(function () {
+ this.element_.setAttribute('aria-hidden', 'true');
+ this.textElement_.textContent = '';
+ if (!Boolean(this.actionElement_.getAttribute('aria-hidden'))) {
+ this.setActionHidden_(true);
+ this.actionElement_.textContent = '';
+ this.actionElement_.removeEventListener('click', this.actionHandler_);
+ }
+ this.actionHandler_ = undefined;
+ this.message_ = undefined;
+ this.actionText_ = undefined;
+ this.active = false;
+ this.checkQueue_();
+ }.bind(this), this.Constant_.ANIMATION_LENGTH);
+};
+/**
+ * Set the action handler hidden state.
+ *
+ * @param {boolean} value
+ * @private
+ */
+MaterialSnackbar.prototype.setActionHidden_ = function (value) {
+ if (value) {
+ this.actionElement_.setAttribute('aria-hidden', 'true');
+ } else {
+ this.actionElement_.removeAttribute('aria-hidden');
+ }
+};
// The component registers itself. It can assume componentHandler is available
// in the global scope.
componentHandler.register({
- constructor: MaterialSlider,
- classAsString: 'MaterialSlider',
- cssClass: 'mdl-js-slider',
+ constructor: MaterialSnackbar,
+ classAsString: 'MaterialSnackbar',
+ cssClass: 'mdl-js-snackbar',
widget: true
});
/**
* @license
* Copyright 2015 Google Inc. All Rights Reserved.
@@ -2554,31 +2570,10 @@
this.element_.addEventListener('mouseup', this.boundMouseUpHandler);
this.updateClasses_();
this.element_.classList.add('is-upgraded');
}
};
-/**
- * Downgrade the component.
- *
- * @private
- */
-MaterialSwitch.prototype.mdlDowngrade_ = function () {
- if (this.rippleContainerElement_) {
- this.rippleContainerElement_.removeEventListener('mouseup', this.boundMouseUpHandler);
- }
- this.inputElement_.removeEventListener('change', this.boundChangeHandler);
- this.inputElement_.removeEventListener('focus', this.boundFocusHandler);
- this.inputElement_.removeEventListener('blur', this.boundBlurHandler);
- this.element_.removeEventListener('mouseup', this.boundMouseUpHandler);
-};
-/**
- * Public alias for the downgrade method.
- *
- * @public
- */
-MaterialSwitch.prototype.mdlDowngrade = MaterialSwitch.prototype.mdlDowngrade_;
-MaterialSwitch.prototype['mdlDowngrade'] = MaterialSwitch.prototype.mdlDowngrade;
// The component registers itself. It can assume componentHandler is available
// in the global scope.
componentHandler.register({
constructor: MaterialSwitch,
classAsString: 'MaterialSwitch',
@@ -2605,11 +2600,11 @@
* Class constructor for Tabs MDL component.
* Implements MDL component design pattern defined at:
* https://github.com/jasonmayes/mdl-component-design-pattern
*
* @constructor
- * @param {HTMLElement} element The element that will be upgraded.
+ * @param {Element} element The element that will be upgraded.
*/
var MaterialTabs = function MaterialTabs(element) {
// Stores the HTML element.
this.element_ = element;
// Initialize instance.
@@ -2689,11 +2684,11 @@
};
/**
* Constructor for an individual tab.
*
* @constructor
- * @param {HTMLElement} tab The HTML element for the tab.
+ * @param {Element} tab The HTML element for the tab.
* @param {MaterialTabs} ctx The MaterialTabs object that owns the tab.
*/
function MaterialTab(tab, ctx) {
if (tab) {
if (ctx.element_.classList.contains(ctx.CssClasses_.MDL_JS_RIPPLE_EFFECT)) {
@@ -2812,18 +2807,28 @@
*/
MaterialTextfield.prototype.onBlur_ = function (event) {
this.element_.classList.remove(this.CssClasses_.IS_FOCUSED);
};
/**
+ * Handle reset event from out side.
+ *
+ * @param {Event} event The event that fired.
+ * @private
+ */
+MaterialTextfield.prototype.onReset_ = function (event) {
+ this.updateClasses_();
+};
+/**
* Handle class updates.
*
* @private
*/
MaterialTextfield.prototype.updateClasses_ = function () {
this.checkDisabled();
this.checkValidity();
this.checkDirty();
+ this.checkFocus();
};
// Public methods.
/**
* Check the disabled state and update field accordingly.
*
@@ -2836,10 +2841,23 @@
this.element_.classList.remove(this.CssClasses_.IS_DISABLED);
}
};
MaterialTextfield.prototype['checkDisabled'] = MaterialTextfield.prototype.checkDisabled;
/**
+ * Check the focus state and update field accordingly.
+ *
+ * @public
+ */
+MaterialTextfield.prototype.checkFocus = function () {
+ if (Boolean(this.element_.querySelector(':focus'))) {
+ this.element_.classList.add(this.CssClasses_.IS_FOCUSED);
+ } else {
+ this.element_.classList.remove(this.CssClasses_.IS_FOCUSED);
+ }
+};
+MaterialTextfield.prototype['checkFocus'] = MaterialTextfield.prototype.checkFocus;
+/**
* Check the validity state and update field accordingly.
*
* @public
*/
MaterialTextfield.prototype.checkValidity = function () {
@@ -2911,13 +2929,15 @@
}
}
this.boundUpdateClassesHandler = this.updateClasses_.bind(this);
this.boundFocusHandler = this.onFocus_.bind(this);
this.boundBlurHandler = this.onBlur_.bind(this);
+ this.boundResetHandler = this.onReset_.bind(this);
this.input_.addEventListener('input', this.boundUpdateClassesHandler);
this.input_.addEventListener('focus', this.boundFocusHandler);
this.input_.addEventListener('blur', this.boundBlurHandler);
+ this.input_.addEventListener('reset', this.boundResetHandler);
if (this.maxRows !== this.Constant_.NO_MAX_ROWS) {
// TODO: This should handle pasting multi line text.
// Currently doesn't.
this.boundKeyDownHandler = this.onKeyDown_.bind(this);
this.input_.addEventListener('keydown', this.boundKeyDownHandler);
@@ -2926,33 +2946,17 @@
this.updateClasses_();
this.element_.classList.add(this.CssClasses_.IS_UPGRADED);
if (invalid) {
this.element_.classList.add(this.CssClasses_.IS_INVALID);
}
+ if (this.input_.hasAttribute('autofocus')) {
+ this.element_.focus();
+ this.checkFocus();
+ }
}
}
};
-/**
- * Downgrade the component
- *
- * @private
- */
-MaterialTextfield.prototype.mdlDowngrade_ = function () {
- this.input_.removeEventListener('input', this.boundUpdateClassesHandler);
- this.input_.removeEventListener('focus', this.boundFocusHandler);
- this.input_.removeEventListener('blur', this.boundBlurHandler);
- if (this.boundKeyDownHandler) {
- this.input_.removeEventListener('keydown', this.boundKeyDownHandler);
- }
-};
-/**
- * Public alias for the downgrade method.
- *
- * @public
- */
-MaterialTextfield.prototype.mdlDowngrade = MaterialTextfield.prototype.mdlDowngrade_;
-MaterialTextfield.prototype['mdlDowngrade'] = MaterialTextfield.prototype.mdlDowngrade;
// The component registers itself. It can assume componentHandler is available
// in the global scope.
componentHandler.register({
constructor: MaterialTextfield,
classAsString: 'MaterialTextfield',
@@ -3002,45 +3006,65 @@
* decide to modify at a later date.
*
* @enum {string}
* @private
*/
-MaterialTooltip.prototype.CssClasses_ = { IS_ACTIVE: 'is-active' };
+MaterialTooltip.prototype.CssClasses_ = {
+ IS_ACTIVE: 'is-active',
+ BOTTOM: 'mdl-tooltip--bottom',
+ LEFT: 'mdl-tooltip--left',
+ RIGHT: 'mdl-tooltip--right',
+ TOP: 'mdl-tooltip--top'
+};
/**
* Handle mouseenter for tooltip.
*
* @param {Event} event The event that fired.
* @private
*/
MaterialTooltip.prototype.handleMouseEnter_ = function (event) {
- event.stopPropagation();
var props = event.target.getBoundingClientRect();
var left = props.left + props.width / 2;
+ var top = props.top + props.height / 2;
var marginLeft = -1 * (this.element_.offsetWidth / 2);
- if (left + marginLeft < 0) {
- this.element_.style.left = 0;
- this.element_.style.marginLeft = 0;
+ var marginTop = -1 * (this.element_.offsetHeight / 2);
+ if (this.element_.classList.contains(this.CssClasses_.LEFT) || this.element_.classList.contains(this.CssClasses_.RIGHT)) {
+ left = props.width / 2;
+ if (top + marginTop < 0) {
+ this.element_.style.top = 0;
+ this.element_.style.marginTop = 0;
+ } else {
+ this.element_.style.top = top + 'px';
+ this.element_.style.marginTop = marginTop + 'px';
+ }
} else {
- this.element_.style.left = left + 'px';
- this.element_.style.marginLeft = marginLeft + 'px';
+ if (left + marginLeft < 0) {
+ this.element_.style.left = 0;
+ this.element_.style.marginLeft = 0;
+ } else {
+ this.element_.style.left = left + 'px';
+ this.element_.style.marginLeft = marginLeft + 'px';
+ }
}
- this.element_.style.top = props.top + props.height + 10 + 'px';
+ if (this.element_.classList.contains(this.CssClasses_.TOP)) {
+ this.element_.style.top = props.top - this.element_.offsetHeight - 10 + 'px';
+ } else if (this.element_.classList.contains(this.CssClasses_.RIGHT)) {
+ this.element_.style.left = props.left + props.width + 10 + 'px';
+ } else if (this.element_.classList.contains(this.CssClasses_.LEFT)) {
+ this.element_.style.left = props.left - this.element_.offsetWidth - 10 + 'px';
+ } else {
+ this.element_.style.top = props.top + props.height + 10 + 'px';
+ }
this.element_.classList.add(this.CssClasses_.IS_ACTIVE);
- window.addEventListener('scroll', this.boundMouseLeaveHandler, false);
- window.addEventListener('touchmove', this.boundMouseLeaveHandler, false);
};
/**
* Handle mouseleave for tooltip.
*
- * @param {Event} event The event that fired.
* @private
*/
-MaterialTooltip.prototype.handleMouseLeave_ = function (event) {
- event.stopPropagation();
+MaterialTooltip.prototype.handleMouseLeave_ = function () {
this.element_.classList.remove(this.CssClasses_.IS_ACTIVE);
- window.removeEventListener('scroll', this.boundMouseLeaveHandler);
- window.removeEventListener('touchmove', this.boundMouseLeaveHandler, false);
};
/**
* Initialize element.
*/
MaterialTooltip.prototype.init = function () {
@@ -3048,44 +3072,23 @@
var forElId = this.element_.getAttribute('for');
if (forElId) {
this.forElement_ = document.getElementById(forElId);
}
if (this.forElement_) {
- // Tabindex needs to be set for `blur` events to be emitted
+ // It's left here because it prevents accidental text selection on Android
if (!this.forElement_.hasAttribute('tabindex')) {
this.forElement_.setAttribute('tabindex', '0');
}
this.boundMouseEnterHandler = this.handleMouseEnter_.bind(this);
this.boundMouseLeaveHandler = this.handleMouseLeave_.bind(this);
this.forElement_.addEventListener('mouseenter', this.boundMouseEnterHandler, false);
- this.forElement_.addEventListener('click', this.boundMouseEnterHandler, false);
- this.forElement_.addEventListener('blur', this.boundMouseLeaveHandler);
- this.forElement_.addEventListener('touchstart', this.boundMouseEnterHandler, false);
- this.forElement_.addEventListener('mouseleave', this.boundMouseLeaveHandler);
+ this.forElement_.addEventListener('touchend', this.boundMouseEnterHandler, false);
+ this.forElement_.addEventListener('mouseleave', this.boundMouseLeaveHandler, false);
+ window.addEventListener('touchstart', this.boundMouseLeaveHandler);
}
}
};
-/**
- * Downgrade the component
- *
- * @private
- */
-MaterialTooltip.prototype.mdlDowngrade_ = function () {
- if (this.forElement_) {
- this.forElement_.removeEventListener('mouseenter', this.boundMouseEnterHandler, false);
- this.forElement_.removeEventListener('click', this.boundMouseEnterHandler, false);
- this.forElement_.removeEventListener('touchstart', this.boundMouseEnterHandler, false);
- this.forElement_.removeEventListener('mouseleave', this.boundMouseLeaveHandler);
- }
-};
-/**
- * Public alias for the downgrade method.
- *
- * @public
- */
-MaterialTooltip.prototype.mdlDowngrade = MaterialTooltip.prototype.mdlDowngrade_;
-MaterialTooltip.prototype['mdlDowngrade'] = MaterialTooltip.prototype.mdlDowngrade;
// The component registers itself. It can assume componentHandler is available
// in the global scope.
componentHandler.register({
constructor: MaterialTooltip,
classAsString: 'MaterialTooltip',
@@ -3128,15 +3131,26 @@
* @private
*/
MaterialLayout.prototype.Constant_ = {
MAX_WIDTH: '(max-width: 1024px)',
TAB_SCROLL_PIXELS: 100,
- MENU_ICON: 'menu',
+ MENU_ICON: '',
CHEVRON_LEFT: 'chevron_left',
CHEVRON_RIGHT: 'chevron_right'
};
/**
+ * Keycodes, for code readability.
+ *
+ * @enum {number}
+ * @private
+ */
+MaterialLayout.prototype.Keycodes_ = {
+ ENTER: 13,
+ ESCAPE: 27,
+ SPACE: 32
+};
+/**
* Modes.
*
* @enum {number}
* @private
*/
@@ -3208,10 +3222,21 @@
this.header_.classList.remove(this.CssClasses_.IS_COMPACT);
this.header_.classList.add(this.CssClasses_.IS_ANIMATING);
}
};
/**
+ * Handles a keyboard event on the drawer.
+ *
+ * @param {Event} evt The event that fired.
+ * @private
+ */
+MaterialLayout.prototype.keyboardEventHandler_ = function (evt) {
+ if (evt.keyCode === this.Keycodes_.ESCAPE) {
+ this.toggleDrawer();
+ }
+};
+/**
* Handles changes in screen size.
*
* @private
*/
MaterialLayout.prototype.screenSizeHandler_ = function () {
@@ -3225,17 +3250,26 @@
this.obfuscator_.classList.remove(this.CssClasses_.IS_DRAWER_OPEN);
}
}
};
/**
- * Handles toggling of the drawer.
+ * Handles events of drawer button.
*
+ * @param {Event} evt The event that fired.
* @private
*/
-MaterialLayout.prototype.drawerToggleHandler_ = function () {
- this.drawer_.classList.toggle(this.CssClasses_.IS_DRAWER_OPEN);
- this.obfuscator_.classList.toggle(this.CssClasses_.IS_DRAWER_OPEN);
+MaterialLayout.prototype.drawerToggleHandler_ = function (evt) {
+ if (evt && evt.type === 'keydown') {
+ if (evt.keyCode === this.Keycodes_.SPACE || evt.keyCode === this.Keycodes_.ENTER) {
+ // prevent scrolling in drawer nav
+ evt.preventDefault();
+ } else {
+ // prevent other keys
+ return;
+ }
+ }
+ this.toggleDrawer();
};
/**
* Handles (un)setting the `is-animating` class
*
* @private
@@ -3273,10 +3307,29 @@
for (var j = 0; j < panels.length; j++) {
panels[j].classList.remove(this.CssClasses_.IS_ACTIVE);
}
};
/**
+ * Toggle drawer state
+ *
+ * @public
+ */
+MaterialLayout.prototype.toggleDrawer = function () {
+ var drawerButton = this.element_.querySelector('.' + this.CssClasses_.DRAWER_BTN);
+ this.drawer_.classList.toggle(this.CssClasses_.IS_DRAWER_OPEN);
+ this.obfuscator_.classList.toggle(this.CssClasses_.IS_DRAWER_OPEN);
+ // Set accessibility properties.
+ if (this.drawer_.classList.contains(this.CssClasses_.IS_DRAWER_OPEN)) {
+ this.drawer_.setAttribute('aria-hidden', 'false');
+ drawerButton.setAttribute('aria-expanded', 'true');
+ } else {
+ this.drawer_.setAttribute('aria-hidden', 'true');
+ drawerButton.setAttribute('aria-expanded', 'false');
+ }
+};
+MaterialLayout.prototype['toggleDrawer'] = MaterialLayout.prototype.toggleDrawer;
+/**
* Initialize element.
*/
MaterialLayout.prototype.init = function () {
if (this.element_) {
var container = document.createElement('div');
@@ -3296,10 +3349,20 @@
}
if (child.classList && child.classList.contains(this.CssClasses_.CONTENT)) {
this.content_ = child;
}
}
+ window.addEventListener('pageshow', function (e) {
+ if (e.persisted) {
+ // when page is loaded from back/forward cache
+ // trigger repaint to let layout scroll in safari
+ this.element_.style.overflowY = 'hidden';
+ requestAnimationFrame(function () {
+ this.element_.style.overflowY = '';
+ }.bind(this));
+ }
+ }.bind(this), false);
if (this.header_) {
this.tabBar_ = this.header_.querySelector('.' + this.CssClasses_.TAB_BAR);
}
var mode = this.Mode_.STANDARD;
if (this.header_) {
@@ -3334,24 +3397,28 @@
// Add drawer toggling button to our layout, if we have an openable drawer.
if (this.drawer_) {
var drawerButton = this.element_.querySelector('.' + this.CssClasses_.DRAWER_BTN);
if (!drawerButton) {
drawerButton = document.createElement('div');
+ drawerButton.setAttribute('aria-expanded', 'false');
+ drawerButton.setAttribute('role', 'button');
+ drawerButton.setAttribute('tabindex', '0');
drawerButton.classList.add(this.CssClasses_.DRAWER_BTN);
var drawerButtonIcon = document.createElement('i');
drawerButtonIcon.classList.add(this.CssClasses_.ICON);
- drawerButtonIcon.textContent = this.Constant_.MENU_ICON;
+ drawerButtonIcon.innerHTML = this.Constant_.MENU_ICON;
drawerButton.appendChild(drawerButtonIcon);
}
if (this.drawer_.classList.contains(this.CssClasses_.ON_LARGE_SCREEN)) {
//If drawer has ON_LARGE_SCREEN class then add it to the drawer toggle button as well.
drawerButton.classList.add(this.CssClasses_.ON_LARGE_SCREEN);
} else if (this.drawer_.classList.contains(this.CssClasses_.ON_SMALL_SCREEN)) {
//If drawer has ON_SMALL_SCREEN class then add it to the drawer toggle button as well.
drawerButton.classList.add(this.CssClasses_.ON_SMALL_SCREEN);
}
drawerButton.addEventListener('click', this.drawerToggleHandler_.bind(this));
+ drawerButton.addEventListener('keydown', this.drawerToggleHandler_.bind(this));
// Add a class if the layout has a drawer, for altering the left padding.
// Adds the HAS_DRAWER to the elements since this.header_ may or may
// not be present.
this.element_.classList.add(this.CssClasses_.HAS_DRAWER);
// If we have a fixed header, add the button to the header rather than
@@ -3364,10 +3431,12 @@
var obfuscator = document.createElement('div');
obfuscator.classList.add(this.CssClasses_.OBFUSCATOR);
this.element_.appendChild(obfuscator);
obfuscator.addEventListener('click', this.drawerToggleHandler_.bind(this));
this.obfuscator_ = obfuscator;
+ this.drawer_.addEventListener('keydown', this.keyboardEventHandler_.bind(this));
+ this.drawer_.setAttribute('aria-hidden', 'true');
}
// Keep an eye on screen size, and add/remove auxiliary class for styling
// of small screens.
this.screenSizeMediaQuery_ = window.matchMedia(this.Constant_.MAX_WIDTH);
this.screenSizeMediaQuery_.addListener(this.screenSizeHandler_.bind(this));
@@ -3439,29 +3508,48 @@
* @param {!Array<HTMLElement>} tabs Array with HTML elements for all tabs.
* @param {!Array<HTMLElement>} panels Array with HTML elements for all panels.
* @param {MaterialLayout} layout The MaterialLayout object that owns the tab.
*/
function MaterialLayoutTab(tab, tabs, panels, layout) {
+ /**
+ * Auxiliary method to programmatically select a tab in the UI.
+ */
+ function selectTab() {
+ var href = tab.href.split('#')[1];
+ var panel = layout.content_.querySelector('#' + href);
+ layout.resetTabState_(tabs);
+ layout.resetPanelState_(panels);
+ tab.classList.add(layout.CssClasses_.IS_ACTIVE);
+ panel.classList.add(layout.CssClasses_.IS_ACTIVE);
+ }
if (layout.tabBar_.classList.contains(layout.CssClasses_.JS_RIPPLE_EFFECT)) {
var rippleContainer = document.createElement('span');
rippleContainer.classList.add(layout.CssClasses_.RIPPLE_CONTAINER);
rippleContainer.classList.add(layout.CssClasses_.JS_RIPPLE_EFFECT);
var ripple = document.createElement('span');
ripple.classList.add(layout.CssClasses_.RIPPLE);
rippleContainer.appendChild(ripple);
tab.appendChild(rippleContainer);
}
tab.addEventListener('click', function (e) {
+ if (tab.getAttribute('href').charAt(0) === '#') {
+ e.preventDefault();
+ selectTab();
+ }
+ });
+ tab.show = selectTab;
+ tab.addEventListener('click', function (e) {
e.preventDefault();
var href = tab.href.split('#')[1];
var panel = layout.content_.querySelector('#' + href);
layout.resetTabState_(tabs);
layout.resetPanelState_(panels);
tab.classList.add(layout.CssClasses_.IS_ACTIVE);
panel.classList.add(layout.CssClasses_.IS_ACTIVE);
});
}
+window['MaterialLayoutTab'] = MaterialLayoutTab;
// The component registers itself. It can assume componentHandler is available
// in the global scope.
componentHandler.register({
constructor: MaterialLayout,
classAsString: 'MaterialLayout',
@@ -3487,11 +3575,11 @@
* Class constructor for Data Table Card MDL component.
* Implements MDL component design pattern defined at:
* https://github.com/jasonmayes/mdl-component-design-pattern
*
* @constructor
- * @param {HTMLElement} element The element that will be upgraded.
+ * @param {Element} element The element that will be upgraded.
*/
var MaterialDataTable = function MaterialDataTable(element) {
this.element_ = element;
// Initialize instance.
this.init();
@@ -3522,11 +3610,11 @@
/**
* Generates and returns a function that toggles the selection state of a
* single row (or multiple rows).
*
* @param {Element} checkbox Checkbox that toggles the selection state.
- * @param {HTMLElement} row Row to toggle when checkbox changes.
+ * @param {Element} row Row to toggle when checkbox changes.
* @param {(Array<Object>|NodeList)=} opt_rows Rows to toggle when checkbox changes.
* @private
*/
MaterialDataTable.prototype.selectRow_ = function (checkbox, row, opt_rows) {
if (row) {
@@ -3560,11 +3648,11 @@
};
/**
* Creates a checkbox for a single or or multiple rows and hooks up the
* event handling.
*
- * @param {HTMLElement} row Row to toggle when checkbox changes.
+ * @param {Element} row Row to toggle when checkbox changes.
* @param {(Array<Object>|NodeList)=} opt_rows Rows to toggle when checkbox changes.
* @private
*/
MaterialDataTable.prototype.createCheckbox_ = function (row, opt_rows) {
var label = document.createElement('label');
@@ -3576,38 +3664,47 @@
];
label.className = labelClasses.join(' ');
var checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.classList.add('mdl-checkbox__input');
- checkbox.addEventListener('change', this.selectRow_(checkbox, row, opt_rows));
+ if (row) {
+ checkbox.checked = row.classList.contains(this.CssClasses_.IS_SELECTED);
+ checkbox.addEventListener('change', this.selectRow_(checkbox, row));
+ } else if (opt_rows) {
+ checkbox.addEventListener('change', this.selectRow_(checkbox, null, opt_rows));
+ }
label.appendChild(checkbox);
componentHandler.upgradeElement(label, 'MaterialCheckbox');
return label;
};
/**
* Initialize element.
*/
MaterialDataTable.prototype.init = function () {
if (this.element_) {
var firstHeader = this.element_.querySelector('th');
- var rows = this.element_.querySelector('tbody').querySelectorAll('tr');
+ var bodyRows = Array.prototype.slice.call(this.element_.querySelectorAll('tbody tr'));
+ var footRows = Array.prototype.slice.call(this.element_.querySelectorAll('tfoot tr'));
+ var rows = bodyRows.concat(footRows);
if (this.element_.classList.contains(this.CssClasses_.SELECTABLE)) {
var th = document.createElement('th');
var headerCheckbox = this.createCheckbox_(null, rows);
th.appendChild(headerCheckbox);
firstHeader.parentElement.insertBefore(th, firstHeader);
for (var i = 0; i < rows.length; i++) {
var firstCell = rows[i].querySelector('td');
if (firstCell) {
var td = document.createElement('td');
- var rowCheckbox = this.createCheckbox_(rows[i]);
- td.appendChild(rowCheckbox);
+ if (rows[i].parentNode.nodeName.toUpperCase() === 'TBODY') {
+ var rowCheckbox = this.createCheckbox_(rows[i]);
+ td.appendChild(rowCheckbox);
+ }
rows[i].insertBefore(td, firstCell);
}
}
+ this.element_.classList.add(this.CssClasses_.IS_UPGRADED);
}
- this.element_.classList.add(this.CssClasses_.IS_UPGRADED);
}
};
// The component registers itself. It can assume componentHandler is available
// in the global scope.
componentHandler.register({
@@ -3725,18 +3822,17 @@
* @private
*/
MaterialRipple.prototype.upHandler_ = function (event) {
// Don't fire for the artificial "mouseup" generated by a double-click.
if (event && event.detail !== 2) {
- this.rippleElement_.classList.remove(this.CssClasses_.IS_VISIBLE);
+ // Allow a repaint to occur before removing this class, so the animation
+ // shows for tap events, which seem to trigger a mouseup too soon after
+ // mousedown.
+ window.setTimeout(function () {
+ this.rippleElement_.classList.remove(this.CssClasses_.IS_VISIBLE);
+ }.bind(this), 0);
}
- // Allow a repaint to occur before removing this class, so the animation
- // shows for tap events, which seem to trigger a mouseup too soon after
- // mousedown.
- window.setTimeout(function () {
- this.rippleElement_.classList.remove(this.CssClasses_.IS_VISIBLE);
- }.bind(this), 0);
};
/**
* Initialize element.
*/
MaterialRipple.prototype.init = function () {
@@ -3832,29 +3928,9 @@
}
};
}
}
};
-/**
- * Downgrade the component
- *
- * @private
- */
-MaterialRipple.prototype.mdlDowngrade_ = function () {
- this.element_.removeEventListener('mousedown', this.boundDownHandler);
- this.element_.removeEventListener('touchstart', this.boundDownHandler);
- this.element_.removeEventListener('mouseup', this.boundUpHandler);
- this.element_.removeEventListener('mouseleave', this.boundUpHandler);
- this.element_.removeEventListener('touchend', this.boundUpHandler);
- this.element_.removeEventListener('blur', this.boundUpHandler);
-};
-/**
- * Public alias for the downgrade method.
- *
- * @public
- */
-MaterialRipple.prototype.mdlDowngrade = MaterialRipple.prototype.mdlDowngrade_;
-MaterialRipple.prototype['mdlDowngrade'] = MaterialRipple.prototype.mdlDowngrade;
// The component registers itself. It can assume componentHandler is available
// in the global scope.
componentHandler.register({
constructor: MaterialRipple,
classAsString: 'MaterialRipple',