@nfq/reflow
Version:
Nfq Templating component for DOM rendering
393 lines (327 loc) • 10.5 kB
JavaScript
import $ from 'jquery';
import {TweenMax} from 'gsap/TweenMax';
import NFQReflowTree from './NFQReflowTree';
import NFQReflowTemplateParser from './NFQReflowTemplateParser';
/**
* Reflow Basis Component.
*/
class NFQReflowComponent {
/**
* Constructor.
*
* @param {Object} props All child properties.
*/
constructor(props) {
/* eslint-disable no-magic-numbers */
this.parent = null;
this.template;
this.props = props || {};
this.hash = null;
this.eventList = {};
this.parentHash = this.props.parentHash || null;
this.parentIndex = this.props.parentIndex || 0;
this.children = {};
/* eslint-enable no-magic-numbers */
}
/**
* Adds itself to the tree.
*/
addToTree() {
let parser = new NFQReflowTemplateParser(this.props, this.children, this.template);
let renderedTemplate = parser.parse();
this.usedChildren = parser.getUsedChilds();
this.hash = NFQReflowTree.addNode(this, renderedTemplate);
}
/**
* Renders the component.
*
* @return {String} Hash of component.
*/
render() {
let parent;
if (NFQReflowTree.checkNode(this)) {
this.addToTree();
NFQReflowTree.addToCallStack(this.hash);
if (this.parent === null) {
parent = $(NFQReflowTree.find(this.hash).rendered);
$('body').prepend(parent);
this.parent = parent;
} else {
parent = $(NFQReflowTree.find(this.hash).rendered);
parent.css('opacity', '0');
$(this.parent).replaceWith(parent);
this.parent = parent;
}
requestAnimationFrame(this.onInternalRendered.bind(this));
if (this.initial) {
requestAnimationFrame(this.onInit.bind(this));
this.setConstruct(false);
}
requestAnimationFrame(this.onRendered.bind(this));
this.renderChildren();
NFQReflowTree.removeFromCallStack(this.hash);
} else {
if (this.parent === null) {
parent = $(NFQReflowTree.find(this.hash).rendered);
$('body').prepend(parent);
this.parent = parent;
} else {
parent = $(NFQReflowTree.find(this.hash).rendered);
$(this.parent).replaceWith(parent);
this.parent = parent;
this.renderChildren();
}
}
requestAnimationFrame(this.onRegisterEvents.bind(this));
return this.hash;
}
/**
* Sets its own parent node.
*
* @param {jQuery} parent Parent node.
*/
setParent(parent) {
this.parent = parent;
}
/**
* Sets sets if it is newly constructed or cached.
*
* @param {jQuery} parent Parent node.
*/
setConstruct(bool) {
this.initial = bool;
}
/**
* Renders child components.
*/
renderChildren() {
let param, child, component, usableProperties, i = 0;
NFQReflowTree.clean(this, this.usedChildren);
for ([param, child] of Object.entries(this.children)) {
if (this.usedChildren.indexOf(param) === -1) {
continue;
}
usableProperties = this.addSpecialProps(child, i);
if (NFQReflowTree.find(child.hash)) {
component = NFQReflowTree.find(child.hash).node;
} else {
component = new child.component(usableProperties, child.children || {});
component.setConstruct(true);
}
component.setParent(this.parent.find(`#${param}`));
child.hash = component.render();
i++;
}
this.propagadeChildsRendered();
}
/**
* Adds special parent Hash property.
*
* @param {Object} child Property Object.
* @param {Object} index Render index of child.
*
* @return {Object} Returns the object with new properties.
*/
addSpecialProps(child, index) {
const component = Object.assign({}, child);
if (typeof component.props === 'undefined') {
component.props = {};
}
component.props.parentHash = this.hash;
component.props.parentIndex = index;
return component.props;
}
/**
* Escapes an regex.
*
* @param {string} s String to escape.
*
* @return {string} Escaped regex.
*/
escapeRegex(s) {
/* eslint-disable no-useless-escape */
return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
/* eslint-enable no-useless-escape */
}
/**
* Propagades onChildsRendered Event up.
*
* @param {NFQReflowComponent} target Originated Target.
*/
propagadeChildsRendered(target = null) {
if (NFQReflowTree.getCallStackPosition(this.hash) === null) {
if (this.parentHash !== null) {
NFQReflowTree.find(this.parentHash).node.propagadeChildsRendered(target);
}
} else if (NFQReflowTree.getCallStackPosition(this.hash) === 0) {
if (this.parentHash !== null) {
NFQReflowTree.find(this.parentHash).node.propagadeChildsRendered(this);
}
}
requestAnimationFrame(this.onChildsRendered.bind(this, target));
}
/**
* Finds DOM elements in module context.
*
* @param {string} selector Selector string.
*
* @return {jQuery} Found jQuery DOM object.
*/
find(selector) {
let obj;
if (this.parent.find(selector).length > 0) {
obj = this.parent.find(selector);
} else {
obj = this.parent.filter(selector);
}
return obj;
}
/**
* Adds an double Save eventHandler.
*
* @param {jQuery} selector DOM selection.
* @param {string} event Event Type.
* @param {string} eventId Event Id.
* @param {function} callback Event Handler.
*/
on(selector, event, eventId, callback) {
let hashEvent = `${event}.${eventId}${this.hash}`;
if (selector === null) {
return;
}
selector.off(hashEvent).on(hashEvent, callback);
if (!this.eventList.hasOwnProperty(eventId)) {
this.eventList[eventId] = {
selector: selector,
hash: hashEvent,
callback: callback
};
}
}
/**
* Adds an double Save eventHandler.
*
* @param {jQuery} parentSelector DOM selection.
* @param {jQuery} selector DOM selection.
* @param {string} event Event Type.
* @param {string} eventId Event Id.
* @param {function} callback Event Handler.
*/
delegate(parentSelector, selector, event, eventId, callback) {
let hashEvent = `${event}.${eventId}${this.hash}`;
if (selector === null || parentSelector === null) {
return;
}
parentSelector.off(hashEvent).on(hashEvent, selector, callback);
if (!this.eventList.hasOwnProperty(eventId)) {
this.eventList[eventId] = {
selector: parentSelector,
hash: hashEvent,
callback: callback
};
}
}
/**
* Removes an double Save eventHandler.
*
* @param {String} eventId ID for the Event to kill.
*/
off(eventId) {
let selector, hash, callback;
if (this.eventList.hasOwnProperty(eventId)) {
selector = this.eventList[eventId].selector;
hash = this.eventList[eventId].hash;
callback = this.eventList[eventId].callback;
selector.off(hash, callback);
delete(this.eventList[eventId]);
}
}
/**
* Sets the value of an property and renders the component.
*
* @param {Mixed} props The property object to set.
*/
setProp(props) {
let prop, val, oldProps = this.props;
if (typeof props === 'object') {
this.props = Object.assign(oldProps, props);
} else {
throw new Error('"props" has to be an Object with key value pairs');
}
this.render();
}
/**
* Sets the value of all properties.
*
* @param {Mixed} props The property object to set.
*/
setProps(props) {
let oldProps = this.props;
this.props = Object.assign(oldProps, props);
}
/**
* Cleans up Component from Tree.
*/
cleanSelf() {
NFQReflowTree.killChildren(this.hash);
}
/**
* Sets the value of an property and renders the component.
*
* @param {Mixed} childs The child object to set.
*/
setChild(childs) {
let child, val;
if (typeof childs === 'object') {
for ([child, val] of Object.entries(childs)) {
if (val === null) {
delete(this.children[child]);
} else {
if (
this.children.hasOwnProperty(child)
&& this.children[child].component === val.component
&& typeof this.children[child].hash !== 'undefined'
) {
NFQReflowTree.find(this.children[child].hash).node.setProp(val.props);
} else {
this.children[child] = val;
this.render();
}
}
}
} else {
throw new Error('"childs" has to be an Object with key value pairs');
}
}
onInternalRendered() {
if (this.props.customClass) {
this.parent.addClass(this.props.customClass);
}
TweenMax.to($(this.parent), 0.2, {opacity: 1});
}
/**
* Gets called only one time upon construction.
*/
onInit() {
/* For convenience. */
}
/**
* Gets called after component render.
*/
onRendered() {
/* For convenience. */
}
/**
* Gets called if it is possible to register Events safetly.
*/
onRegisterEvents() {
/* For convenience. */
}
/**
* Gets called after all childs got rendered.
*/
onChildsRendered() {
/* For convenience. */
}
}
export default NFQReflowComponent;