Source: components/FormBuilder/FormBuilder.es.js

import ClayModal from 'clay-modal';
import Component from 'metal-jsx';
import compose from '../../util/compose.es';
import dom from 'metal-dom';
import FormRenderer from '../../components/Form/FormRenderer.es';
import Sidebar from '../../components/Sidebar/Sidebar.es';
import withActionableFields from './withActionableFields.es';
import withEditablePageHeader from './withEditablePageHeader.es';
import withMoveableFields from './withMoveableFields.es';
import withMultiplePages from './withMultiplePages.es';
import withResizeableColumns from './withResizeableColumns.es';
import {Config} from 'metal-state';
import {EventHandler} from 'metal-events';
import {focusedFieldStructure, pageStructure, ruleStructure} from '../../util/config.es';
import {generateFieldName} from '../LayoutProvider/util/fields.es';
import {makeFetch} from '../../util/fetch.es';
import {normalizeSettingsContextPages} from '../../util/fieldSupport.es';
import {PagesVisitor} from '../../util/visitors.es';

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

class FormBuilderBase extends Component {
	static PROPS = {

		/**
		 * @default
		 * @instance
		 * @memberof FormBuilder
		 * @type {?number}
		 */

		activePage: Config.number().value(0),

		/**
		 * @default undefined
		 * @instance
		 * @memberof FormBuilder
		 * @type {?string}
		 */

		defaultLanguageId: Config.string(),

		/**
		 * @default undefined
		 * @instance
		 * @memberof FormBuilder
		 * @type {?string}
		 */

		editingLanguageId: Config.string(),

		/**
		 * @default []
		 * @instance
		 * @memberof Sidebar
		 * @type {?(array|undefined)}
		 */

		fieldTypes: Config.array().value([]),

		/**
		 * @default {}
		 * @instance
		 * @memberof FormBuilder
		 * @type {?object}
		 */

		focusedField: focusedFieldStructure.value({}),

		/**
		 * @default []
		 * @instance
		 * @memberof FormBuilder
		 * @type {?array<object>}
		 */

		pages: Config.arrayOf(pageStructure).value([]),

		/**
		 * @instance
		 * @memberof FormBuilder
		 * @type {string}
		 */

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

		/**
		 * @instance
		 * @memberof FormBuilder
		 * @type {string}
		 */

		rules: Config.arrayOf(ruleStructure).required(),

		/**
		 * @default undefined
		 * @instance
		 * @memberof FormRenderer
		 * @type {!string}
		 */

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

	attached() {
		const {activePage, pages} = this.props;
		const {store} = this.context;
		const formBasicInfo = document.querySelector('.ddm-form-basic-info');
		const translationManager = document.querySelector('.ddm-translation-manager');

		if (formBasicInfo && translationManager) {
			formBasicInfo.classList.remove('hide');
			translationManager.classList.remove('hide');
		}

		if (!this._pageHasFields(pages, activePage)) {
			this.openSidebar();
		}

		this._eventHandler.add(
			store.on('fieldDuplicated', () => this.openSidebar())
		);
	}

	created() {
		this._eventHandler = new EventHandler();
		this._handleCancelChangesModalButtonClicked = this._handleCancelChangesModalButtonClicked.bind(this);
	}

	disposeInternal() {
		super.disposeInternal();

		this._eventHandler.removeAllListeners();
	}

	getFormRendererEvents() {
		return {
			fieldClicked: this._handleFieldClicked.bind(this)
		};
	}

	getSidebarEvents() {
		return {
			fieldAdded: this._handleFieldAdded.bind(this),
			fieldBlurred: this._handleSidebarFieldBlurred.bind(this),
			fieldChangesCanceled: this._handleCancelFieldChangesModal.bind(this),
			fieldDeleted: this._handleFieldDeleted.bind(this),
			fieldDuplicated: this._handleFieldDuplicated.bind(this),
			fieldSetAdded: this._handleFieldSetAdded.bind(this),
			focusedFieldUpdated: this._handleFocusedFieldUpdated.bind(this),
			settingsFieldBlurred: this._handleSettingsFieldBlurred.bind(this),
			settingsFieldEdited: this._handleSettingsFieldEdited.bind(this)
		};
	}

	openSidebar() {
		const {sidebar} = this.refs;

		sidebar.open();
	}

	preparePagesForRender(pages) {
		const visitor = new PagesVisitor(pages);

		return visitor.mapFields(
			field => {
				if (field.type === 'select' && field.dataSourceType !== 'manual') {
					field = {
						...field,
						options: [
							{
								label: Liferay.Language.get('dynamically-loaded-data'),
								value: 'dynamic'
							}
						],
						value: 'dynamic'
					};
				}

				return field;
			}
		);
	}

	render() {
		const {props} = this;
		const {
			activePage,
			defaultLanguageId,
			editingLanguageId,
			fieldSets,
			fieldTypes,
			focusedField,
			namespace,
			pages,
			paginationMode,
			rules,
			spritemap,
			visible
		} = props;

		return (
			<div>
				<div class="container ddm-form-builder">
					<div class="sheet">
						<FormRenderer
							activePage={activePage}
							editable={true}
							editingLanguageId={editingLanguageId}
							events={this.getFormRendererEvents()}
							pages={this.preparePagesForRender(pages)}
							paginationMode={paginationMode}
							ref="FormRenderer"
							spritemap={spritemap}
						/>

						<ClayModal
							body={Liferay.Language.get('are-you-sure-you-want-to-cancel')}
							events={{
								clickButton: this._handleCancelChangesModalButtonClicked
							}}
							footerButtons={[
								{
									alignment: 'right',
									label: Liferay.Language.get('dismiss'),
									style: 'primary',
									type: 'close'
								},
								{
									alignment: 'right',
									label: Liferay.Language.get('yes-cancel'),
									style: 'primary',
									type: 'button'
								}
							]}
							ref="cancelChangesModal"
							size="sm"
							spritemap={spritemap}
							title={Liferay.Language.get('cancel-field-changes-question')}
						/>
					</div>
				</div>

				<Sidebar
					defaultLanguageId={defaultLanguageId}
					editingLanguageId={editingLanguageId}
					events={this.getSidebarEvents()}
					fieldSets={fieldSets}
					fieldTypes={fieldTypes}
					focusedField={focusedField}
					namespace={namespace}
					ref="sidebar"
					rules={rules}
					spritemap={spritemap}
					visible={visible}
				/>
			</div>
		);
	}

	rendered() {
		const {sidebar} = this.refs;

		sidebar.refreshDragAndDrop();
	}

	syncEditingLanguageId(editingLanguageId) {
		const {defaultLanguageId} = this.props;
		const addButton = document.querySelector('#addFieldButton');

		if (defaultLanguageId === editingLanguageId) {
			addButton.classList.remove('invisible');
		}
		else {
			addButton.classList.add('invisible');
		}
	}

	syncVisible(visible) {
		const addButton = document.querySelector('#addFieldButton');
		const translationManager = document.querySelector('.ddm-translation-manager');

		super.syncVisible(visible);

		if (visible) {
			addButton.classList.remove('hide');
			translationManager.classList.remove('hide');

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

	willReceiveProps(changes) {
		let {activePage, pages} = this.props;
		let openSidebar = false;

		if (changes.activePage && changes.activePage.newVal !== -1) {
			activePage = changes.activePage.newVal;

			if (!this._pageHasFields(pages, activePage)) {
				openSidebar = true;
			}
		}

		if (
			changes.pages &&
			changes.pages.prevVal &&
			changes.pages.newVal.length !== changes.pages.prevVal.length
		) {
			pages = changes.pages.newVal;

			if (!this._pageHasFields(pages, activePage)) {
				openSidebar = true;
			}
		}

		if (openSidebar) {
			this.openSidebar();
		}
	}

	_fetchFieldSet(fieldSetId) {
		const {
			editingLanguageId,
			fieldSetDefinitionURL,
			groupId,
			namespace
		} = this.props;

		return makeFetch(
			{
				method: 'GET',
				url: `${fieldSetDefinitionURL}?ddmStructureId=${fieldSetId}&languageId=${editingLanguageId}&portletNamespace=${namespace}&scopeGroupId=${groupId}`
			}
		).then(
			({pages}) => pages
		).catch(
			error => {
				throw new Error(error);
			}
		);
	}

	_handleAddFieldButtonClicked() {
		this.openSidebar();
	}

	_handleCancelChangesModalButtonClicked(event) {
		const {store} = this.context;

		event.stopPropagation();

		const {target} = event;
		const {cancelChangesModal, sidebar} = this.refs;

		if (this._isOutsideModal(target)) {
			sidebar.close();
		}

		cancelChangesModal.emit('hide');

		if (!event.target.classList.contains('close-modal')) {
			store.emit('fieldChangesCanceled', {});
		}
	}

	_handleCancelFieldChangesModal() {
		const {cancelChangesModal} = this.refs;

		cancelChangesModal.show();
	}

	_handleFieldAdded(event) {
		const {dispatch} = this.context;
		const {fieldType} = event;
		const {editingLanguageId} = this.props;
		const {settingsContext} = fieldType;
		const {pages} = settingsContext;
		const newFieldName = generateFieldName(this.props.pages, fieldType.label);

		const focusedField = {
			...fieldType,
			fieldName: newFieldName,
			settingsContext: {
				...settingsContext,
				pages: normalizeSettingsContextPages(pages, editingLanguageId, fieldType, newFieldName),
				type: fieldType.name
			}
		};

		const addedToPlaceholder = !event.data.target.parentElement.parentElement.classList.contains('position-relative');

		dispatch(
			'fieldAdded',
			{
				...event,
				addedToPlaceholder,
				focusedField
			}
		);

		this.openSidebar();
	}

	_handleFieldClicked(event) {
		const {dispatch} = this.context;

		this.openSidebar();

		dispatch('fieldClicked', event);
	}

	_handleFieldDeleted(event) {
		this.emit('fieldDeleted', event);
	}

	_handleFieldDuplicated(event) {
		this.emit('fieldDuplicated', event);
	}

	_handleFieldSetAdded(event) {
		const {data} = event;
		const {dispatch} = this.context;
		const {fieldSetId} = data.source.dataset;

		this._fetchFieldSet(fieldSetId).then(
			pages => {
				dispatch(
					'fieldSetAdded',
					{
						...event,
						fieldSetPages: pages
					}
				);
			}
		);
	}

	_handleFocusedFieldUpdated(focusedField) {
		const {dispatch} = this.context;
		const settingsContext = focusedField.settingsContext;

		dispatch(
			'focusedFieldUpdated',
			{
				...focusedField,
				settingsContext
			}
		);
	}

	_handleSettingsFieldBlurred({fieldInstance, value}) {
		const {store} = this.context;
		const {editingLanguageId} = this.props;
		const {fieldName} = fieldInstance;

		store.emit(
			'fieldBlurred',
			{
				editingLanguageId,
				propertyName: fieldName,
				propertyValue: value
			}
		);
	}

	_handleSettingsFieldEdited({fieldInstance, value}) {
		if (fieldInstance && !fieldInstance.isDisposed()) {
			const {editingLanguageId} = this.props;
			const {fieldName} = fieldInstance;
			const {store} = this.context;

			store.emit(
				'fieldEdited',
				{
					editingLanguageId,
					propertyName: fieldName,
					propertyValue: value
				}
			);
		}
	}

	_handleSidebarFieldBlurred() {
		const {store} = this.context;

		store.emit('sidebarFieldBlurred');
	}

	_isOutsideModal(node) {
		return !dom.closest(node, '.close-modal');
	}

	_pageHasFields(pages, pageIndex) {
		const visitor = new PagesVisitor([pages[pageIndex]]);

		let hasFields = false;

		visitor.mapFields(
			() => {
				hasFields = true;
			}
		);

		return hasFields;
	}
}

export default compose(
	withActionableFields,
	withEditablePageHeader,
	withMoveableFields,
	withMultiplePages,
	withResizeableColumns
)(FormBuilderBase);

export {FormBuilderBase};