| /** |
| * @license |
| * 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. |
| */ |
| |
| /** |
| * A component handler interface using the revealing module design pattern. |
| * More details on this design pattern here: |
| * https://github.com/jasonmayes/mdl-component-design-pattern |
| * |
| * @author Jason Mayes. |
| */ |
| /* exported componentHandler */ |
| |
| // Pre-defining the componentHandler interface, for closure documentation and |
| // static verification. |
| var componentHandler = { |
| /** |
| * Searches existing DOM for elements of our component type and upgrades them |
| * if they have not already been upgraded. |
| * |
| * @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. |
| */ |
| upgradeDom: function(optJsClass, optCssClass) {}, |
| /** |
| * Upgrades a specific element rather than all in the DOM. |
| * |
| * @param {!Element} element The element we wish to upgrade. |
| * @param {string=} optJsClass Optional name of the class we want to upgrade |
| * the element to. |
| */ |
| upgradeElement: function(element, optJsClass) {}, |
| /** |
| * Upgrades a specific list of elements rather than all in the DOM. |
| * |
| * @param {!Element|!Array<!Element>|!NodeList|!HTMLCollection} elements |
| * The elements we wish to upgrade. |
| */ |
| upgradeElements: function(elements) {}, |
| /** |
| * Upgrades all registered components found in the current DOM. This is |
| * automatically called on window load. |
| */ |
| upgradeAllRegistered: function() {}, |
| /** |
| * 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(!HTMLElement)} callback The function to call upon an |
| * upgrade. This function should expect 1 parameter - the HTMLElement which |
| * got upgraded. |
| */ |
| registerUpgradedCallback: function(jsClass, callback) {}, |
| /** |
| * Registers a class for future use and attempts to upgrade existing DOM. |
| * |
| * @param {componentHandler.ComponentConfigPublic} config the registration configuration |
| */ |
| register: function(config) {}, |
| /** |
| * Downgrade either a given node, an array of nodes, or a NodeList. |
| * |
| * @param {!Node|!Array<!Node>|!NodeList} nodes |
| */ |
| downgradeElements: function(nodes) {} |
| }; |
| |
| componentHandler = (function() { |
| 'use strict'; |
| |
| /** @type {!Array<componentHandler.ComponentConfig>} */ |
| var registeredComponents_ = []; |
| |
| /** @type {!Array<componentHandler.Component>} */ |
| var createdComponents_ = []; |
| |
| var componentConfigProperty_ = 'mdlComponentConfigInternal_'; |
| |
| /** |
| * 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 {componentHandler.ComponentConfig=} 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) { |
| if (typeof optReplace !== 'undefined') { |
| registeredComponents_[i] = optReplace; |
| } |
| return registeredComponents_[i]; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Returns an array of the classNames of the upgraded classes on the element. |
| * |
| * @param {!Element} 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 {!Element} element The element we want to check. |
| * @param {string} jsClass The class to check for. |
| * @returns {boolean} |
| * @private |
| */ |
| function isElementUpgraded_(element, jsClass) { |
| var upgradedList = getUpgradedListOfElement_(element); |
| return upgradedList.indexOf(jsClass) !== -1; |
| } |
| |
| /** |
| * Create an event object. |
| * |
| * @param {string} eventType The type name of the event. |
| * @param {boolean} bubbles Whether the event should bubble up the DOM. |
| * @param {boolean} cancelable Whether the event can be canceled. |
| * @returns {!Event} |
| */ |
| function createEvent_(eventType, bubbles, cancelable) { |
| if ('CustomEvent' in window && typeof window.CustomEvent === 'function') { |
| return new CustomEvent(eventType, { |
| bubbles: bubbles, |
| cancelable: cancelable |
| }); |
| } else { |
| var ev = document.createEvent('Events'); |
| ev.initEvent(eventType, bubbles, cancelable); |
| return ev; |
| } |
| } |
| |
| /** |
| * Searches existing DOM for elements of our component type and upgrades them |
| * if they have not already been upgraded. |
| * |
| * @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(optJsClass, optCssClass) { |
| if (typeof optJsClass === 'undefined' && |
| typeof optCssClass === 'undefined') { |
| for (var i = 0; i < registeredComponents_.length; i++) { |
| upgradeDomInternal(registeredComponents_[i].className, |
| registeredComponents_[i].cssClass); |
| } |
| } else { |
| var jsClass = /** @type {string} */ (optJsClass); |
| if (typeof optCssClass === 'undefined') { |
| var registeredClass = findRegisteredClass_(jsClass); |
| if (registeredClass) { |
| optCssClass = registeredClass.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 {!Element} element The element we wish to upgrade. |
| * @param {string=} optJsClass Optional name of the class we want to upgrade |
| * the element to. |
| */ |
| function upgradeElementInternal(element, optJsClass) { |
| // Verify argument type. |
| if (!(typeof element === 'object' && element instanceof Element)) { |
| throw new Error('Invalid argument provided to upgrade MDL element.'); |
| } |
| // Allow upgrade to be canceled by canceling emitted event. |
| var upgradingEv = createEvent_('mdl-componentupgrading', true, true); |
| element.dispatchEvent(upgradingEv); |
| if (upgradingEv.defaultPrevented) { |
| return; |
| } |
| |
| 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)); |
| } |
| |
| // Upgrade the element for each classes. |
| for (var i = 0, n = classesToUpgrade.length, registeredClass; i < n; i++) { |
| registeredClass = classesToUpgrade[i]; |
| if (registeredClass) { |
| // 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. |
| 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[registeredClass.className] = instance; |
| } |
| } else { |
| throw new Error( |
| 'Unable to find a registered component for the given class.'); |
| } |
| |
| var upgradedEv = createEvent_('mdl-componentupgraded', true, false); |
| element.dispatchEvent(upgradedEv); |
| } |
| } |
| |
| /** |
| * Upgrades a specific list of elements rather than all in the DOM. |
| * |
| * @param {!Element|!Array<!Element>|!NodeList|!HTMLCollection} elements |
| * The elements we wish to upgrade. |
| */ |
| function upgradeElementsInternal(elements) { |
| if (!Array.isArray(elements)) { |
| if (elements instanceof Element) { |
| elements = [elements]; |
| } else { |
| elements = Array.prototype.slice.call(elements); |
| } |
| } |
| for (var i = 0, n = elements.length, element; i < n; i++) { |
| element = elements[i]; |
| if (element instanceof HTMLElement) { |
| upgradeElementInternal(element); |
| if (element.children.length > 0) { |
| upgradeElementsInternal(element.children); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Registers a class for future use and attempts to upgrade existing DOM. |
| * |
| * @param {componentHandler.ComponentConfigPublic} config |
| */ |
| function registerInternal(config) { |
| // In order to support both Closure-compiled and uncompiled code accessing |
| // this method, we need to allow for both the dot and array syntax for |
| // property access. You'll therefore see the `foo.bar || foo['bar']` |
| // pattern repeated across this method. |
| var widgetMissing = (typeof config.widget === 'undefined' && |
| typeof config['widget'] === 'undefined'); |
| var widget = true; |
| |
| if (!widgetMissing) { |
| widget = config.widget || config['widget']; |
| } |
| |
| var newConfig = /** @type {componentHandler.ComponentConfig} */ ({ |
| classConstructor: config.constructor || config['constructor'], |
| className: config.classAsString || config['classAsString'], |
| cssClass: config.cssClass || config['cssClass'], |
| widget: widget, |
| callbacks: [] |
| }); |
| |
| registeredComponents_.forEach(function(item) { |
| if (item.cssClass === newConfig.cssClass) { |
| throw new Error('The provided cssClass has already been registered: ' + item.cssClass); |
| } |
| if (item.className === newConfig.className) { |
| throw new Error('The provided className has already been registered'); |
| } |
| }); |
| |
| if (config.constructor.prototype |
| .hasOwnProperty(componentConfigProperty_)) { |
| throw new Error( |
| 'MDL component classes must not have ' + componentConfigProperty_ + |
| ' defined as a property.'); |
| } |
| |
| var found = findRegisteredClass_(config.classAsString, newConfig); |
| |
| if (!found) { |
| registeredComponents_.push(newConfig); |
| } |
| } |
| |
| /** |
| * 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(!HTMLElement)} 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) { |
| regClass.callbacks.push(callback); |
| } |
| } |
| |
| /** |
| * Upgrades all registered components found in the current DOM. This is |
| * automatically called on window load. |
| */ |
| function upgradeAllRegisteredInternal() { |
| for (var n = 0; n < registeredComponents_.length; n++) { |
| upgradeDomInternal(registeredComponents_[n].className); |
| } |
| } |
| |
| /** |
| * Check the component for the downgrade method. |
| * Execute if found. |
| * Remove component from createdComponents list. |
| * |
| * @param {?componentHandler.Component} component |
| */ |
| function deconstructComponentInternal(component) { |
| if (component) { |
| 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 ev = createEvent_('mdl-componentdowngraded', true, false); |
| component.element_.dispatchEvent(ev); |
| } |
| } |
| |
| /** |
| * Downgrade either a given node, an array of nodes, or a NodeList. |
| * |
| * @param {!Node|!Array<!Node>|!NodeList} nodes |
| */ |
| function downgradeNodesInternal(nodes) { |
| /** |
| * Auxiliary function to downgrade a single node. |
| * @param {!Node} node the node to be downgraded |
| */ |
| var downgradeNode = function(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]); |
| } |
| } else if (nodes instanceof Node) { |
| downgradeNode(nodes); |
| } else { |
| 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 |
| }; |
| })(); |
| |
| /** |
| * Describes the type of a registered component type managed by |
| * componentHandler. Provided for benefit of the Closure compiler. |
| * |
| * @typedef {{ |
| * constructor: Function, |
| * classAsString: string, |
| * cssClass: string, |
| * widget: (string|boolean|undefined) |
| * }} |
| */ |
| componentHandler.ComponentConfigPublic; // jshint ignore:line |
| |
| /** |
| * Describes the type of a registered component type managed by |
| * componentHandler. Provided for benefit of the Closure compiler. |
| * |
| * @typedef {{ |
| * constructor: !Function, |
| * className: string, |
| * cssClass: string, |
| * widget: (string|boolean), |
| * callbacks: !Array<function(!HTMLElement)> |
| * }} |
| */ |
| componentHandler.ComponentConfig; // jshint ignore:line |
| |
| /** |
| * Created component (i.e., upgraded element) type as managed by |
| * componentHandler. Provided for benefit of the Closure compiler. |
| * |
| * @typedef {{ |
| * element_: !HTMLElement, |
| * className: string, |
| * classAsString: string, |
| * cssClass: string, |
| * widget: string |
| * }} |
| */ |
| componentHandler.Component; // jshint ignore:line |
| |
| // Export all symbols, for the benefit of Closure compiler. |
| // No effect on uncompiled code. |
| componentHandler['upgradeDom'] = componentHandler.upgradeDom; |
| componentHandler['upgradeElement'] = componentHandler.upgradeElement; |
| componentHandler['upgradeElements'] = componentHandler.upgradeElements; |
| componentHandler['upgradeAllRegistered'] = |
| componentHandler.upgradeAllRegistered; |
| componentHandler['registerUpgradedCallback'] = |
| componentHandler.registerUpgradedCallback; |
| componentHandler['register'] = componentHandler.register; |
| componentHandler['downgradeElements'] = componentHandler.downgradeElements; |
| window.componentHandler = componentHandler; |
| window['componentHandler'] = componentHandler; |
| |
| window.addEventListener('load', function() { |
| 'use strict'; |
| |
| /** |
| * Performs a "Cutting the mustard" test. If the browser supports the features |
| * tested, adds a mdl-js class to the <html> element. It then upgrades all MDL |
| * components requiring JavaScript. |
| */ |
| if ('classList' in document.createElement('div') && |
| 'querySelector' in document && |
| 'addEventListener' in window && Array.prototype.forEach) { |
| document.documentElement.classList.add('mdl-js'); |
| componentHandler.upgradeAllRegistered(); |
| } else { |
| /** |
| * Dummy function to avoid JS errors. |
| */ |
| componentHandler.upgradeElement = function() {}; |
| /** |
| * Dummy function to avoid JS errors. |
| */ |
| componentHandler.register = function() {}; |
| } |
| }); |