Source: components/RuleBuilder/RuleBuilder.es.js

import Component from 'metal-jsx';
import dom from 'metal-dom';
import RuleEditor from '../../components/RuleEditor/RuleEditor.es';
import RuleList from '../../components/RuleList/RuleList.es';
import {Config} from 'metal-state';
import {EventHandler} from 'metal-events';
import {makeFetch} from '../../util/fetch.es';

/**
 * Builder.
 * @extends Component
 */

class RuleBuilder extends Component {
	static PROPS = {
		dataProviderInstanceParameterSettingsURL: Config.string().required(),

		dataProviderInstancesURL: Config.string().required(),

		functionsMetadata: Config.object(
			{
				number: Config.arrayOf(
					Config.shapeOf(
						{
							label: Config.string(),
							name: Config.string(),
							parameterTypes: Config.array(),
							returnType: Config.string()
						}
					)
				),
				text: Config.arrayOf(
					Config.shapeOf(
						{
							label: Config.string(),
							name: Config.string(),
							parameterTypes: Config.array(),
							returnType: Config.string()
						}
					)
				),
				user: Config.arrayOf(
					Config.shapeOf(
						{
							label: Config.string(),
							name: Config.string(),
							parameterTypes: Config.array(),
							returnType: Config.string()
						}
					)
				)
			}
		),

		functionsURL: Config.string().required(),

		pages: Config.array().required(),

		rolesURL: Config.string().required(),

		rules: Config.arrayOf(
			Config.shapeOf(
				{
					actions: Config.arrayOf(
						Config.shapeOf(
							{
								action: Config.string(),
								label: Config.string(),
								target: Config.string()
							}
						)
					),
					conditions: Config.arrayOf(
						Config.shapeOf(
							{
								operands: Config.arrayOf(
									Config.shapeOf(
										{
											label: Config.string(),
											repeatable: Config.bool(),
											type: Config.string(),
											value: Config.string()
										}
									)
								),
								operator: Config.string()
							}
						)
					),
					logicalOperator: Config.string()
				}
			)
		).value([]),

		/**
		 * The path to the SVG spritemap file containing the icons.
		 * @default undefined
		 * @instance
		 * @memberof Form
		 * @type {!string}
		 */

		spritemap: Config.string().required()
	}

	static STATE = {
		dataProvider: Config.arrayOf(
			Config.shapeOf(
				{
					id: Config.string(),
					name: Config.string(),
					uuid: Config.string()
				}
			)
		).internal(),

		/**
		 * @default
		 * @instance
		 * @memberof RuleBuilder
		 *
		 */

		index: Config.number(),

		mode: Config.oneOf(['view', 'edit', 'create']).value('view'),

		originalRule: Config.object(),

		roles: Config.arrayOf(
			Config.shapeOf(
				{
					id: Config.string(),
					name: Config.string()
				}
			)
		).internal(),

		rules: Config.arrayOf(
			Config.shapeOf(
				{
					actions: Config.arrayOf(
						Config.shapeOf(
							{
								action: Config.string(),
								label: Config.string(),
								target: Config.string()
							}
						)
					),
					conditions: Config.arrayOf(
						Config.shapeOf(
							{
								operands: Config.arrayOf(
									Config.shapeOf(
										{
											label: Config.string(),
											repeatable: Config.bool(),
											type: Config.string(),
											value: Config.string()
										}
									)
								),
								operator: Config.string()
							}
						)
					),
					logicalOperator: Config.string()
				}
			)
		).valueFn('_setRulesValueFn')
	};

	created() {
		this._eventHandler = new EventHandler();

		this._fetchDataProvider();
		this._fetchRoles();
	}

	disposeInternal() {
		super.disposeInternal();

		this._eventHandler.removeAllListeners();
	}

	render() {
		const {
			dataProviderInstanceParameterSettingsURL,
			dataProviderInstancesURL,
			functionsMetadata,
			functionsURL,
			pages,
			spritemap
		} = this.props;

		const {
			dataProvider,
			index,
			mode,
			roles,
			rules
		} = this.state;

		return (
			<div class="container">
				{mode === 'create' && (
					<RuleEditor
						actions={[]}
						conditions={[]}
						dataProvider={dataProvider}
						dataProviderInstanceParameterSettingsURL={dataProviderInstanceParameterSettingsURL}
						dataProviderInstancesURL={dataProviderInstancesURL}
						events={{
							ruleAdded: this._handleRuleAdded.bind(this),
							ruleCancel: this._handleRuleCanceled.bind(this),
							ruleDeleted: this._handleRuleDeleted.bind(this),
							ruleEdited: this._handleRuleEdited.bind(this)
						}}
						functionsMetadata={functionsMetadata}
						functionsURL={functionsURL}
						key={'create'}
						pages={pages}
						ref="RuleEditor"
						roles={roles}
						spritemap={spritemap}
					/>
				)}
				{mode === 'edit' && (
					<RuleEditor
						dataProvider={dataProvider}
						dataProviderInstanceParameterSettingsURL={dataProviderInstanceParameterSettingsURL}
						dataProviderInstancesURL={dataProviderInstancesURL}
						events={{
							ruleAdded: this._handleRuleSaved.bind(this),
							ruleCancel: this._handleRuleCanceled.bind(this)
						}}
						functionsMetadata={functionsMetadata}
						functionsURL={functionsURL}
						key={'edit'}
						pages={pages}
						ref="RuleEditor"
						roles={roles}
						rule={rules[index]}
						ruleEditedIndex={index}
						spritemap={spritemap}
					/>
				)}
				{mode === 'view' && (
					<RuleList
						dataProvider={dataProvider}
						events={{
							ruleAdded: this._handleRuleAdded.bind(this),
							ruleCancel: this._handleRuleCanceled.bind(this),
							ruleDeleted: this._handleRuleDeleted.bind(this),
							ruleEdited: this._handleRuleEdited.bind(this)
						}}
						pages={pages}
						ref="RuleList"
						roles={roles}
						rules={rules}
						spritemap={spritemap}
					/>
				)}
			</div>
		);
	}

	rendered() {
		const {mode} = this.state;
		const {visible} = this.props;

		if (visible) {
			const addButton = document.querySelector('#addFieldButton');

			if (mode === 'create' || mode === 'edit') {
				addButton.classList.add('hide');
			}
			else {
				addButton.classList.remove('hide');
			}
		}
	}

	syncVisible(visible) {
		super.syncVisible(visible);

		if (visible) {
			this._eventHandler.add(
				dom.on('#addFieldButton', 'click', this._handleAddRuleClick.bind(this))
			);
		}
		else {
			this._eventHandler.removeAllListeners();
		}
	}

	willReceiveProps({rules}) {
		if (rules && rules.newVal) {
			this.setState(
				{
					rules: rules.newVal
				}
			);
		}
	}

	_fetchDataProvider() {
		const {dataProviderInstancesURL} = this.props;

		makeFetch(
			{
				method: 'GET',
				url: dataProviderInstancesURL
			}
		).then(
			responseData => {
				if (!this.isDisposed()) {
					this.setState(
						{
							dataProvider: responseData.map(
								data => {
									return {
										...data,
										label: data.name,
										value: data.id
									};
								}
							)
						}
					);
				}
			}
		).catch(
			error => {
				throw new Error(error);
			}
		);
	}

	_fetchRoles() {
		const {rolesURL} = this.props;

		makeFetch(
			{
				method: 'GET',
				url: rolesURL
			}
		).then(
			responseData => {
				if (!this.isDisposed()) {
					this.setState(
						{
							roles: responseData.map(
								data => {
									return {
										...data,
										label: data.name,
										value: data.id
									};
								}
							)
						}
					);
				}
			}
		).catch(
			error => {
				throw new Error(error);
			}
		);
	}

	_handleAddRuleClick(event) {
		this._showRuleCreation();

		this._hideAddRuleButton(event.delegateTarget);
	}

	_handleRuleAdded(event) {
		this.emit(
			'ruleAdded',
			{
				...event
			}
		);

		this._showRuleList();
	}

	_handleRuleCanceled(event) {
		const {index} = this.state;
		const rules = this.state.rules.map(
			(rule, ruleIndex) => {
				return index === ruleIndex ? this.state.originalRule : rule;
			}
		);

		this.setState(
			{
				mode: 'view',
				rules
			}
		);
	}

	_handleRuleDeleted({ruleId}) {
		this.emit(
			'ruleDeleted',
			{
				ruleId
			}
		);
	}

	_handleRuleEdited({ruleId}) {
		const {rules} = this.state;

		ruleId = parseInt(ruleId, 10);

		this.setState(
			{
				index: ruleId,
				mode: 'edit',
				originalRule: JSON.parse(JSON.stringify(rules[ruleId]))
			}
		);
	}

	_handleRuleSaved(event) {
		this.emit(
			'ruleSaved',
			{
				...event,
				ruleId: event.ruleEditedIndex
			}
		);

		this._showRuleList();
	}

	_hideAddRuleButton(element) {
		dom.addClasses(element, 'hide');
	}

	_setRulesValueFn() {
		return this.props.rules;
	}

	_showRuleCreation() {
		this.setState(
			{
				mode: 'create'
			}
		);
	}

	_showRuleList() {
		this.setState(
			{
				mode: 'view'
			}
		);
	}
}

export default RuleBuilder;
export {RuleBuilder};