Merge pull request #317 from google/handler-downgrade

Downgrade functionality and components updated with downgrade methods.
master
Jonathan Garbee 2015-06-01 08:07:33 -04:00
commit f1252fd0b8
9 changed files with 273 additions and 76 deletions

View File

@ -96,17 +96,31 @@ MaterialButton.prototype.init = function() {
if (this.element_.classList.contains(this.CssClasses_.RIPPLE_EFFECT)) {
var rippleContainer = document.createElement('span');
rippleContainer.classList.add(this.CssClasses_.RIPPLE_CONTAINER);
var ripple = document.createElement('span');
ripple.classList.add(this.CssClasses_.RIPPLE);
rippleContainer.appendChild(ripple);
ripple.addEventListener('mouseup', this.blurHandler.bind(this));
this.rippleElement_ = document.createElement('span');
this.rippleElement_.classList.add(this.CssClasses_.RIPPLE);
rippleContainer.appendChild(this.rippleElement_);
this.boundRippleBlurHandler = this.blurHandler.bind(this);
this.rippleElement_.addEventListener('mouseup', this.boundRippleBlurHandler);
this.element_.appendChild(rippleContainer);
}
this.element_.addEventListener('mouseup', this.blurHandler.bind(this));
this.element_.addEventListener('mouseleave', this.blurHandler.bind(this));
this.boundButtonBlurHandler = this.blurHandler.bind(this);
this.element_.addEventListener('mouseup', this.boundButtonBlurHandler);
this.element_.addEventListener('mouseleave', this.boundButtonBlurHandler);
}
};
/**
* Downgrade the element.
*/
MaterialButton.prototype.mdlDowngrade_ = function() {
'use strict';
if (this.rippleElement_) {
this.rippleElement_.removeEventListener('mouseup', this.boundRippleBlurHandler);
}
this.element_.removeEventListener('mouseup', this.boundButtonBlurHandler);
this.element_.removeEventListener('mouseleave', this.boundButtonBlurHandler);
};
// The component registers itself. It can assume componentHandler is available
// in the global scope.
componentHandler.register({

View File

@ -211,32 +211,49 @@ MaterialCheckbox.prototype.init = function() {
this.element_.appendChild(tickContainer);
this.element_.appendChild(boxOutline);
var rippleContainer;
if (this.element_.classList.contains(this.CssClasses_.RIPPLE_EFFECT)) {
this.element_.classList.add(this.CssClasses_.RIPPLE_IGNORE_EVENTS);
rippleContainer = document.createElement('span');
rippleContainer.classList.add(this.CssClasses_.RIPPLE_CONTAINER);
rippleContainer.classList.add(this.CssClasses_.RIPPLE_EFFECT);
rippleContainer.classList.add(this.CssClasses_.RIPPLE_CENTER);
rippleContainer.addEventListener('mouseup', this.onMouseUp_.bind(this));
this.rippleContainerElement_ = document.createElement('span');
this.rippleContainerElement_.classList.add(this.CssClasses_.RIPPLE_CONTAINER);
this.rippleContainerElement_.classList.add(this.CssClasses_.RIPPLE_EFFECT);
this.rippleContainerElement_.classList.add(this.CssClasses_.RIPPLE_CENTER);
this.boundRippleMouseUp = this.onMouseUp_.bind(this);
this.rippleContainerElement_.addEventListener('mouseup', this.boundRippleMouseUp);
var ripple = document.createElement('span');
ripple.classList.add(this.CssClasses_.RIPPLE);
rippleContainer.appendChild(ripple);
this.element_.appendChild(rippleContainer);
this.rippleContainerElement_.appendChild(ripple);
this.element_.appendChild(this.rippleContainerElement_);
}
this.inputElement_.addEventListener('change', this.onChange_.bind(this));
this.inputElement_.addEventListener('focus', this.onFocus_.bind(this));
this.inputElement_.addEventListener('blur', this.onBlur_.bind(this));
this.element_.addEventListener('mouseup', this.onMouseUp_.bind(this));
this.boundInputOnChange = this.onChange_.bind(this);
this.boundInputOnFocus = this.onFocus_.bind(this);
this.boundInputOnBlur = this.onBlur_.bind(this);
this.boundElementMouseUp = this.onMouseUp_.bind(this);
this.inputElement_.addEventListener('change', this.boundInputOnChange);
this.inputElement_.addEventListener('focus', this.boundInputOnFocus);
this.inputElement_.addEventListener('blur', this.boundInputOnBlur);
this.element_.addEventListener('mouseup', this.boundElementMouseUp);
this.updateClasses_();
this.element_.classList.add(this.CssClasses_.IS_UPGRADED);
}
};
/*
* Downgrade the component.
*/
MaterialCheckbox.prototype.mdlDowngrade_ = function() {
'use strict';
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);
};
// The component registers itself. It can assume componentHandler is available
// in the global scope.
componentHandler.register({

View File

@ -193,32 +193,50 @@ MaterialIconToggle.prototype.init = function() {
this.inputElement_ =
this.element_.querySelector('.' + this.CssClasses_.INPUT);
var rippleContainer;
if (this.element_.classList.contains(this.CssClasses_.JS_RIPPLE_EFFECT)) {
this.element_.classList.add(this.CssClasses_.RIPPLE_IGNORE_EVENTS);
rippleContainer = document.createElement('span');
rippleContainer.classList.add(this.CssClasses_.RIPPLE_CONTAINER);
rippleContainer.classList.add(this.CssClasses_.JS_RIPPLE_EFFECT);
rippleContainer.classList.add(this.CssClasses_.RIPPLE_CENTER);
rippleContainer.addEventListener('mouseup', this.onMouseUp_.bind(this));
this.rippleContainerElement_ = document.createElement('span');
this.rippleContainerElement_.classList.add(this.CssClasses_.RIPPLE_CONTAINER);
this.rippleContainerElement_.classList.add(this.CssClasses_.JS_RIPPLE_EFFECT);
this.rippleContainerElement_.classList.add(this.CssClasses_.RIPPLE_CENTER);
this.boundRippleMouseUp = this.onMouseUp_.bind(this);
this.rippleContainerElement_.addEventListener('mouseup', this.boundRippleMouseUp);
var ripple = document.createElement('span');
ripple.classList.add(this.CssClasses_.RIPPLE);
rippleContainer.appendChild(ripple);
this.element_.appendChild(rippleContainer);
this.rippleContainerElement_.appendChild(ripple);
this.element_.appendChild(this.rippleContainerElement_);
}
this.inputElement_.addEventListener('change', this.onChange_.bind(this));
this.inputElement_.addEventListener('focus', this.onFocus_.bind(this));
this.inputElement_.addEventListener('blur', this.onBlur_.bind(this));
this.element_.addEventListener('mouseup', this.onMouseUp_.bind(this));
this.boundInputOnChange = this.onChange_.bind(this);
this.boundInputOnFocus = this.onFocus_.bind(this);
this.boundInputOnBlur = this.onBlur_.bind(this);
this.boundElementOnMouseUp = this.onMouseUp_.bind(this);
this.inputElement_.addEventListener('change', this.boundInputOnChange);
this.inputElement_.addEventListener('focus', this.boundInputOnFocus);
this.inputElement_.addEventListener('blur', this.boundInputOnBlur);
this.element_.addEventListener('mouseup', this.boundElementOnMouseUp);
this.updateClasses_();
this.element_.classList.add('is-upgraded');
}
};
/*
* Downgrade the component
*/
MaterialIconToggle.prototype.mdlDowngrade_ = function() {
'use strict';
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);
};
// The component registers itself. It can assume componentHandler is available
// in the global scope.
componentHandler.register({

View File

@ -20,12 +20,14 @@
* https://github.com/jasonmayes/mdl-component-design-pattern
* @author Jason Mayes.
*/
/* exported componentHandler */
/* exported componentHandler */
var componentHandler = (function() {
'use strict';
var registeredComponents_ = [];
var createdComponents_ = [];
var downgradeMethod_ = 'mdlDowngrade_';
var componentConfigProperty_ = 'mdlComponentConfigInternal_';
/**
* Searches registered components for a class we are interested in using.
@ -47,7 +49,6 @@ var componentHandler = (function() {
return false;
}
/**
* Searches existing DOM for elements of our component type and upgrades them
* if they have not already been upgraded.
@ -77,7 +78,6 @@ var componentHandler = (function() {
}
}
/**
* Upgrades a specific element rather than all in the DOM.
* @param {HTMLElement} element The element we wish to upgrade.
@ -98,6 +98,7 @@ var componentHandler = (function() {
if (registeredClass) {
// new
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) {
@ -109,9 +110,7 @@ var componentHandler = (function() {
element.widget = instance;
}
} else {
// If component creator forgot to register, try and see if
// it is in global scope.
createdComponents_.push(new window[jsClass](element));
throw 'Unable to find a registered component for the given class.';
}
var ev = document.createEvent('Events');
@ -120,7 +119,6 @@ var componentHandler = (function() {
}
}
/**
* Registers a class for future use and attempts to upgrade existing DOM.
* @param {object} config An object containing:
@ -135,6 +133,19 @@ var componentHandler = (function() {
'callbacks': []
};
registeredComponents_.forEach(function(item) {
if (item.cssClass === newConfig.cssClass) {
throw 'The provided cssClass has already been registered.';
}
if (item.className === newConfig.className) {
throw '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.';
}
var found = findRegisteredClass_(config.classAsString, newConfig);
if (!found) {
@ -142,7 +153,6 @@ var componentHandler = (function() {
}
}
/**
* Allows user to be alerted to any upgrades that are performed for a given
* component type
@ -158,7 +168,6 @@ var componentHandler = (function() {
}
}
/**
* Upgrades all registered components found in the current DOM. This is
* automatically called on window load.
@ -169,6 +178,66 @@ var componentHandler = (function() {
}
}
/**
* Finds a created component by a given DOM node.
*
* @param node
* @returns {*}
*/
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
*/
function deconstructComponentInternal(component) {
if (component &&
component[componentConfigProperty_]
.classConstructor.prototype
.hasOwnProperty(downgradeMethod_)) {
component[downgradeMethod_]();
var componentIndex = createdComponents_.indexOf(component);
createdComponents_.splice(componentIndex, 1);
var upgrades = component.element_.dataset.upgraded.split(',');
var componentPlace = upgrades.indexOf(component[componentConfigProperty_].classAsString);
upgrades.splice(componentPlace, 1);
component.element_.dataset.upgraded = upgrades.join(',');
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.
*
* @param nodes
*/
function downgradeNodesInternal(nodes) {
var downgradeNode = function(node) {
deconstructComponentInternal(findCreatedComponentByNodeInternal(node));
};
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 'Invalid argument provided to downgrade MDL nodes.';
}
}
// Now return the functions that should be made public with their publicly
// facing names...
return {
@ -176,7 +245,8 @@ var componentHandler = (function() {
upgradeElement: upgradeElementInternal,
upgradeAllRegistered: upgradeAllRegisteredInternal,
registerUpgradedCallback: registerUpgradedCallbackInternal,
register: registerInternal
register: registerInternal,
downgradeElements: downgradeNodesInternal
};
})();

View File

@ -142,14 +142,17 @@ MaterialRipple.prototype.init = function() {
this.rippleElement_.style.height = this.rippleSize_ + 'px';
}
this.element_.addEventListener('mousedown', this.downHandler_.bind(this));
this.boundDownHandler = this.downHandler_.bind(this);
this.element_.addEventListener('mousedown',
this.boundDownHandler);
this.element_.addEventListener('touchstart',
this.downHandler_.bind(this));
this.boundDownHandler);
this.element_.addEventListener('mouseup', this.upHandler_.bind(this));
this.element_.addEventListener('mouseleave', this.upHandler_.bind(this));
this.element_.addEventListener('touchend', this.upHandler_.bind(this));
this.element_.addEventListener('blur', this.upHandler_.bind(this));
this.boundUpHandler = this.upHandler_.bind(this);
this.element_.addEventListener('mouseup', this.boundUpHandler);
this.element_.addEventListener('mouseleave', this.boundUpHandler);
this.element_.addEventListener('touchend', this.boundUpHandler);
this.element_.addEventListener('blur', this.boundUpHandler);
this.getFrameCount = function() {
return this.frameCount_;
@ -212,6 +215,22 @@ MaterialRipple.prototype.init = function() {
}
};
/*
* Downgrade the component
*/
MaterialRipple.prototype.mdlDowngrade_ = function() {
'use strict';
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);
};
// The component registers itself. It can assume componentHandler is available
// in the global scope.
componentHandler.register({

View File

@ -102,7 +102,7 @@ MaterialSlider.prototype.onContainerMouseDown_ = function(event) {
// If this click is not on the parent element (but rather some child)
// ignore. It may still bubble up.
if(event.target !== this.element_.parentElement) {
if (event.target !== this.element_.parentElement) {
return;
}
@ -216,16 +216,31 @@ MaterialSlider.prototype.init = function() {
backgroundFlex.appendChild(this.backgroundUpper_);
}
this.element_.addEventListener('input', this.onInput_.bind(this));
this.element_.addEventListener('change', this.onChange_.bind(this));
this.element_.addEventListener('mouseup', this.onMouseUp_.bind(this));
this.element_.parentElement.addEventListener('mousedown', this.onContainerMouseDown_.bind(this));
this.boundInputHandler = this.onInput_.bind(this);
this.boundChangeHandler = this.onChange_.bind(this);
this.boundMouseUpHandler = this.onMouseUp_.bind(this);
this.boundContainerMouseDownHandler = this.onContainerMouseDown_.bind(this);
this.element_.addEventListener('input', this.boundInputHandler);
this.element_.addEventListener('change', this.boundChangeHandler);
this.element_.addEventListener('mouseup', this.boundMouseUpHandler);
this.element_.parentElement.addEventListener('mousedown', this.boundContainerMouseDownHandler);
this.updateValueStyles_();
this.element_.classList.add(this.CssClasses_.IS_UPGRADED);
}
};
/*
* Downgrade the component
*/
MaterialSlider.prototype.mdlDowngrade_ = function() {
'use strict';
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);
};
// The component registers itself. It can assume componentHandler is available
// in the global scope.
componentHandler.register({

View File

@ -210,35 +210,54 @@ MaterialSwitch.prototype.init = function() {
this.element_.appendChild(track);
this.element_.appendChild(thumb);
var rippleContainer;
this.boundMouseUpHandler = this.onMouseUp_.bind(this);
if (this.element_.classList.contains(
this.CssClasses_.RIPPLE_EFFECT)) {
this.element_.classList.add(
this.CssClasses_.RIPPLE_IGNORE_EVENTS);
rippleContainer = document.createElement('span');
rippleContainer.classList.add(
this.rippleContainerElement_ = document.createElement('span');
this.rippleContainerElement_.classList.add(
this.CssClasses_.RIPPLE_CONTAINER);
rippleContainer.classList.add(this.CssClasses_.RIPPLE_EFFECT);
rippleContainer.classList.add(this.CssClasses_.RIPPLE_CENTER);
rippleContainer.addEventListener('mouseup', this.onMouseUp_.bind(this));
this.rippleContainerElement_.classList.add(this.CssClasses_.RIPPLE_EFFECT);
this.rippleContainerElement_.classList.add(this.CssClasses_.RIPPLE_CENTER);
this.rippleContainerElement_.addEventListener('mouseup', this.boundMouseUpHandler);
var ripple = document.createElement('span');
ripple.classList.add(this.CssClasses_.RIPPLE);
rippleContainer.appendChild(ripple);
this.element_.appendChild(rippleContainer);
this.rippleContainerElement_.appendChild(ripple);
this.element_.appendChild(this.rippleContainerElement_);
}
this.inputElement_.addEventListener('change', this.onChange_.bind(this));
this.inputElement_.addEventListener('focus', this.onFocus_.bind(this));
this.inputElement_.addEventListener('blur', this.onBlur_.bind(this));
this.element_.addEventListener('mouseup', this.onMouseUp_.bind(this));
this.boundChangeHandler = this.onChange_.bind(this);
this.boundFocusHandler = this.onFocus_.bind(this);
this.boundBlurHandler = this.onBlur_.bind(this);
this.inputElement_.addEventListener('change', this.boundChangeHandler);
this.inputElement_.addEventListener('focus', this.boundFocusHandler);
this.inputElement_.addEventListener('blur', this.boundBlurHandler);
this.element_.addEventListener('mouseup', this.boundMouseUpHandler);
this.updateClasses_();
this.element_.classList.add('is-upgraded');
}
};
/*
* Downgrade the component.
*/
MaterialSwitch.prototype.mdlDowngrade_ = function() {
'use strict';
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);
};
// The component registers itself. It can assume componentHandler is available
// in the global scope.
componentHandler.register({

View File

@ -179,14 +179,18 @@ MaterialTextfield.prototype.init = function() {
}
}
this.input_.addEventListener('input', this.updateClasses_.bind(this));
this.input_.addEventListener('focus', this.onFocus_.bind(this));
this.input_.addEventListener('blur', this.onBlur_.bind(this));
this.boundUpdateClassesHandler = this.updateClasses_.bind(this);
this.boundFocusHandler = this.onFocus_.bind(this);
this.boundBlurHandler = this.onBlur_.bind(this);
this.input_.addEventListener('input', this.boundUpdateClassesHandler);
this.input_.addEventListener('focus', this.boundFocusHandler);
this.input_.addEventListener('blur', this.boundBlurHandler);
if (this.maxRows !== this.Constant_.NO_MAX_ROWS) {
// TODO: This should handle pasting multi line text.
// Currently doesn't.
this.input_.addEventListener('keydown', this.onKeyDown_.bind(this));
this.boundKeyDownHandler = this.onKeyDown_.bind(this);
this.input_.addEventListener('keydown', this.boundKeyDownHandler);
}
this.updateClasses_();
@ -195,6 +199,19 @@ MaterialTextfield.prototype.init = function() {
}
};
/*
* Downgrade the component
*/
MaterialTextfield.prototype.mdlDowngrade_ = function() {
'use strict';
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);
}
};
// The component registers itself. It can assume componentHandler is available
// in the global scope.
componentHandler.register({

View File

@ -49,7 +49,6 @@ MaterialTooltip.prototype.CssClasses_ = {
IS_ACTIVE: 'is-active'
};
/**
* Handle mouseenter for tooltip.
* @param {Event} event The event that fired.
@ -66,7 +65,6 @@ MaterialTooltip.prototype.handleMouseEnter_ = function(event) {
this.element_.classList.add(this.CssClasses_.IS_ACTIVE);
};
/**
* Handle mouseleave for tooltip.
* @param {Event} event The event that fired.
@ -79,7 +77,6 @@ MaterialTooltip.prototype.handleMouseLeave_ = function(event) {
this.element_.classList.remove(this.CssClasses_.IS_ACTIVE);
};
/**
* Initialize element.
*/
@ -88,20 +85,31 @@ MaterialTooltip.prototype.init = function() {
if (this.element_) {
var forElId = this.element_.getAttribute('for');
var forEl = null;
if (forElId) {
forEl = document.getElementById(forElId);
this.forElement_ = document.getElementById(forElId);
}
if (forEl) {
forEl.addEventListener('mouseenter', this.handleMouseEnter_.bind(this),
if (this.forElement_) {
this.boundMouseEnterHandler = this.handleMouseEnter_.bind(this);
this.boundMouseLeaveHandler = this.handleMouseLeave_.bind(this);
this.forElement_.addEventListener('mouseenter', this.boundMouseEnterHandler,
false);
forEl.addEventListener('mouseleave', this.handleMouseLeave_.bind(this));
this.forElement_.addEventListener('mouseleave', this.boundMouseLeaveHandler);
}
}
};
/*
* Downgrade the component
*/
MaterialTooltip.prototype.mdlDowngrade_ = function() {
'use strict';
if (this.forElement_) {
this.forElement_.removeEventListener('mouseenter', this.boundMouseEnterHandler, false);
this.forElement_.removeEventListener('mouseleave', this.boundMouseLeaveHandler);
}
};
// The component registers itself. It can assume componentHandler is available
// in the global scope.