import $ from 'jquery';
import { isVoid, isString, isInteger, isObject,
isNode, isFunction, isSymbol } from '../../js/utils/type';
import {eventType} from '../../js/utils/events';
import BaseModule from './basemodule';
/**
* @module Modules
*/
const _attach = Symbol('_attachHandler');
const _detach = Symbol('_detachHandler');
/**
* A basic module to build other modules upon. The constructor either awaits a
* single DOMNode or a configuration object which must have a "container"
* property
* @class Modules._DOMModule
* @constructor
* @extends Modules._BaseModule
*/
class DOMModule extends BaseModule {
constructor(config) {
super();
this.$win = $(window);
this.$doc = $(document);
this.$head = $(document.head);
this.$body = $(document.body);
if (isObject(config)) {
this.$rootNode = $(config.container);
} else if (isNode(config) || isString(config)) {
this.$rootNode = $(config);
} else {
throw `DOMModule: Unable to create $rootNode from ${config}`;
}
}
/**
* Destroys the instance, releases all attached eventlisteners and
* removes all observers and states. Also removes the associated
* HTMLElement from the DOM
*
* @method destroy
* @for Modules._DOMModule
* @return {undefined} Nothing
*/
destroy() {
super.destroy();
this.$win.off(eventType.RESIZE);
this.$rootNode.off(eventType.CLICK);
this.$rootNode.off(eventType.TRANSITION);
this.$rootNode.off(eventType.INPUT);
this.$rootNode.off(eventType.CHANGE);
this.$rootNode.off(eventType.FOCUSIN);
this.$rootNode.off(eventType.FOCUSOUT);
this.$rootNode.off(eventType.SUBMIT);
this.$rootNode.remove();
this.$rootNode = null;
this.$win = null;
}
// === TRAVERSAL ===
/**
* Takes a CSS style selector and returns all elements under the root node
* of the widget which match the selector as jQuery collection
*
* @method $findChilds
* @for Modules._DOMModule
* @param {string} selector Selector to match elements against
* @return {jQuery} A jQuery collection of matched elements
*
* @example
* import DOMModule from './modules/__base/dommodule';
*
* class MyClass extends DOMModule {
* render() {
* this.$buttons = this.$findChilds('button');
* }
* }
*/
$findChilds(selector) {
return this.$rootNode.find(selector);
}
/**
* Takes a CSS style selector and returns the closes parent element of
* the root element of the widget that matches the selector. If no
* selector is specified, returns the immediate parent element of the
* root element.
*
* @method $findParent
* @for Modules._DOMModule
* @param {string} [selector] Selector to match elements against
* @return {jQuery} A jQuery collection of matched elements
*
* @example
* import DOMModule from './modules/__base/dommodule';
*
* class MyClass extends DOMModule {
* render() {
* this.$rootContainer = this.$findParent('div');
* }
* }
*/
$findParent(selector) {
return !selector ? this.$rootNode.parent() : this.$rootNode.closest(selector);
}
/**
* Takes a key and returns either the value of a matching data- attribute
* or null if no data- attribute is set. Works only on the $root element
*
* @method $data
* @for Modules._DOMModule
* @param {string} key Key of the data- attribute to get
* @return {any|null} The value or null
*
* @example
* import DOMModule from './modules/__base/dommodule';
*
* class MyClass extends DOMModule {
* render() {
* const urlVal = this.$data('url') || '/default/url';
* }
* }
*/
$data(key) {
return isString(key) ? this.$rootNode.data(key) || null : null;
}
/**
* Takes an zero based index and returns the raw DOMNode from the underlying
* jQuery collection on the given index. If the index is higher than the
* length of the collection, returns the last element of the collection.
* If it is omitted, returns the first element of the collection.
* Works only on the $root element
*
* @method $raw
* @for Modules._DOMModule
* @param {integer|any} index Index of the raw DOMNode you want to get
* @return {DOMNode} The node on the given position
*
* @example
* import DOMModule from './modules/__base/dommodule';
*
* let items = new DOMModule('li'); // get all <li> elements as $rootNode
*
* items.$raw(); // -> first <li>, equivalent to items.$raw(0)
* items.$raw(4); // -> fourth or last <li>, depending on number of elements
*/
$raw(i) {
// max/min/abs stuff in here ensures we cannot get past the length
// of the entire jQuery collection, regardless of which integer is given
return this.$rootNode.get(!isInteger(i) ? 0 : Math.max(0, Math.min(
this.$rootNode.length - 1,
Math.abs(i)
)));
}
// === EVENTS ===
/**
* Takes a CSS style selector and a function or methodname and registers
* the function or method as eventhandler for all child elements of
* the root element which match the selector when either the
* mousedown/touchstart/pointerdown event occurs.
* If the selector is omitted and only a function/methodname is given,
* registers the handler on the root element.
*
* @method onPress
* @for Modules._DOMModule
* @param {string} [selector] Selector to match elements against
* @param {string|function} handler Eventhandler function or method name
* @return {this} The instance
*
* @example
* import DOMModule from './modules/__base/dommodule';
*
* class MyClass extends DOMModule {
* render() {
* this.onPress('button', 'pressHandle');
* }
* pressHandle(event) {
* // ... your code ...
* }
* }
*/
onPress(selector, handler) {
return this[_attach](eventType.DOWN, selector, handler);
}
/**
* Opposite of the onPress method
*
* @method offPress
* @for Modules._DOMModule
* @param {string} [selector] Selector to match elements against
* @param {string|function} handler Eventhandler function or method name
* @return {this} The instance
*/
offPress(selector, handler) {
return this[_detach](eventType.DOWN, selector, handler);
}
/**
* Takes a CSS style selector and a function or methodname and registers
* the function or method as eventhandler for all child elements of
* the root element which match the selector when either the
* mousemove/touchmove/pointermove event occurs.
* If the selector is omitted and only a function/methodname is given,
* registers the handler on the root element.
*
* @method onMove
* @for Modules._DOMModule
* @param {string} [selector] Selector to match elements against
* @param {string|function} handler Eventhandler function or method name
* @return {this} The instance
*
* @example
* import DOMModule from './modules/__base/dommodule';
*
* class MyClass extends DOMModule {
* render() {
* this.onMove('.movearea', 'moveHandle');
* }
* moveHandle(event) {
* // ... your code ...
* }
* }
*/
onMove(selector, handler) {
return this[_attach](eventType.Move, selector, handler);
}
/**
* Opposite of the onMove method
*
* @method offMove
* @for Modules._DOMModule
* @param {string} [selector] Selector to match elements against
* @param {string|function} handler Eventhandler function or method name
* @return {this} The instance
*/
offMove(selector, handler) {
return this[_detach](eventType.Move, selector, handler);
}
/**
* Takes a CSS style selector and a function or methodname and registers
* the function or method as eventhandler for all child elements of
* the root element which match the selector when either the
* mouseup/touchend/pointerup event occurs.
* If the selector is omitted and only a function/methodname is given,
* registers the handler on the root element.
*
* @method onRelease
* @for Modules._DOMModule
* @param {string} [selector] Selector to match elements against
* @param {string|function} handler Eventhandler function or method name
* @return {this} The instance
*
* @example
* import DOMModule from './modules/__base/dommodule';
*
* class MyClass extends DOMModule {
* render() {
* this.onRelease('button', 'releaseHandle');
* }
* releaseHandle(event) {
* // ... your code ...
* }
* }
*/
onRelease(selector, handler) {
return this[_attach](eventType.UP, selector, handler);
}
/**
* Opposite of the onRelease method
*
* @method offRelease
* @for Modules._DOMModule
* @param {string} [selector] Selector to match elements against
* @param {string|function} handler Eventhandler function or method name
* @return {this} The instance
*/
offRelease(selector, handler) {
return this[_detach](eventType.UP, selector, handler);
}
/**
* Takes a CSS style selector and a function or methodname and registers
* the function or method as eventhandler for all child elements of
* the root element which match the selector when the click event occurs.
* If the selector is omitted and only a function/methodname is given,
* registers the handler on the root element.
*
* @method onClick
* @for Modules._DOMModule
* @param {string} [selector] Selector to match elements against
* @param {string|function} handler Eventhandler function or method name
* @return {this} The instance
*
* @example
* import DOMModule from './modules/__base/dommodule';
*
* class MyClass extends DOMModule {
* render() {
* this.onClick('button', 'clickHandle');
* }
* clickHandle(event) {
* // ... your code ...
* }
* }
*/
onClick(selector, handler) {
return this[_attach](eventType.CLICK, selector, handler);
}
/**
* Opposite of the onClick method
*
* @method offClick
* @for Modules._DOMModule
* @param {string} [selector] Selector to match elements against
* @param {string|function} handler Eventhandler function or method name
* @return {this} The instance
*/
offClick(selector, handler) {
return this[_detach](eventType.CLICK, selector, handler);
}
/**
* Takes a CSS style selector and a function or methodname and registers
* the function or method as eventhandler for all child elements of
* the root element which match the selector when the change event occurs.
* If the selector is omitted and only a function/methodname is given,
* registers the handler on the root element.
*
* @method onChange
* @for Modules._DOMModule
* @param {string} [selector] Selector to match elements against
* @param {string|function} handler Eventhandler function or method name
* @return {this} The instance
*
* @example
* import DOMModule from './modules/__base/dommodule';
*
* class MyClass extends DOMModule {
* render() {
* this.onChange('button', 'changeHandle');
* }
* changeHandle(event) {
* // ... your code ...
* }
* }
*/
onChange(selector, handler) {
return this[_attach](eventType.CHANGE, selector, handler);
}
/**
* Opposite of the onChange method
*
* @method offChange
* @for Modules._DOMModule
* @param {string} [selector] Selector to match elements against
* @param {string|function} handler Eventhandler function or method name
* @return {this} The instance
*/
offChange(selector, handler) {
return this[_detach](eventType.CHANGE, selector, handler);
}
/**
* Takes a CSS style selector and a function or methodname and registers
* the function or method as eventhandler for all child elements of
* the root element which match the selector when the focus event occurs.
* If the selector is omitted and only a function/methodname is given,
* registers the handler on the root element.
*
* @method onFocus
* @for Modules._DOMModule
* @param {string} [selector] Selector to match elements against
* @param {string|function} handler Eventhandler function or method name
* @return {this} The instance
*
* @example
* import DOMModule from './modules/__base/dommodule';
*
* class MyClass extends DOMModule {
* render() {
* this.onFocus('input[name=MyInput]', 'focusHandle');
* }
* focusHandle(event) {
* // ... your code ...
* }
* }
*/
onFocus(selector, handler) {
return this[_attach](eventType.FOCUSIN, selector, handler);
}
/**
* Opposite of the onFocus method
*
* @method offFocus
* @for Modules._DOMModule
* @param {string} [selector] Selector to match elements against
* @param {string|function} handler Eventhandler function or method name
* @return {this} The instance
*/
offFocus(selector, handler) {
return this[_detach](eventType.FOCUSIN, selector, handler);
}
/**
* Takes a CSS style selector and a function or methodname and registers
* the function or method as eventhandler for all child elements of
* the root element which match the selector when the blur event occurs.
* If the selector is omitted and only a function/methodname is given,
* registers the handler on the root element.
*
* @method onBlur
* @for Modules._DOMModule
* @param {string} [selector] Selector to match elements against
* @param {string|function} handler Eventhandler function or method name
* @return {this} The instance
*
* @example
* import DOMModule from './modules/__base/dommodule';
*
* class MyClass extends DOMModule {
* render() {
* this.onBlur('input[name=MyInput]', 'blurHandle');
* }
* blurHandle(event) {
* // ... your code ...
* }
* }
*/
onBlur(selector, handler) {
return this[_attach](eventType.FOCUSOUT, selector, handler);
}
/**
* Opposite of the onBlur method
*
* @method offBlur
* @for Modules._DOMModule
* @param {string} [selector] Selector to match elements against
* @param {string|function} handler Eventhandler function or method name
* @return {this} The instance
*/
offBlur(selector, handler) {
return this[_detach](eventType.FOCUSOUT, selector, handler);
}
/**
* Takes a CSS style selector and a function or methodname and registers
* the function or method as eventhandler for all child elements of
* the root element which match the selector when either the
* keyup/input event occurs.
* If the selector is omitted and only a function/methodname is given,
* registers the handler on the root element.
*
* @method onInput
* @for Modules._DOMModule
* @param {string} [selector] Selector to match elements against
* @param {string|function} handler Eventhandler function or method name
* @return {this} The instance
*
* @example
* import DOMModule from './modules/__base/dommodule';
*
* class MyClass extends DOMModule {
* render() {
* this.onInput('input[name=MyInput]', 'inputHandle');
* }
* inputHandle(event) {
* // ... your code ...
* }
* }
*/
onInput(selector, handler) {
return this[_attach](eventType.INPUT, selector, handler);
}
/**
* Opposite of the onInput method
*
* @method offInput
* @for Modules._DOMModule
* @param {string} [selector] Selector to match elements against
* @param {string|function} handler Eventhandler function or method name
* @return {this} The instance
*/
offInput(selector, handler) {
return this[_detach](eventType.INPUT, selector, handler);
}
/**
* Takes a CSS style selector and a function or methodname and registers
* the function or method as eventhandler for all child elements of
* the root element which match the selector when either the
* transitionend/webkitTransitionEnd/mozTransitionEnd/msTransitionEnd/
* oTransitionEnd event occurs.
* If the selector is omitted and only a function/methodname is given,
* registers the handler on the root element.
*
* @method onTransitionEnd
* @for Modules._DOMModule
* @param {string} [selector] Selector to match elements against
* @param {string|function} handler Eventhandler function or method name
* @return {this} The instance
*
* @example
* import DOMModule from './modules/__base/dommodule';
*
* class MyClass extends DOMModule {
* render() {
* this.onTransitionEnd('.effectarea', 'transitionEndHandle');
* }
* transitionEndHandle(event) {
* // ... your code ...
* }
* }
*/
onTransitionEnd(selector, handler) {
return this[_attach](eventType.TRANSITION, selector, handler);
}
/**
* Opposite of the onTransitionEnd method
*
* @method offTransitionEnd
* @for Modules._DOMModule
* @param {string} [selector] Selector to match elements against
* @param {string|function} handler Eventhandler function or method name
* @return {this} The instance
*/
offTransitionEnd(selector, handler) {
return this[_detach](eventType.TRANSITION, selector, handler);
}
/**
* Takes a CSS style selector and a function or methodname and registers
* the function or method as eventhandler for all child elements of
* the root element which match the selector when submit event occurs.
* If the selector is omitted and only a function/methodname is given,
* registers the handler on the root element.
*
* @method onSubmit
* @for Modules._DOMModule
* @param {string} [selector] Selector to match elements against
* @param {string|function} handler Eventhandler function or method name
* @return {this} The instance
*
* @example
* import DOMModule from './modules/__base/dommodule';
*
* class MyClass extends DOMModule {
* render() {
* this.onSubmit('.my-form', 'submitHandler');
* }
* submitHandler(event) {
* // ... your code ...
* }
* }
*/
onSubmit(selector, handler) {
return this[_attach](eventType.SUBMIT, selector, handler);
}
/**
* Opposite of the onSubmit method
*
* @method offSubmit
* @for Modules._DOMModule
* @param {string} [selector] Selector to match elements against
* @param {string|function} handler Eventhandler function or method name
* @return {this} The instance
*/
offSubmit(selector, handler) {
return this[_detach](eventType.SUBMIT, selector, handler);
}
/**
* Takes function or methodname and registers the function or method as
* eventhandler on the window object.
*
* @method onResize
* @for Modules._DOMModule
* @param {string|function} fn Eventhandler function or method name
* @return {this} The instance
*
* @example
* import DOMModule from './modules/__base/dommodule';
*
* class MyClass extends DOMModule {
* render() {
* this.onResize('resizeHandle');
* }
* resizeHandle(event) {
* // ... your code ...
* }
* }
*/
onResize(fn) {
if (isFunction(fn)) {
this.$win.on(eventType.RESIZE, fn);
return this;
}
if ((isString(fn) || isSymbol(fn)) && isFunction(this[fn])) {
this.$win.on(eventType.RESIZE, this[fn].bind(this));
}
return this;
}
/**
* Opposite of the onResize method
*
* @method offResize
* @for Modules._DOMModule
* @param {string} [selector] Selector to match elements against
* @param {string|function} fn Eventhandler function or method name
* @return {this} The instance
*/
offResize(fn) {
if (isFunction(fn)) {
this.$win.off(eventType.RESIZE, fn);
return this;
}
if ((isString(fn) || isSymbol(fn)) && isFunction(this[fn])) {
this.$win.off(eventType.RESIZE, this[fn].bind(this));
return this;
}
this.$win.off(eventType.RESIZE);
return this;
}
// === PRIVATE STUFF ===
[_attach](type, selector, fn) {
if (isVoid(fn)) {
if (isFunction(selector)) {
this.$rootNode.on(type, selector);
return this;
}
if ((isString(selector) || isSymbol(selector)) && isFunction(this[selector])) {
this.$rootNode.on(type, this[selector].bind(this));
}
return this;
}
if (isFunction(fn)) {
this.$rootNode.on(type, selector, fn);
return this;
}
if ((isString(fn) || isSymbol(fn)) && isFunction(this[fn])) {
this.$rootNode.on(type, selector, this[fn].bind(this));
}
return this;
}
[_detach](type, selector, fn) {
if (isVoid(fn)) {
if (isFunction(selector)) {
this.$rootNode.off(type, selector);
return this;
}
if ((isString(selector) || isSymbol(selector)) && isFunction(this[selector])) {
this.$rootNode.off(type, this[selector].bind(this));
}
return this;
}
if (isFunction(fn)) {
this.$rootNode.off(type, selector, fn);
return this;
}
if ((isString(fn) || isSymbol(fn)) && isFunction(this[fn])) {
this.$rootNode.off(type, selector, this[fn].bind(this));
}
return this;
}
}
export default DOMModule;