Your IP : 3.143.4.111


Current Path : /var/www/www-root/data/www/info.monolith-realty.ru/bitrix/js/ui/stepprocessing/src/
Upload File :
Current File : /var/www/www-root/data/www/info.monolith-realty.ru/bitrix/js/ui/stepprocessing/src/dialog.js

// @flow

import 'ui.design-tokens';
import 'ui.progressbar';
import {Type, Tag, Loc, Dom, Event} from 'main.core';
import { BaseEvent, EventEmitter } from 'main.core.events';
import { Popup, PopupManager } from 'main.popup';
import { Alert, AlertColor, AlertIcon, AlertSize } from 'ui.alerts';
import {Button, CancelButton} from 'ui.buttons';
import type { OptionsField } from './process-types';
import { BaseField } from './fields/base-field';
import { TextField } from './fields/text-field';
import { FileField } from './fields/file-field';
import { CheckboxField } from './fields/checkbox-field';
import { SelectField } from './fields/select-field';
import { RadioField } from './fields/radio-field';

/**
 * @namespace {BX.UI.StepProcessing}
 */
export type DialogOptions = {
	id: string,
	messages?: {
		title?: string,
		summary?: string,
		startButton?:  string,
		stopButton?: string,
		closeButton?: string
	},
	optionsFields?: OptionsField[],
	optionsFieldsValue?: Object,
	showButtons?: {
		start?: boolean,
		stop?: boolean,
		close?: boolean
	},
	handlers?: {
		start?: any => void,
		stop?: any => void,
		dialogShown?: any => void,
		dialogClosed?: any => void
	},
	minWidth?: number,
	maxWidth?: number
};

export const DialogStyle = {
	ProcessWindow: 'bx-stepprocessing-dialog-process',
	ProcessPopup: 'bx-stepprocessing-dialog-process-popup',
	ProcessSummary: 'bx-stepprocessing-dialog-process-summary',
	ProcessProgressbar: 'bx-stepprocessing-dialog-process-progressbar',
	ProcessOptions: 'bx-stepprocessing-dialog-process-options',
	ProcessOptionContainer: 'bx-stepprocessing-dialog-process-option-container',
	ProcessOptionsTitle: 'bx-stepprocessing-dialog-process-options-title',
	ProcessOptionsInput: 'bx-stepprocessing-dialog-process-options-input',
	ProcessOptionsObligatory: 'ui-alert ui-alert-xs ui-alert-warning',
	ProcessOptionText: 'bx-stepprocessing-dialog-process-option-text',
	ProcessOptionCheckbox: 'bx-stepprocessing-dialog-process-option-checkbox',
	ProcessOptionMultiple: 'bx-stepprocessing-dialog-process-option-multiple',
	ProcessOptionFile: 'bx-stepprocessing-dialog-process-option-file',
	ProcessOptionSelect: 'bx-stepprocessing-dialog-process-option-select',
	ButtonStart: 'popup-window-button-accept',
	ButtonStop: 'popup-window-button-disable',
	ButtonCancel: 'popup-window-button-link-cancel',
	ButtonDownload: 'popup-window-button-link-download',
	ButtonRemove: 'popup-window-button-link-remove'
};

export const DialogEvent = {
	Shown: 'BX.UI.StepProcessing.Dialog.Shown',
	Closed: 'BX.UI.StepProcessing.Dialog.Closed',
	Start: 'BX.UI.StepProcessing.Dialog.Start',
	Stop: 'BX.UI.StepProcessing.Dialog.Stop',
};

/**
 * UI of process dialog
 *
 * @namespace {BX.UI.StepProcessing}
 * @event BX.UI.StepProcessing.Dialog.Shown
 * @event BX.UI.StepProcessing.Dialog.Closed
 * @event BX.UI.StepProcessing.Dialog.Start
 * @event BX.UI.StepProcessing.Dialog.Stop
 */
export class Dialog
{
	id: string = '';

	/**
	 * @type {DialogOptions}
	 * @private
	 */
	_settings: DialogOptions = {};

	//popup
	popupWindow: Popup;
	isShown: boolean = false;

	//UI
	error: Alert;
	warning: Alert;
	progressBar: BX.UI.ProgressBar;
	buttons: {[type: 'start'|'stop'|'close']: Button} = {};
	fields: {[name: string]: BaseField} = {};

	//DOM
	optionsFieldsBlock: HTMLElement;
	summaryBlock: HTMLElement;
	errorBlock: HTMLElement;
	warningBlock: HTMLElement;
	progressBarBlock: HTMLElement;

	/**
	 * @private
	 */
	_messages = {};

	/**
	 * @private
	 */
	_handlers = {};

	/**
	 * @private
	 */
	isAdminPanel = false;

	constructor(settings: DialogOptions = {})
	{
		this._settings = settings;

		this.id = this.getSetting('id', 'ProcessDialog_' + Math.random().toString().substring(2));

		this._messages = this.getSetting('messages', {});

		let optionsFields = {};
		const fields = this.getSetting('optionsFields');
		if (Type.isArray(fields))
		{
			fields.forEach(option => {
				if (
					Type.isPlainObject(option) &&
					option.hasOwnProperty('name') &&
					option.hasOwnProperty('type') &&
					option.hasOwnProperty('title')
				)
				{
					optionsFields[option.name] = option;
				}
			});
		}
		else if (Type.isPlainObject(fields))
		{
			Object.keys(fields).forEach(optionName => {
				let option = fields[optionName];
				if (
					Type.isPlainObject(option) &&
					option.hasOwnProperty('name') &&
					option.hasOwnProperty('type') &&
					option.hasOwnProperty('title')
				)
				{
					optionsFields[option.name] = option;
				}
			});
		}
		this.setSetting('optionsFields', optionsFields);

		const optionsFieldsValue = this.getSetting('optionsFieldsValue');
		if (!optionsFieldsValue)
		{
			this.setSetting('optionsFieldsValue',{});
		}

		const showButtons = this.getSetting('showButtons');
		if (!showButtons)
		{
			this.setSetting('showButtons', {'start':true, 'stop':true, 'close':true});
		}

		this._handlers = this.getSetting('handlers', {});
	}

	destroy()
	{
		if (this.popupWindow)
		{
			this.popupWindow.destroy();
			this.popupWindow = null;
		}
	}

	getId()
	{
		return this.id;
	}

	getSetting(name: $Keys<DialogOptions>, defaultVal: ?any = null)
	{
		return this._settings.hasOwnProperty(name) ? this._settings[name] : defaultVal;
	}
	setSetting(name: $Keys<DialogOptions>, value: any)
	{
		this._settings[name] = value;
		return this;
	}

	getMessage(name: string): string
	{
		return this._messages && this._messages.hasOwnProperty(name) ? this._messages[name] : "";
	}
	setMessage(name: string, text: string)
	{
		this._messages[name] = text;
		return this;
	}

	//region Event handlers

	setHandler(type: string, handler: any => void)
	{
		if (typeof(handler) == 'function')
		{
			this._handlers[type] = handler;
		}
		return this;
	}
	callHandler(type: string, args: {[string]: any})
	{
		if (typeof(this._handlers[type]) == 'function')
		{
			this._handlers[type].apply(this, args);
		}
	}

	//endregion

	//region Run

	start()
	{
		this.callHandler('start');
		EventEmitter.emit(DialogEvent.Start, new BaseEvent({dialog: this}));
	}

	stop()
	{
		this.callHandler('stop');
		EventEmitter.emit(DialogEvent.Stop, new BaseEvent({dialog: this}));
	}

	show()
	{
		if (this.isShown)
		{
			return;
		}

		const optionElement = document.querySelector('#bx-admin-prefix');
		if (optionElement)
		{
			this.isAdminPanel = true;
		}

		this.progressBar = new BX.UI.ProgressBar({
			statusType: BX.UI.ProgressBar.Status.COUNTER,
			size: this.isAdminPanel ? BX.UI.ProgressBar.Size.LARGE : BX.UI.ProgressBar.Size.MEDIUM,
			fill: this.isAdminPanel,
			column: !this.isAdminPanel
		});

		this.error = new Alert({
			color: AlertColor.DANGER,
			icon: AlertIcon.DANGER,
			size: AlertSize.SMALL
		});

		this.warning = new Alert({
			color: AlertColor.WARNING,
			icon: AlertIcon.WARNING,
			size: AlertSize.SMALL
		});

		this.popupWindow = PopupManager.create({
			id: this.getId(),
			cacheable: false,
			titleBar: this.getMessage("title"),
			autoHide: false,
			closeByEsc: false,
			closeIcon: true,
			content: this._prepareDialogContent(),
			draggable: true,
			buttons: this._prepareDialogButtons(),
			className: DialogStyle.ProcessWindow,
			bindOptions: {forceBindPosition: false},
			events: {
				onClose: BX.delegate(this.onDialogClose, this)
			},
			overlay: true,
			resizable: true,
			minWidth: Number.parseInt(this.getSetting('minWidth', 500)),
			maxWidth: Number.parseInt(this.getSetting('maxWidth', 1000))
		});

		if (!this.popupWindow.isShown())
		{
			this.popupWindow.show();
		}

		this.isShown = this.popupWindow.isShown();

		if (this.isShown)
		{
			this.callHandler('dialogShown');
			EventEmitter.emit(DialogEvent.Shown, new BaseEvent({dialog: this}));
		}
		return this;
	}

	close()
	{
		if (!this.isShown)
		{
			return;
		}
		if (this.popupWindow)
		{
			this.popupWindow.close();
		}
		this.isShown = false;

		this.callHandler('dialogClosed');
		EventEmitter.emit(DialogEvent.Closed, new BaseEvent({dialog: this}));

		return this;
	}

	// endregion

	//region Dialog

	/**
	 * @private
	 */
	_prepareDialogContent()
	{
		this.summaryBlock = Tag.render`<div class="${DialogStyle.ProcessSummary}">${this.getMessage('summary')}</div>`;

		this.errorBlock = this.error.getContainer();
		this.warningBlock = this.warning.getContainer();
		this.errorBlock.style.display = 'none';
		this.warningBlock.style.display = 'none';

		if (this.progressBar)
		{
			this.progressBarBlock =  Tag.render`<div class="${DialogStyle.ProcessProgressbar}" style="display:none"></div>`;
			this.progressBarBlock.appendChild(this.progressBar.getContainer());
		}

		if (!this.optionsFieldsBlock)
		{
			this.optionsFieldsBlock = Tag.render`<div class="${DialogStyle.ProcessOptions}" style="display:none"></div>`;
		}
		else
		{
			Dom.clean(this.optionsFieldsBlock);
		}

		let optionsFields = this.getSetting('optionsFields', {});
		let optionsFieldsValue = this.getSetting('optionsFieldsValue', {});

		Object.keys(optionsFields).forEach(optionName => {
			let optionValue = optionsFieldsValue[optionName] ? optionsFieldsValue[optionName] : null;
			let optionBlock = this._renderOption(optionsFields[optionName], optionValue);
			if (optionBlock instanceof HTMLElement)
			{
				this.optionsFieldsBlock.appendChild(optionBlock);
				this.optionsFieldsBlock.style.display = 'block';
			}
		});

		let dialogContent = Tag.render`<div class="${DialogStyle.ProcessPopup}"></div>`;
		dialogContent.appendChild(this.summaryBlock);
		dialogContent.appendChild(this.warningBlock);
		dialogContent.appendChild(this.errorBlock);

		if (this.progressBarBlock)
		{
			dialogContent.appendChild(this.progressBarBlock);
		}

		if (this.optionsFieldsBlock)
		{
			dialogContent.appendChild(this.optionsFieldsBlock);
		}

		return dialogContent;
	}

	/**
	 * @private
	 */
	_renderOption(option: OptionsField, optionValue: any = null)
	{
		option.id = this.id + '_opt_' + option.name;

		switch (option.type)
		{
			case 'text':
				this.fields[option.name] = new TextField(option);
				break;

			case 'file':
				this.fields[option.name] = new FileField(option);
				break;

			case 'checkbox':
				this.fields[option.name] = new CheckboxField(option);
				break;

			case 'select':
				this.fields[option.name] = new SelectField(option);
				break;

			case 'radio':
				this.fields[option.name] = new RadioField(option);
				break;
		}

		if (optionValue !== null)
		{
			this.fields[option.name].setValue(optionValue);
		}
		const optionBlock = this.fields[option.name].getContainer();

		return optionBlock;
	}

	//endregion

	//region Events

	onDialogClose()
	{
		if (this.popupWindow)
		{
			this.popupWindow.destroy();
			this.popupWindow = null;
		}

		this.buttons = {};
		this.fields = {};
		this.summaryBlock = null;

		this.isShown = false;

		this.callHandler('dialogClosed');
		EventEmitter.emit(DialogEvent.Closed, new BaseEvent({dialog: this}));
	}

	handleStartButtonClick()
	{
		const btn = this.getButton('start');
		if (btn && btn.isDisabled())
		{
			return;
		}

		this.start();
	}

	handleStopButtonClick()
	{
		const btn = this.getButton('stop');
		if (btn && btn.isDisabled())
		{
			return;
		}

		this.stop();
	}

	handleCloseButtonClick()
	{
		this.popupWindow.close();
	}

	//endregion

	//region Buttons

	/**
	 * @private
	 */
	_prepareDialogButtons(): Button[]
	{
		const showButtons = this.getSetting('showButtons');
		let ret = [];
		this.buttons = {};

		if (showButtons.start)
		{
			const startButtonText = this.getMessage('startButton');
			this.buttons.start = new Button({
				text: startButtonText || 'Start',
				color: Button.Color.SUCCESS,
				icon: Button.Icon.START,
				//className: DialogStyle.ButtonStart,
				events:
					{
						click: BX.delegate(this.handleStartButtonClick, this)
					}
			});
			ret.push(this.buttons.start);
		}

		if (showButtons.stop)
		{
			const stopButtonText = this.getMessage('stopButton');
			this.buttons.stop = new Button({
				text: stopButtonText || 'Stop',
				color: Button.Color.LIGHT_BORDER,
				icon: Button.Icon.STOP,
				//className: DialogStyle.ButtonStop,
				events:
					{
						click: BX.delegate(this.handleStopButtonClick, this)
					}
			});
			this.buttons.stop.setDisabled();
			ret.push(this.buttons.stop);
		}

		if (showButtons.close)
		{
			const closeButtonText = this.getMessage('closeButton');
			this.buttons.close = new CancelButton({
				text: closeButtonText || 'Close',
				color: Button.Color.LIGHT_BORDER,
				tag: Button.Tag.SPAN,
				events:
					{
						click: BX.delegate(this.handleCloseButtonClick, this)
					}
			});
			ret.push(this.buttons.close);
		}

		return ret;
	}

	/**
	 * @param {String} downloadLink
	 * @param {String} fileName
	 * @param {function} purgeHandler
	 * @return self
	 */
	setDownloadButtons(downloadLink: string, fileName: string, purgeHandler: any => {})
	{
		let ret = [];

		if (downloadLink)
		{
			let downloadButtonText = this.getMessage("downloadButton");
			downloadButtonText = downloadButtonText !== "" ? downloadButtonText : "Download file";
			const downloadButton = new Button({
				text: downloadButtonText,
				color: Button.Color.SUCCESS,
				icon: Button.Icon.DOWNLOAD,
				className: DialogStyle.ButtonDownload,
				tag: Button.Tag.LINK,
				link: downloadLink,
				props: {
					//href: downloadLink,
					download: fileName
				}
			});
			ret.push(downloadButton);
		}

		if (typeof(purgeHandler) == 'function')
		{
			let clearButtonText = this.getMessage("clearButton");
			clearButtonText = clearButtonText !== "" ? clearButtonText : "Delete file";
			const clearButton = new Button({
				text: clearButtonText,
				color: Button.Color.LIGHT_BORDER,
				icon: Button.Icon.REMOVE,
				className: DialogStyle.ButtonRemove,
				events:
					{
						click: purgeHandler
					}
			});
			ret.push(clearButton);
		}

		if (this.buttons.close)
		{
			ret.push(this.buttons.close);
		}

		if (ret.length > 0 && this.popupWindow)
		{
			this.popupWindow.setButtons(ret);
		}
		return this;
	}

	resetButtons(showButtons = {'start':true, 'stop':true, 'close':true})
	{
		this._prepareDialogButtons();

		showButtons = showButtons || this.getSetting('showButtons');

		let ret = [];

		if (showButtons.start)
		{
			ret.push(this.buttons.start);
		}
		if (showButtons.stop)
		{
			ret.push(this.buttons.stop);
		}
		if (showButtons.close)
		{
			ret.push(this.buttons.close);
		}
		if (ret.length > 0 && this.popupWindow)
		{
			this.popupWindow.setButtons(ret);
		}
		return this;
	}

	getButton(bid: string): ?Button
	{
		return this.buttons[bid] ??  null;
	}

	lockButton(bid: string, lock: boolean, wait: boolean)
	{
		const btn = this.getButton(bid);
		if (btn)
		{
			btn.setDisabled(lock);
			if (Type.isBoolean(wait))
			{
				btn.setWaiting(wait);
			}
		}
		return this;
	}

	showButton(bid: string, show: boolean)
	{
		const btn = this.getButton(bid);
		if (btn)
		{
			btn.getContainer().style.display = !!show ? '' : 'none';
		}
		if (bid === 'close')
		{
			if (this.popupWindow && this.popupWindow.closeIcon)
			{
				this.popupWindow.closeIcon.style.display = !!show ? '' : 'none';
			}
		}
		return this;
	}

	// endregion

	//region Summary

	setSummary(content: string, isHtml: boolean = false)
	{
		if (this.optionsFieldsBlock)
		{
			BX.clean(this.optionsFieldsBlock);
			this.optionsFieldsBlock.style.display = 'none';
		}
		if (Type.isStringFilled(content))
		{
			if (this.summaryBlock)
			{
				if (!!isHtml)
					this.summaryBlock.innerHTML = content;
				else
					this.summaryBlock.innerHTML = BX.util.htmlspecialchars(content);

				this.summaryBlock.style.display = "block";
			}
		}
		else
		{
			this.summaryBlock.innerHTML = "";
			this.summaryBlock.style.display = "none";
		}
		return this;
	}

	//endregion

	//region Errors

	setErrors(errors: Array<string>, isHtml: bool = false)
	{
		errors.forEach(err => this.setError(err, isHtml));
		return this;
	}
	setError(content, isHtml)
	{
		if (Type.isStringFilled(content))
		{
			this.setSummary('');

			if (this.progressBar)
			{
				this.progressBar.setColor(BX.UI.ProgressBar.Color.DANGER);
			}

			if (!!isHtml)
			{
				this.error.setText(content);
			}
			else
			{
				this.error.setText(BX.util.htmlspecialchars(content));
			}

			this.errorBlock.style.display = "flex";
		}
		return this;
	}
	clearErrors()
	{
		if (this.error)
		{
			this.error.setText('');
		}
		if (this.errorBlock)
		{
			this.errorBlock.style.display = 'none';
		}
		return this;
	}
	setWarning(err: string, isHtml: boolean = false)
	{
		if (Type.isStringFilled(err))
		{
			if (!!isHtml)
			{
				this.warning.setText(err);
			}
			else
			{
				this.warning.setText(BX.util.htmlspecialchars(err));
			}
			this.warningBlock.style.display = "flex";
		}
		return this;
	}
	clearWarnings()
	{
		if (this.warning)
		{
			this.warning.setText("");
		}
		if (this.warningBlock)
		{
			this.warningBlock.style.display = 'none';
		}
		return this;
	}

	//endregion

	//region Progressbar

	setProgressBar(totalItems: number, processedItems: number, textBefore: string)
	{
		if (this.progressBar)
		{
			if (Type.isNumber(processedItems) && Type.isNumber(totalItems) && totalItems > 0)
			{
				BX.show(this.progressBarBlock);
				this.progressBar.setColor(BX.UI.ProgressBar.Color.PRIMARY);
				this.progressBar.setMaxValue(totalItems);
				textBefore = textBefore || "";
				this.progressBar.setTextBefore(textBefore);
				this.progressBar.update(processedItems);
			}
			else
			{
				this.hideProgressBar();
			}
		}
		return this;
	}
	hideProgressBar()
	{
		if (this.progressBar)
		{
			BX.hide(this.progressBarBlock);
		}
		return this;
	}

	//endregion

	//region Initial options

	getOptionField(name: string): ?BaseField
	{
		if (Type.isString(name))
		{
			if (this.fields[name] && this.fields[name] instanceof BaseField)
			{
				return this.fields[name];
			}
		}
		return null;
	}

	getOptionFieldValues()
	{
		let initialOptions = {};
		if (this.optionsFieldsBlock)
		{
			Object.keys(this.fields).forEach(optionName => {
				let field = this.getOptionField(optionName);
				let val = field.getValue();
				if (field.type === 'checkbox' && Type.isBoolean(val))
				{
					initialOptions[optionName] = val ? 'Y' : 'N';
				}
				else if (Type.isArray(val))
				{
					if (Type.isArrayFilled(val))
					{
						initialOptions[optionName] = val;
					}
				}
				else if (val)
				{
					initialOptions[optionName] = val;
				}
			});
		}
		return initialOptions;
	}

	checkOptionFields(): boolean
	{
		let checked = true;
		if (this.optionsFieldsBlock)
		{
			Object.keys(this.fields).forEach(optionName => {
				let field = this.getOptionField(optionName);
				if (field.obligatory)
				{
					if (!field.isFilled())
					{
						field.showWarning();
						checked = false;
					}
					else
					{
						field.hideWarning();
					}
				}
			});
		}
		return checked;
	}

	lockOptionFields(flag: boolean = true)
	{
		if (this.optionsFieldsBlock)
		{
			Object.keys(this.fields).forEach(optionName => {
				let field = this.getOptionField(optionName);
				if (field)
				{
					field.lock(flag);
				}
			});
		}
		return this;
	}
	//endregion
}