2016-03-09 12:40:22 -05:00
|
|
|
/**
|
|
|
|
* @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;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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.');
|
|
|
|
}
|
|
|
|
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.');
|
|
|
|
}
|
|
|
|
|
2016-03-17 13:11:23 -04:00
|
|
|
var ev;
|
|
|
|
if ('CustomEvent' in window && typeof window.CustomEvent === 'function') {
|
|
|
|
ev = new Event('mdl-componentupgraded', {
|
|
|
|
'bubbles': true, 'cancelable': false
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
ev = document.createEvent('Events');
|
|
|
|
ev.initEvent('mdl-componentupgraded', true, true);
|
|
|
|
}
|
2016-03-09 12:40:22 -05:00
|
|
|
element.dispatchEvent(ev);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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 (typeof elements.item === 'function') {
|
|
|
|
elements = Array.prototype.slice.call(/** @type {Array} */ (elements));
|
|
|
|
} else {
|
|
|
|
elements = [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(','));
|
|
|
|
|
2016-03-17 13:11:23 -04:00
|
|
|
var ev;
|
|
|
|
if ('CustomEvent' in window && typeof window.CustomEvent === 'function') {
|
|
|
|
ev = new Event('mdl-componentdowngraded', {
|
|
|
|
'bubbles': true, 'cancelable': false
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
ev = document.createEvent('Events');
|
|
|
|
ev.initEvent('mdl-componentdowngraded', true, true);
|
|
|
|
}
|
2016-03-09 12:40:22 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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() {};
|
|
|
|
}
|
|
|
|
});
|