@odopod/odo-base-component
Version:
Base component for odo components. Includes media query listeners and exports base globals
380 lines (290 loc) • 10.9 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('tiny-emitter')) :
typeof define === 'function' && define.amd ? define(['tiny-emitter'], factory) :
(global.OdoBaseComponent = factory(global.TinyEmitter));
}(this, (function (TinyEmitter) { 'use strict';
TinyEmitter = TinyEmitter && TinyEmitter.hasOwnProperty('default') ? TinyEmitter['default'] : TinyEmitter;
/**
* Returns a function, that, as long as it continues to be invoked, will not
* be triggered. The function will be called after it stops being called for
* N milliseconds. If `immediate` is passed, trigger the function on the
* leading edge, instead of the trailing. The function also has a property 'clear'
* that is a function which will clear the timer to prevent previously scheduled executions.
*
* @source underscore.js
* @see http://unscriptable.com/2009/03/20/debouncing-javascript-methods/
* @param {Function} function to wrap
* @param {Number} timeout in ms (`100`)
* @param {Boolean} whether to execute at the beginning (`false`)
* @api public
*/
var debounce = function debounce(func, wait, immediate) {
var timeout, args, context, timestamp, result;
if (null == wait) wait = 100;
function later() {
var last = Date.now() - timestamp;
if (last < wait && last >= 0) {
timeout = setTimeout(later, wait - last);
} else {
timeout = null;
if (!immediate) {
result = func.apply(context, args);
context = args = null;
}
}
}
var debounced = function debounced() {
context = this;
args = arguments;
timestamp = Date.now();
var callNow = immediate && !timeout;
if (!timeout) timeout = setTimeout(later, wait);
if (callNow) {
result = func.apply(context, args);
context = args = null;
}
return result;
};
debounced.clear = function () {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
};
debounced.flush = function () {
if (timeout) {
result = func.apply(context, args);
context = args = null;
clearTimeout(timeout);
timeout = null;
}
};
return debounced;
};
var classCallCheck = function (instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
};
var createClass = function () {
function defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
return function (Constructor, protoProps, staticProps) {
if (protoProps) defineProperties(Constructor.prototype, protoProps);
if (staticProps) defineProperties(Constructor, staticProps);
return Constructor;
};
}();
var inherits = function (subClass, superClass) {
if (typeof superClass !== "function" && superClass !== null) {
throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
}
subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: {
value: subClass,
enumerable: false,
writable: true,
configurable: true
}
});
if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
};
var possibleConstructorReturn = function (self, call) {
if (!self) {
throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
}
return call && (typeof call === "object" || typeof call === "function") ? call : self;
};
/**
* A class to be inherited from for components which interact with the DOM.
* @extends {TinyEmitter}
*/
var BaseComponent = function (_TinyEmitter) {
inherits(BaseComponent, _TinyEmitter);
/**
* Create a new base component.
* @param {Element} element Main element which represents this class.
* @param {boolean} [addMediaListeners=false] Whether or not to add media
* query listeners to allow this component to react to media changes.
* @throws {TypeError} Throws when the element is not defined.
*/
function BaseComponent(element) {
var addMediaListeners = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
classCallCheck(this, BaseComponent);
var _this = possibleConstructorReturn(this, _TinyEmitter.call(this));
if (!element) {
throw new TypeError(_this.constructor.name + '\'s "element" in the constructor must be element. Got: "' + element + '".');
}
/**
* Main element for this class.
* @type {Element}
*/
_this.element = element;
if (addMediaListeners && BaseComponent.hasMediaQueries) {
_this._registerMediaQueryListeners();
}
return _this;
}
/**
* Determine the context for queries to the DOM.
* @param {Element} [context=this.element] Optional element to search within.
* @return {!Element}
* @private
*/
BaseComponent.prototype._getContext = function _getContext() {
var context = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.element;
return context;
};
/**
* Retrieve an element by class name within the main element for this class.
* @param {string} klass Name of the class to search for.
* @param {Element} [context] Element to search within. Defaults to main element.
* @return {?Element} The first element which matches the class name, or null.
*/
BaseComponent.prototype.getElementByClass = function getElementByClass(klass, context) {
return this._getContext(context).getElementsByClassName(klass)[0] || null;
};
/**
* Retrieve elements by class name within the main element for this class.
* @param {string} klass Name of the class to search for.
* @param {Element} [context] Element to search within. Defaults to main element.
* @return {Element[]} An array of elements matching the class name.
*/
BaseComponent.prototype.getElementsByClass = function getElementsByClass(klass, context) {
return Array.from(this._getContext(context).getElementsByClassName(klass));
};
/**
* Retrieve elements by selector within the main element for this class.
* @param {string} selector Selector to search for.
* @param {Element} [context] Element to search within. Defaults to main element.
* @return {Element[]} An array of elements matching the selector.
*/
BaseComponent.prototype.getElementsBySelector = function getElementsBySelector(selector, context) {
return Array.from(this._getContext(context).querySelectorAll(selector));
};
/**
* Alias for the static getter `breakpoint`.
* @return {Object}
*/
/**
* Override this method to respond to media query changes.
*/
BaseComponent.prototype.onMediaQueryChange = function onMediaQueryChange() {};
/**
* Clean up element references and event listeners.
*/
BaseComponent.prototype.dispose = function dispose() {
var _this2 = this;
this.element = null;
if (this._onMediaChange) {
Object.keys(BaseComponent.queries).forEach(function (k) {
BaseComponent.queries[k].removeListener(_this2._onMediaChange);
});
this._onMediaChange = null;
}
};
BaseComponent.prototype._registerMediaQueryListeners = function _registerMediaQueryListeners() {
var _this3 = this;
this._onMediaChange = debounce(this.onMediaQueryChange.bind(this), 50);
Object.keys(BaseComponent.queries).forEach(function (k) {
BaseComponent.queries[k].addListener(_this3._onMediaChange);
});
};
/**
* Returns an object with `matches` and `current`. This is an alias for
* `BaseComponent.matches` and `BaseComponent.getCurrentBreakpoint()`.
* @return {!Object}
* @static
*/
/**
* Query the media query list to see if it currently matches.
* @param {string} key Breakpoint key to see if it matches.
* @return {boolean} Whether the given key is the current breakpoint.
* @throws {Error} Will throw an error if the key is not recognized.
* @static
*/
BaseComponent.matches = function matches(key) {
if (BaseComponent.queries[key]) {
return BaseComponent.queries[key].matches;
}
throw new Error('Unrecognized breakpoint key: "' + key + '"');
};
/**
* Loop through the 4 media query lists to determine which one currently
* matches. Returns the key which matches or `null` if none match.
* @return {?string}
* @static
*/
BaseComponent.getCurrentBreakpoint = function getCurrentBreakpoint() {
var key = Object.keys(BaseComponent.queries).find(function (k) {
return BaseComponent.queries[k].matches;
});
return key || null;
};
/**
* Create a new media queries object with keys for each breakpoint.
* @param {number[]} bps Array of breakpoints.
* @return {!Object}
* @private
* @static
*/
BaseComponent._getQueries = function _getQueries(bps) {
return {
xs: matchMedia('(max-width:' + (bps[0] - 1) + 'px)'),
sm: matchMedia('(min-width:' + bps[0] + 'px) and (max-width:' + (bps[1] - 1) + 'px)'),
md: matchMedia('(min-width:' + bps[1] + 'px) and (max-width:' + (bps[2] - 1) + 'px)'),
lg: matchMedia('(min-width:' + bps[2] + 'px)')
};
};
/**
* Allows you to redefine the default breakpoints. If you want to redefine
* breakpoints, make sure you call this method before initializing classes
* which inherit from BaseComponent.
* @param {number[]} breakpoints An array of 3 numbers.
* @static
*/
BaseComponent.defineBreakpoints = function defineBreakpoints(breakpoints) {
BaseComponent.BREAKPOINTS = breakpoints;
BaseComponent.queries = BaseComponent._getQueries(breakpoints);
};
createClass(BaseComponent, [{
key: 'breakpoint',
get: function get$$1() {
return BaseComponent.breakpoint;
}
}], [{
key: 'breakpoint',
get: function get$$1() {
return {
matches: BaseComponent.matches,
get current() {
return BaseComponent.getCurrentBreakpoint();
}
};
}
}]);
return BaseComponent;
}(TinyEmitter);
// Define breakpoints commonly used on Odopod projects.
BaseComponent.defineBreakpoints([768, 1024, 1392]);
/**
* Array of breakpoint key names.
* @type {string[]}
*/
BaseComponent.BREAKPOINT_NAMES = Object.keys(BaseComponent.queries);
/**
* Support: IE9
* Whether the browser has `addListener` on `MediaQueryList` instances.
* @type {boolean}
*/
BaseComponent.hasMediaQueries = typeof BaseComponent.queries.xs.addListener === 'function';
return BaseComponent;
})));
//# sourceMappingURL=odo-base-component.js.map