src/components/actions/Actions.jsx
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import {
ButtonToolbar,
ButtonGroup,
Button,
DropdownMenu,
DropdownItem,
} from 'reactstrap';
import cx from 'classnames';
import { isEmpty, every } from 'lodash';
import { callActionImpl } from '../../actions/toolbar';
import ModalDialog from './ModalDialog/ModalDialog';
import factoryResolver from '../../utils/factoryResolver';
import ButtonContainer from './ButtonContainer';
import SecurityNotRender from '../../core/auth/SecurityNotRender';
import linkResolver from '../../utils/linkResolver';
/**
* Компонент redux-обертка для тулбара
* @reactProps {object} actions - объект с src экшенов
* @reactProps {object} toolbar - массив из групп кнопок
* @reactProps {string} containerKey - id контейнера (widgetId, pageId...)
* @reactProps {function} resolve
* @reactProps {object} options
* @reactProps {string} className
* @reactProps {object} style
* @example
* const actions = {
* "update": {
* "src": "dummyActionImpl"
* },
* "delete": {
* "src": "dummyActionImpl"
* }
*}
*
* const toolbar = [
* {
* buttons: [
* {
* id: '1',
* title: "Кнопка",
* actionId: 'dummy',
* hint: "Кликни меня",
* },
* {
* id: '2',
* title: "Click2",
* hint: "Click Click Click",
* subMenu: [
* {
* id: '3',
* title: "Click3",
* actionId: 'dummy',
* hint: "Click Click Click",
* },
* {
* id: '4',
* title: "Click4",
* hint: "Click Click Click",
* actionId: 'dummy'
* }
* ]
* }
* ]
* }
* ]
*
* <Actions toolbar={toolbar} actions={actions} containerKey="FormWidget"/>
*
*/
class Actions extends React.Component {
constructor(props) {
super(props);
this.state = {
confirmVisibleId: null,
};
this.closeConfirm = this.closeConfirm.bind(this);
this.onClickHelper = this.onClickHelper.bind(this);
this.mapButtonConfirmProps = this.mapButtonConfirmProps.bind(this);
}
/**
* Закрывает окноподтверждаения
*/
closeConfirm() {
this.setState({ confirmVisibleId: null });
}
/**
* Обертка вокруг onClick
* @param button
* @param confirm
*/
onClickHelper(button, confirm) {
const { actions, resolve, options } = this.props;
this.onClick(
button.actionId,
button.id,
confirm,
actions,
resolve,
button.validatedWidgetId,
button.validate,
options
);
}
/**
* Маппинг свойст модального окна подтверждения
* @param confirm
* @returns {{text: *}}
*/
mapButtonConfirmProps({ confirm }) {
if (confirm) {
const store = this.context.store.getState();
const { modelLink, text } = confirm;
const resolvedText = linkResolver(store, {
link: modelLink,
value: text,
});
return {
...confirm,
text: resolvedText,
};
}
}
/**
* рендер кнопки или элемента списка дропдауна
* @param Component
* @param button
* @returns {*}
*/
renderButton(Component, button, parentId) {
const btn = (
<React.Fragment>
<ButtonContainer
id={button.id}
onClick={() => this.onClickHelper(button, button.confirm)}
initialProps={button}
component={Component}
containerKey={this.props.containerKey}
parentId={parentId}
/>
<ModalDialog
{...this.mapButtonConfirmProps(button)}
visible={this.state.confirmVisibleId === button.id}
onConfirm={() => {
this.onClickHelper(button);
this.closeConfirm();
}}
onDeny={this.closeConfirm}
close={this.closeConfirm}
/>
</React.Fragment>
);
return <SecurityNotRender config={button.security} component={btn} />;
}
/**
* Корневой рендер кнопок
* @param buttons
* @returns {*}
*/
renderButtons(buttons) {
return (
buttons &&
buttons.map((button, index) => {
let buttonEl = null;
if (button.subMenu) {
buttonEl = this.renderDropdownButton(button);
} else if (button.dropdownSrc) {
buttonEl = this.renderCustomDropdown(button);
} else {
buttonEl = this.renderButton(Button, button);
}
return (
<SecurityNotRender
key={index}
config={button.security}
component={buttonEl}
/>
);
})
);
}
/**
* резолв экшена
*/
onClick(
actionId,
id,
confirm,
actions,
resolve,
validatedWidgetId,
validate = true,
options = {}
) {
if (confirm) {
this.setState({ confirmVisibleId: id });
} else {
resolve(actions[actionId].src, validatedWidgetId, {
...actions[actionId].options,
actionId,
buttonId: id,
validate,
pageId: this.props.pageId,
...options[actionId],
});
}
}
/**
* рендер кнопки-дропдауна
*/
renderDropdownButton({
title,
color,
id,
hint,
visible,
hintPosition,
subMenu,
icon,
size,
disabled,
}) {
const dropdownProps = {
size,
title,
color,
hint,
icon,
visible,
disabled,
hintPosition,
};
return (
<ButtonContainer
id={id}
component={DropdownMenu}
initialProps={dropdownProps}
containerKey={this.props.containerKey}
color={color}
>
{subMenu.map(item => this.renderButton(DropdownItem, item, id))}
</ButtonContainer>
);
}
/**
* Рендер дропдауна с кастомным меню (по dropdownSrc)
* @param title
* @param color
* @param id
* @param hint
* @param hintPosition
* @param visible
* @param subMenu
* @param dropdownSrc
* @param icon
* @param actionId
* @param size
* @returns {*}
*/
renderCustomDropdown({
title,
color,
id,
hint,
hintPosition,
visible,
subMenu,
dropdownSrc,
icon,
actionId,
size,
}) {
const { containerKey } = this.props;
const CustomMenu = factoryResolver(dropdownSrc);
const dropdownProps = {
size,
title,
color,
hint,
hintPosition,
visible,
icon,
};
return (
<ButtonContainer
id={id}
component={DropdownMenu}
containerKey={this.props.containerKey}
initialProps={dropdownProps}
>
<CustomMenu widgetId={containerKey} />
</ButtonContainer>
);
}
/**
* Базовый рендер
*/
render() {
const { toolbar, className, style } = this.props;
return (
<ButtonToolbar className={className} style={style}>
{toolbar.map(({ buttons, style, className, security }, i) => {
const buttonGroup = (
<ButtonGroup style={style} className={className}>
{this.renderButtons(buttons)}
</ButtonGroup>
);
return (
<SecurityNotRender
key={i}
config={security}
component={buttonGroup}
/>
);
})}
</ButtonToolbar>
);
}
}
Actions.contextTypes = {
store: PropTypes.object,
};
Actions.defaultProps = {
toolbar: [],
};
Actions.propTypes = {
toolbar: PropTypes.array,
actions: PropTypes.object,
containerKey: PropTypes.string,
className: PropTypes.string,
style: PropTypes.object,
resolve: PropTypes.func,
options: PropTypes.object,
};
/**
* мэппинг диспатча экшенов в функции
* @param dispatch
*/
const mapDispatchToProps = dispatch => {
return {
resolve: (actionSrc, validatedWidgetId, options) => {
dispatch(
callActionImpl(actionSrc, { ...options, dispatch, validatedWidgetId })
);
},
};
};
export default connect(
null,
mapDispatchToProps
)(Actions);