@syncfusion/ej2-react-base
Version:
A common package of Essential JS 2 React base, methods and class definitions
626 lines (619 loc) • 26.5 kB
JavaScript
import { Component, Children, PureComponent, createElement } from 'react';
import { createPortal } from 'react-dom';
import { extend, isNullOrUndefined, setValue, getValue, isObject, onIntlChange, getTemplateEngine, setTemplateEngine } from '@syncfusion/ej2-base';
/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types */
const defaulthtmlkeys = ['alt', 'className', 'disabled', 'form', 'id',
'readOnly', 'style', 'tabIndex', 'title', 'type', 'name',
'onClick', 'onFocus', 'onBlur'];
const delayUpdate = ['accordion', 'tab', 'splitter'];
const isColEName = /\]/;
class ComponentBase extends Component {
constructor() {
super(...arguments);
this.mountingState = false;
this.attrKeys = [];
this.cachedTimeOut = 0;
this.isAppendCalled = false;
this.initRenderCalled = false;
this.isReactForeceUpdate = false;
this.isReact = true;
this.isshouldComponentUpdateCalled = false;
this.isCreated = false;
}
// Lifecycle methods are changed by React team and so we can deprecate this method and use
// Reference link:https://reactjs.org/docs/react-component.html#unsafe_componentWillMount
componentDidMount() {
this.refreshChild(true);
this.canDelayUpdate = delayUpdate.indexOf(this.getModuleName()) !== -1;
// Used timeout to resolve template binding
// Reference link: https://github.com/facebook/react/issues/10309#issuecomment-318433235
this.renderReactComponent();
if (this.portals && this.portals.length) {
this.mountingState = true;
this.renderReactTemplates();
this.mountingState = false;
}
}
componentDidUpdate(prev) {
if (!this.isshouldComponentUpdateCalled && this.initRenderCalled && !this.isReactForeceUpdate) {
if (prev !== this.props) {
this.isshouldComponentUpdateCalled = true;
this.updateProperties(this.props, false, prev);
}
}
}
renderReactComponent() {
const ele = this.reactElement;
if (ele && !this.isAppendCalled) {
this.isAppendCalled = true;
this.appendTo(ele);
}
}
// Lifecycle methods are changed by React team and so we can deprecate this method and use
// Reference link:https://reactjs.org/docs/react-component.html#unsafe_componentwillreceiveprops
/**
* @param {Object} nextProps - Specifies the property value.
* @returns {boolean} - Returns boolean value.
* @private
*/
shouldComponentUpdate(nextProps) {
this.isshouldComponentUpdateCalled = true;
if (!this.initRenderCalled) {
this.updateProperties(nextProps, true);
return true;
}
if (!this.isAppendCalled) {
clearTimeout(this.cachedTimeOut);
this.isAppendCalled = true;
this.appendTo(this.reactElement);
}
this.updateProperties(nextProps);
return true;
}
updateProperties(nextProps, silent, prev) {
const dProps = extend({}, nextProps);
const keys = Object.keys(nextProps);
const prevProps = extend({}, prev || this.props);
// The statelessTemplates props value is taken from sample level property or default component property.
const statelessTemplates = !isNullOrUndefined(prevProps['statelessTemplates']) ? prevProps['statelessTemplates'] :
(!isNullOrUndefined(this['statelessTemplateProps']) ? this['statelessTemplateProps'] : []);
for (const propkey of keys) {
const isClassName = propkey === 'className';
if (propkey === 'children') {
continue;
}
if (!isClassName && !isNullOrUndefined(this.htmlattributes[`${propkey}`]) &&
this.htmlattributes[`${propkey}`] !== dProps[`${propkey}`]) {
this.htmlattributes[`${propkey}`] = dProps[`${propkey}`];
}
if (this.compareValues(prevProps[`${propkey}`], nextProps[`${propkey}`])) {
delete dProps[`${propkey}`];
}
else if (this.attrKeys.indexOf(propkey) !== -1) {
if (isClassName) {
this.clsName = true;
const propsClsName = prevProps[`${propkey}`].split(' ');
for (let i = 0; i < propsClsName.length; i++) {
this.element.classList.remove(propsClsName[parseInt(i.toString(), 10)]);
}
const dpropsClsName = dProps[`${propkey}`].split(' ');
for (let j = 0; j < dpropsClsName.length; j++) {
this.element.classList.add(dpropsClsName[parseInt(j.toString(), 10)]);
}
}
else if (propkey !== 'disabled' && !Object.prototype.hasOwnProperty.call(this.properties, propkey)) {
delete dProps[`${propkey}`];
}
}
else if (propkey === 'value' && nextProps[`${propkey}`] === this[`${propkey}`]) {
delete dProps[`${propkey}`];
}
else if (statelessTemplates.indexOf(propkey) > -1 && ((propkey === 'content' && typeof dProps[`${propkey}`] === 'function') || (nextProps[`${propkey}`].toString() === this[`${propkey}`].toString()))) {
delete dProps[`${propkey}`];
}
}
if (dProps['children']) {
delete dProps['children'];
}
if (this.initRenderCalled && (this.canDelayUpdate || prevProps.delayUpdate)) {
setTimeout(() => {
this.refreshProperties(dProps, nextProps, silent);
});
}
else {
this.refreshProperties(dProps, nextProps, silent);
}
}
refreshProperties(dProps, nextProps, silent) {
const statelessTemplates = !isNullOrUndefined(this.props['statelessTemplates']) ? this.props['statelessTemplates'] : [];
if (Object.keys(dProps).length) {
if (!silent) {
this.processComplexTemplate(dProps, this);
}
this.setProperties(dProps, silent);
}
if (statelessTemplates.indexOf('directiveTemplates') === -1) {
this.refreshChild(silent, nextProps);
}
}
processComplexTemplate(curObject, context) {
const compTemplate = context.complexTemplate;
if (compTemplate) {
for (const prop in compTemplate) {
if (Object.prototype.hasOwnProperty.call(compTemplate, prop)) {
const PropVal = compTemplate[`${prop}`];
if (curObject[`${prop}`]) {
setValue(PropVal, getValue(prop, curObject), curObject);
}
}
}
}
}
getDefaultAttributes() {
this.isReact = true;
const propKeys = Object.keys(this.props);
//let stringValue: string[] = ['autocomplete', 'dropdownlist', 'combobox'];
const ignoreProps = ['children', 'statelessTemplates', 'immediateRender', 'isLegacyTemplate', 'delayUpdate'];
// if ((stringValue.indexOf(this.getModuleName()) !== -1) && (!isNullOrUndefined(this.props["value"]))) {
// this.value = (<{ [key: string]: Object }>this.props)["value"];
// }
if (!this.htmlattributes) {
this.htmlattributes = {};
}
this.attrKeys = defaulthtmlkeys.concat(this.controlAttributes || []);
for (const prop of propKeys) {
if (prop.indexOf('data-') !== -1 || prop.indexOf('aria-') !== -1 || this.attrKeys.indexOf(prop) !== -1 || (Object.keys(this.properties).indexOf(`${prop}`) === -1 && ignoreProps.indexOf(`${prop}`) === -1)) {
if (this.htmlattributes[`${prop}`] !== this.props[`${prop}`]) {
this.htmlattributes[`${prop}`] = this.props[`${prop}`];
}
}
}
if (!this.htmlattributes.ref) {
this.htmlattributes.ref = (ele) => {
this.reactElement = ele;
};
const keycompoentns = ['autocomplete', 'combobox', 'dropdownlist', 'dropdowntree', 'multiselect',
'listbox', 'colorpicker', 'numerictextbox', 'textbox', 'smarttextarea',
'uploader', 'maskedtextbox', 'slider', 'datepicker', 'datetimepicker', 'daterangepicker', 'timepicker', 'checkbox', 'switch', 'radio', 'rating', 'textarea', 'multicolumncombobox'];
if (keycompoentns.indexOf(this.getModuleName()) !== -1) {
this.htmlattributes.key = '' + ComponentBase.reactUid;
ComponentBase.reactUid++;
if (this.type && !this.htmlattributes['type']) {
this.htmlattributes['type'] = this.multiline ? 'hidden' : this.type;
}
if (this.name && !this.htmlattributes['name']) {
this.htmlattributes['name'] = this.name;
}
}
}
if (this.clsName) {
const clsList = this.element.classList;
const className = this.htmlattributes['className'];
for (let i = 0; i < clsList.length; i++) {
if ((className.indexOf(clsList[parseInt(i.toString(), 10)]) === -1)) {
this.htmlattributes['className'] = this.htmlattributes['className'] + ' ' + clsList[parseInt(i.toString(), 10)];
}
}
}
return this.htmlattributes;
}
trigger(eventName, eventProp, successHandler) {
if (this.isDestroyed !== true && this.modelObserver) {
if (isColEName.test(eventName)) {
const handler = getValue(eventName, this);
if (handler) {
handler.call(this, eventProp);
if (successHandler) {
successHandler.call(this, eventProp);
}
}
else if (successHandler) {
successHandler.call(this, eventProp);
}
}
if ((eventName === 'change' || eventName === 'input')) {
if (this.props.onChange && eventProp.event) {
this.props.onChange.call(undefined, {
syntheticEvent: eventProp.event,
nativeEvent: { text: eventProp.value },
value: eventProp.value,
target: this
});
}
}
const prevDetection = this.isProtectedOnChange;
this.isProtectedOnChange = false;
if (eventName === 'created') {
setTimeout(() => {
this.isCreated = true;
if (!this.isDestroyed) {
this.modelObserver.notify(eventName, eventProp, successHandler);
}
}, 10);
}
else {
this.modelObserver.notify(eventName, eventProp, successHandler);
}
this.isProtectedOnChange = prevDetection;
}
}
compareValues(value1, value2) {
const typeVal = typeof value1;
const typeVal2 = typeof value2;
if (typeVal === typeVal2) {
if (value1 === value2) {
return true;
}
if ((!isNullOrUndefined(value1) && value1.constructor) !== (!isNullOrUndefined(value2) && value2.constructor)) {
return false;
}
if (value1 instanceof Date ||
value1 instanceof RegExp ||
value1 instanceof String ||
value1 instanceof Number) {
return value1.toString() === value2.toString();
}
if (isObject(value1) || Array.isArray(value1)) {
let tempVal = value1;
let tempVal2 = value2;
if (isObject(tempVal)) {
tempVal = [value1];
tempVal2 = [value2];
}
return this.compareObjects(tempVal, tempVal2).status;
}
if (value1.moduleName &&
value1.moduleName === value2.moduleName &&
(value1.moduleName === 'query' ||
value1.moduleName === 'datamanager')) {
if (JSON.stringify(value1) === JSON.stringify(value2)) {
return true;
}
}
}
return false;
}
compareObjects(oldProps, newProps, propName) {
let status = true;
const lenSimilarity = (oldProps.length === newProps.length);
const diffArray = [];
const templateProps = !isNullOrUndefined(this['templateProps']) ? this['templateProps'] : [];
if (lenSimilarity) {
for (let i = 0, len = newProps.length; i < len; i++) {
const curObj = {};
const oldProp = oldProps[parseInt(i.toString(), 10)];
const newProp = newProps[parseInt(i.toString(), 10)];
const keys = Object.keys(newProp);
if (keys.length !== 0) {
for (const key of keys) {
const oldValue = oldProp[`${key}`];
const newValue = newProp[`${key}`];
if (key === 'items') {
for (let _j = 0; _j < newValue.length; _j++) {
if (this.getModuleName() === 'richtexteditor' && typeof (newValue[parseInt(_j.toString(), 10)]) === 'object') {
return { status: true };
}
}
}
if (this.getModuleName() === 'grid' && key === 'field') {
curObj[`${key}`] = newValue;
}
if (!Object.prototype.hasOwnProperty.call(oldProp, key) || !((templateProps.length > 0 && templateProps.indexOf(`${key}`) === -1 && typeof (newValue) === 'function') ? this.compareValues(oldValue.toString(), newValue.toString()) : this.compareValues(oldValue, newValue))) {
if (!propName) {
return { status: false };
}
status = false;
curObj[`${key}`] = newValue;
}
}
}
else if (newProps[parseInt(i.toString(), 10)] === oldProps[parseInt(i.toString(), 10)]) {
status = true;
}
else {
if (!propName) {
return { status: false };
}
status = false;
}
if (this.getModuleName() === 'grid' && propName === 'columns' && isNullOrUndefined(curObj['field'])) {
curObj['field'] = undefined;
}
if (Object.keys(curObj).length) {
diffArray.push({ index: i, value: curObj, key: propName });
}
}
}
else {
status = false;
}
return { status: status, changedProperties: diffArray };
}
refreshChild(silent, props) {
if (this.checkInjectedModules) {
const prevModule = this.getInjectedModules() || [];
const curModule = this.getInjectedServices() || [];
for (const mod of curModule) {
if (prevModule.indexOf(mod) === -1) {
prevModule.push(mod);
}
}
this.injectedModules = prevModule;
}
if (this.directivekeys) {
let changedProps = [];
let key = '';
const directiveValue = this.validateChildren({}, this.directivekeys, (props || this.props));
if (directiveValue && Object.keys(directiveValue).length) {
if (!silent && this.skipRefresh) {
for (const fields of this.skipRefresh) {
delete directiveValue[`${fields}`];
}
}
if (this.prevProperties) {
const dKeys = Object.keys(this.prevProperties);
for (let i = 0; i < dKeys.length; i++) {
key = dKeys[parseInt(i.toString(), 10)];
if (!Object.prototype.hasOwnProperty.call(directiveValue, key)) {
continue;
}
const compareOutput = this.compareObjects(this.prevProperties[`${key}`], directiveValue[`${key}`], key);
if (compareOutput.status) {
delete directiveValue[`${key}`];
}
else {
if (compareOutput.changedProperties.length) {
changedProps = changedProps.concat(compareOutput.changedProperties);
}
const obj = {};
obj[`${key}`] = directiveValue[`${key}`];
this.prevProperties = extend(this.prevProperties, obj);
}
}
}
else {
this.prevProperties = extend({}, directiveValue, {}, true);
}
if (changedProps.length) {
if (this.getModuleName() === 'grid' && key === 'columns') {
for (let _c1 = 0, allColumns = this.columns; _c1 < allColumns.length; _c1++) {
const compareField1 = getValue('field', allColumns[parseInt(_c1.toString(), 10)]);
const compareField2 = getValue(_c1 + '.value.field', changedProps);
if (compareField1 === compareField2) {
const propInstance = getValue(changedProps[parseInt(_c1.toString(), 10)].key + '.' + changedProps[parseInt(_c1.toString(), 10)].index, this);
if (propInstance && propInstance.setProperties) {
propInstance.setProperties(changedProps[parseInt(_c1.toString(), 10)].value);
}
else {
extend(propInstance, changedProps[parseInt(_c1.toString(), 10)].value);
}
}
else {
this.setProperties(directiveValue, silent);
}
}
}
else {
for (const changes of changedProps) {
const propInstance = getValue(changes.key + '.' + changes.index, this);
if (propInstance && propInstance.setProperties) {
propInstance.setProperties(changes.value);
}
else {
extend(propInstance, changes.value);
}
}
}
}
else {
this.setProperties(directiveValue, silent);
}
}
}
}
componentWillUnmount() {
clearTimeout(this.cachedTimeOut);
const modulesName = ['dropdowntree', 'checkbox'];
const hasModule = ((!modulesName.indexOf(this.getModuleName())) ? document.body.contains(this.element) : true);
if (this.initRenderCalled && this.isAppendCalled && this.element && hasModule && !this.isDestroyed && this.isCreated) {
this.destroy();
}
onIntlChange.offIntlEvents();
}
appendReactElement(element, container) {
const portal = createPortal(element, container);
if (!this.portals) {
this.portals = [portal];
}
else {
this.portals.push(portal);
}
}
renderReactTemplates(callback) {
this.isReactForeceUpdate = true;
if (callback) {
this.forceUpdate(callback);
}
else {
this.forceUpdate();
}
this.isReactForeceUpdate = false;
}
clearTemplate(templateNames, index, callback) {
if (templateNames && templateNames.length) {
Array.prototype.forEach.call(templateNames, (propName) => {
this.portals.forEach((portal) => {
if (portal.propName === propName) ;
});
if (!isNullOrUndefined(index) && this.portals[index] && this.portals[index].propName === propName) {
this.portals.splice(index, 1);
}
else {
for (let i = 0; i < this.portals.length; i++) {
if (this.portals[parseInt(i.toString(), 10)].propName === propName) {
this.portals.splice(i, 1);
i--;
}
}
}
});
}
else {
this.portals = [];
}
this.renderReactTemplates(callback);
}
validateChildren(childCache, mapper, props) {
let flag = false;
const childs = Children.toArray(props.children);
for (const child of childs) {
const ifield = this.getChildType(child);
const key = mapper[`${ifield}`];
if (ifield && mapper) {
const childProps = this.getChildProps(Children.toArray(child.props.children), key);
if (childProps.length) {
flag = true;
childCache[child.type.propertyName || ifield] = childProps;
}
}
}
if (flag) {
return childCache;
}
return null;
}
getChildType(child) {
if (child.type && child.type.isDirective) {
return child.type.moduleName || '';
}
return '';
}
getChildProps(subChild, matcher) {
const ret = [];
for (const child of subChild) {
let accessProp = false;
let key;
if (typeof matcher === 'string') {
accessProp = true;
key = matcher;
}
else {
key = Object.keys(matcher)[0];
}
const prop = child.props;
const field = this.getChildType(child);
if (field === key) {
if (accessProp || !prop.children) {
const cacheVal = extend({}, prop, {}, true);
this.processComplexTemplate(cacheVal, child.type);
ret.push(cacheVal);
}
else {
const cachedValue = this.validateChildren(extend({}, prop), matcher[`${key}`], prop) || prop;
if (cachedValue['children']) {
delete cachedValue['children'];
}
this.processComplexTemplate(cachedValue, child.type);
ret.push(cachedValue);
}
}
}
return ret;
}
getInjectedServices() {
const childs = Children.toArray(this.props.children);
for (const child of childs) {
if (child.type && child.type.isService) {
return child.props.services;
}
}
return [];
}
}
/**
* @private
*/
ComponentBase.reactUid = 1;
/**
* Apply mixins for the React components.
*
* @param {any} derivedClass ?
* @param {any[]} baseClass ?
* @returns {void} ?
* @private
*/
function applyMixins(derivedClass, baseClass) {
baseClass.forEach((baseClass) => {
Object.getOwnPropertyNames(baseClass.prototype).forEach((name) => {
if (name !== 'isMounted' && name !== 'replaceState' && name !== 'render') {
derivedClass.prototype[`${name}`] = baseClass.prototype[`${name}`];
}
});
});
}
/**
* Directory base
*/
class ComplexBase extends PureComponent {
render() {
return null;
}
}
ComplexBase.isDirective = true;
/* eslint-disable @typescript-eslint/no-explicit-any */
class Inject extends PureComponent {
render() {
return null;
}
}
Inject.isService = true;
/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types */
/**
* Compile the string value to DOM elements.
*/
const stringCompiler = getTemplateEngine();
/**
* Compile the template property to the DOM elements.
*
* @param {any} templateElement ?
* @param {Object} helper ?
* @returns {Function} ?
* @private
*/
function compile(templateElement, helper) {
if (typeof templateElement === 'string' || (templateElement.prototype && templateElement.prototype.CSPTemplate && typeof templateElement === 'function')) {
return stringCompiler(templateElement, helper);
}
else {
return (data, component, prop, element) => {
let actTemplate = templateElement;
let actData = data;
if (typeof actTemplate === 'object') {
actTemplate = templateElement.template;
actData = extend({}, data, templateElement.data || {});
}
let cEle;
if (element) {
cEle = element;
}
else {
cEle = document.createElement('div');
}
const rele = createElement(actTemplate, actData);
const portal = createPortal(rele, cEle);
portal.propName = prop;
if (!component.portals) {
component.portals = [portal];
}
else {
component.portals.push(portal);
}
if (!element) {
return [cEle];
}
};
}
}
setTemplateEngine({ compile: compile });
export { ComplexBase, ComponentBase, Inject, applyMixins, compile };
//# sourceMappingURL=ej2-react-base.es2015.js.map