vendor/assets/javascripts/material.js in material_design_lite-rails-1.0.0 vs vendor/assets/javascripts/material.js in material_design_lite-rails-1.0.1

- old
+ new

@@ -15,11 +15,11 @@ * limitations under the License. */ /** * A component handler interface using the revealing module design pattern. - * More details on this pattern design here: + * More details on this design pattern here: * https://github.com/jasonmayes/mdl-component-design-pattern * @author Jason Mayes. */ /* exported componentHandler */ var componentHandler = (function() { @@ -32,12 +32,12 @@ /** * Searches registered components for a class we are interested in using. * Optionally replaces a match with passed object if specified. * @param {string} name The name of a class we want to use. - * @param {object} optReplace Optional object to replace match with. - * @return {object | false} + * @param {Object=} optReplace Optional object to replace match with. + * @return {Object | boolean} * @private */ function findRegisteredClass_(name, optReplace) { for (var i = 0; i < registeredComponents_.length; i++) { if (registeredComponents_[i].className === name) { @@ -49,82 +49,150 @@ } return false; } /** + * Returns an array of the classNames of the upgraded classes on the element. + * @param {HTMLElement} element The element to fetch data from. + * @return {Array<string>} + * @private + */ + function getUpgradedListOfElement_(element) { + var dataUpgraded = element.getAttribute('data-upgraded'); + // Use `['']` as default value to conform the `,name,name...` style. + return dataUpgraded === null ? [''] : dataUpgraded.split(','); + } + + /** + * Returns true if the given element has already been upgraded for the given + * class. + * @param {HTMLElement} element The element we want to check. + * @param {string} jsClass The class to check for. + * @return boolean + * @private + */ + function isElementUpgraded_(element, jsClass) { + var upgradedList = getUpgradedListOfElement_(element); + return upgradedList.indexOf(jsClass) !== -1; + } + + /** * Searches existing DOM for elements of our component type and upgrades them * if they have not already been upgraded. - * @param {string} jsClass the programatic name of the element class we need - * to create a new instance of. - * @param {string} cssClass the name of the CSS class elements of this type - * will have. + * @param {!string=} optJsClass the programatic name of the element class we + * need to create a new instance of. + * @param {!string=} optCssClass the name of the CSS class elements of this + * type will have. */ - function upgradeDomInternal(jsClass, cssClass) { - if (jsClass === undefined && cssClass === undefined) { + function upgradeDomInternal(optJsClass, optCssClass) { + if (optJsClass === undefined && optCssClass === undefined) { for (var i = 0; i < registeredComponents_.length; i++) { upgradeDomInternal(registeredComponents_[i].className, registeredComponents_[i].cssClass); } } else { - if (cssClass === undefined) { + var jsClass = /** @type {!string} */ (optJsClass); + if (optCssClass === undefined) { var registeredClass = findRegisteredClass_(jsClass); if (registeredClass) { - cssClass = registeredClass.cssClass; + optCssClass = registeredClass.cssClass; } } - var elements = document.querySelectorAll('.' + cssClass); + var elements = document.querySelectorAll('.' + optCssClass); for (var n = 0; n < elements.length; n++) { upgradeElementInternal(elements[n], jsClass); } } } /** * Upgrades a specific element rather than all in the DOM. * @param {HTMLElement} element The element we wish to upgrade. - * @param {string} jsClass The name of the class we want to upgrade + * @param {!string=} optJsClass Optional name of the class we want to upgrade * the element to. */ - function upgradeElementInternal(element, jsClass) { - // Only upgrade elements that have not already been upgraded. - var dataUpgraded = element.getAttribute('data-upgraded'); + function upgradeElementInternal(element, optJsClass) { + // Verify argument type. + if (!(typeof element === 'object' && element instanceof Element)) { + throw new Error('Invalid argument provided to upgrade MDL element.'); + } + var upgradedList = getUpgradedListOfElement_(element); + var classesToUpgrade = []; + // If jsClass is not provided scan the registered components to find the + // ones matching the element's CSS classList. + if (!optJsClass) { + var classList = element.classList; + registeredComponents_.forEach(function (component) { + // Match CSS & Not to be upgraded & Not upgraded. + if (classList.contains(component.cssClass) && + classesToUpgrade.indexOf(component) === -1 && + !isElementUpgraded_(element, component.className)) { + classesToUpgrade.push(component); + } + }); + } else if (!isElementUpgraded_(element, optJsClass)) { + classesToUpgrade.push(findRegisteredClass_(optJsClass)); + } - if (dataUpgraded === null || dataUpgraded.indexOf(jsClass) === -1) { - // Upgrade element. - if (dataUpgraded === null) { - dataUpgraded = ''; - } - element.setAttribute('data-upgraded', dataUpgraded + ',' + jsClass); - var registeredClass = findRegisteredClass_(jsClass); + // Upgrade the element for each classes. + for (var i = 0, n = classesToUpgrade.length, registeredClass; i < n; i++) { + registeredClass = classesToUpgrade[i]; if (registeredClass) { - // new + // Mark element as upgraded. + upgradedList.push(registeredClass.className); + element.setAttribute('data-upgraded', upgradedList.join(',')); var instance = new registeredClass.classConstructor(element); instance[componentConfigProperty_] = registeredClass; createdComponents_.push(instance); // Call any callbacks the user has registered with this component type. - registeredClass.callbacks.forEach(function(callback) { - callback(element); - }); + for (var j = 0, m = registeredClass.callbacks.length; j < m; j++) { + registeredClass.callbacks[j](element); + } if (registeredClass.widget) { // Assign per element instance for control over API - element[jsClass] = instance; + element[registeredClass.className] = instance; } } else { - throw 'Unable to find a registered component for the given class.'; + throw new Error( + 'Unable to find a registered component for the given class.'); } var ev = document.createEvent('Events'); ev.initEvent('mdl-componentupgraded', true, true); element.dispatchEvent(ev); } } /** + * Upgrades a specific list of elements rather than all in the DOM. + * @param {HTMLElement | Array<HTMLElement> | NodeList | HTMLCollection} elements + * The elements we wish to upgrade. + */ + function upgradeElementsInternal(elements) { + if (!Array.isArray(elements)) { + if (typeof elements.item === 'function') { + elements = Array.prototype.slice.call(elements); + } else { + elements = [elements]; + } + } + for (var i = 0, n = elements.length, element; i < n; i++) { + element = elements[i]; + if (element instanceof HTMLElement) { + if (element.children.length > 0) { + upgradeElementsInternal(element.children); + } + upgradeElementInternal(element); + } + } + } + + /** * Registers a class for future use and attempts to upgrade existing DOM. - * @param {object} config An object containing: + * @param {Object} config An object containing: * {constructor: Constructor, classAsString: string, cssClass: string} */ function registerInternal(config) { var newConfig = { 'classConstructor': config.constructor, @@ -134,21 +202,22 @@ 'callbacks': [] }; registeredComponents_.forEach(function(item) { if (item.cssClass === newConfig.cssClass) { - throw 'The provided cssClass has already been registered.'; + throw new Error('The provided cssClass has already been registered.'); } if (item.className === newConfig.className) { - throw 'The provided className has already been registered'; + throw new Error('The provided className has already been registered'); } }); if (config.constructor.prototype .hasOwnProperty(componentConfigProperty_)) { - throw 'MDL component classes must not have ' + componentConfigProperty_ + - ' defined as a property.'; + throw new Error( + 'MDL component classes must not have ' + componentConfigProperty_ + + ' defined as a property.'); } var found = findRegisteredClass_(config.classAsString, newConfig); if (!found) { @@ -159,11 +228,11 @@ /** * Allows user to be alerted to any upgrades that are performed for a given * component type * @param {string} jsClass The class name of the MDL component we wish * to hook into for any upgrades performed. - * @param {function} callback The function to call upon an upgrade. This + * @param {!Function} callback The function to call upon an upgrade. This * function should expect 1 parameter - the HTMLElement which got upgraded. */ function registerUpgradedCallbackInternal(jsClass, callback) { var regClass = findRegisteredClass_(jsClass); if (regClass) { @@ -238,19 +307,20 @@ downgradeNode(nodes[n]); } } else if (nodes instanceof Node) { downgradeNode(nodes); } else { - throw 'Invalid argument provided to downgrade MDL nodes.'; + throw new Error('Invalid argument provided to downgrade MDL nodes.'); } } // Now return the functions that should be made public with their publicly // facing names... return { upgradeDom: upgradeDomInternal, upgradeElement: upgradeElementInternal, + upgradeElements: upgradeElementsInternal, upgradeAllRegistered: upgradeAllRegisteredInternal, registerUpgradedCallback: registerUpgradedCallbackInternal, register: registerInternal, downgradeElements: downgradeNodesInternal }; @@ -441,11 +511,12 @@ // The component registers itself. It can assume componentHandler is available // in the global scope. componentHandler.register({ constructor: MaterialButton, classAsString: 'MaterialButton', - cssClass: 'mdl-js-button' + cssClass: 'mdl-js-button', + widget: true }); /** * @license * Copyright 2015 Google Inc. All Rights Reserved. @@ -706,11 +777,12 @@ // The component registers itself. It can assume componentHandler is available // in the global scope. componentHandler.register({ constructor: MaterialCheckbox, classAsString: 'MaterialCheckbox', - cssClass: 'mdl-js-checkbox' + cssClass: 'mdl-js-checkbox', + widget: true }); /** * @license * Copyright 2015 Google Inc. All Rights Reserved. @@ -954,11 +1026,12 @@ // The component registers itself. It can assume componentHandler is available // in the global scope. componentHandler.register({ constructor: MaterialIconToggle, classAsString: 'MaterialIconToggle', - cssClass: 'mdl-js-icon-toggle' + cssClass: 'mdl-js-icon-toggle', + widget: true }); /** * @license * Copyright 2015 Google Inc. All Rights Reserved. @@ -1297,11 +1370,13 @@ * @private */ MaterialMenu.prototype.addAnimationEndListener_ = function() { 'use strict'; - var cleanup = 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); @@ -1420,11 +1495,12 @@ // The component registers itself. It can assume componentHandler is available // in the global scope. componentHandler.register({ constructor: MaterialMenu, classAsString: 'MaterialMenu', - cssClass: 'mdl-js-menu' + cssClass: 'mdl-js-menu', + widget: true }); /** * @license * Copyright 2015 Google Inc. All Rights Reserved. @@ -1521,16 +1597,27 @@ this.element_.classList.add('is-upgraded'); } }; +/* +* Downgrade the component +*/ +MaterialProgress.prototype.mdlDowngrade_ = function() { + 'use strict'; + while (this.element_.firstChild) { + this.element_.removeChild(this.element_.firstChild); + } +}; + // The component registers itself. It can assume componentHandler is available // in the global scope. componentHandler.register({ constructor: MaterialProgress, classAsString: 'MaterialProgress', - cssClass: 'mdl-js-progress' + cssClass: 'mdl-js-progress', + widget: true }); /** * @license * Copyright 2015 Google Inc. All Rights Reserved. @@ -1783,11 +1870,12 @@ // The component registers itself. It can assume componentHandler is available // in the global scope. componentHandler.register({ constructor: MaterialRadio, classAsString: 'MaterialRadio', - cssClass: 'mdl-js-radio' + cssClass: 'mdl-js-radio', + widget: true }); /** * @license * Copyright 2015 Google Inc. All Rights Reserved. @@ -2035,11 +2123,12 @@ // The component registers itself. It can assume componentHandler is available // in the global scope. componentHandler.register({ constructor: MaterialSlider, classAsString: 'MaterialSlider', - cssClass: 'mdl-js-slider' + cssClass: 'mdl-js-slider', + widget: true }); /** * @license * Copyright 2015 Google Inc. All Rights Reserved. @@ -2175,11 +2264,12 @@ // The component registers itself. It can assume componentHandler is available // in the global scope. componentHandler.register({ constructor: MaterialSpinner, classAsString: 'MaterialSpinner', - cssClass: 'mdl-js-spinner' + cssClass: 'mdl-js-spinner', + widget: true }); /** * @license * Copyright 2015 Google Inc. All Rights Reserved. @@ -2444,11 +2534,12 @@ // The component registers itself. It can assume componentHandler is available // in the global scope. componentHandler.register({ constructor: MaterialSwitch, classAsString: 'MaterialSwitch', - cssClass: 'mdl-js-switch' + cssClass: 'mdl-js-switch', + widget: true }); /** * @license * Copyright 2015 Google Inc. All Rights Reserved. @@ -2705,32 +2796,56 @@ * @param {HTMLElement} label The label whose classes we should update. * @private */ MaterialTextfield.prototype.updateClasses_ = function() { 'use strict'; + this.checkDisabled(); + this.checkValidity(); + this.checkDirty(); +}; +// Public methods. + +/** + * Check the disabled state and update field accordingly. + * @public + */ +MaterialTextfield.prototype.checkDisabled = function() { + 'use strict'; if (this.input_.disabled) { this.element_.classList.add(this.CssClasses_.IS_DISABLED); } else { this.element_.classList.remove(this.CssClasses_.IS_DISABLED); } +}; +/** + * Check the validity state and update field accordingly. + * @public + */ +MaterialTextfield.prototype.checkValidity = function() { + 'use strict'; if (this.input_.validity.valid) { this.element_.classList.remove(this.CssClasses_.IS_INVALID); } else { this.element_.classList.add(this.CssClasses_.IS_INVALID); } +}; +/** +* Check the dirty state and update field accordingly. +* @public +*/ +MaterialTextfield.prototype.checkDirty = function() { + 'use strict'; if (this.input_.value && this.input_.value.length > 0) { this.element_.classList.add(this.CssClasses_.IS_DIRTY); } else { this.element_.classList.remove(this.CssClasses_.IS_DIRTY); } }; -// Public methods. - /** * Disable text field. * @public */ MaterialTextfield.prototype.disable = function() { @@ -2820,11 +2935,12 @@ // The component registers itself. It can assume componentHandler is available // in the global scope. componentHandler.register({ constructor: MaterialTextfield, classAsString: 'MaterialTextfield', - cssClass: 'mdl-js-textfield' + cssClass: 'mdl-js-textfield', + widget: true }); /** * @license * Copyright 2015 Google Inc. All Rights Reserved. @@ -2885,14 +3001,25 @@ MaterialTooltip.prototype.handleMouseEnter_ = function(event) { 'use strict'; event.stopPropagation(); var props = event.target.getBoundingClientRect(); - this.element_.style.left = props.left + (props.width / 2) + 'px'; - this.element_.style.marginLeft = -1 * (this.element_.offsetWidth / 2) + 'px'; + var left = props.left + (props.width / 2); + var marginLeft = -1 * (this.element_.offsetWidth / 2); + + 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'; 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. @@ -2901,10 +3028,12 @@ MaterialTooltip.prototype.handleMouseLeave_ = function(event) { 'use strict'; event.stopPropagation(); this.element_.classList.remove(this.CssClasses_.IS_ACTIVE); + window.removeEventListener('scroll', this.boundMouseLeaveHandler); + window.removeEventListener('touchmove', this.boundMouseLeaveHandler, false); }; /** * Initialize element. */ @@ -2917,16 +3046,24 @@ if (forElId) { this.forElement_ = document.getElementById(forElId); } if (this.forElement_) { + // Tabindex needs to be set for `blur` events to be emitted + if (!this.forElement_.getAttribute('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); } } }; @@ -2936,10 +3073,11 @@ MaterialTooltip.prototype.mdlDowngrade_ = function() { 'use strict'; 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); } }; // The component registers itself. It can assume componentHandler is available @@ -3051,11 +3189,15 @@ IS_COMPACT: 'is-compact', IS_SMALL_SCREEN: 'is-small-screen', IS_DRAWER_OPEN: 'is-visible', IS_ACTIVE: 'is-active', IS_UPGRADED: 'is-upgraded', - IS_ANIMATING: 'is-animating' + IS_ANIMATING: 'is-animating', + + ON_LARGE_SCREEN : 'mdl-layout--large-screen-only', + ON_SMALL_SCREEN : 'mdl-layout--small-screen-only' + }; /** * Handles scrolling on the content. * @private @@ -3236,9 +3378,17 @@ // Add drawer toggling button to our layout, if we have an openable drawer. if (this.drawer_) { var drawerButton = document.createElement('div'); drawerButton.classList.add(this.CssClasses_.DRAWER_BTN); + + 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); + } var drawerButtonIcon = document.createElement('i'); drawerButtonIcon.classList.add(this.CssClasses_.ICON); drawerButtonIcon.textContent = this.Constant_.MENU_ICON; drawerButton.appendChild(drawerButtonIcon); drawerButton.addEventListener('click',