Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NEW Add save action to element inline edit forms #392

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion client/dist/js/bundle.js

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions client/src/boot/registerTransforms.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import revertToBlockVersionMutation from 'state/history/revertToBlockVersionMuta
import readBlocksForPageQuery from 'state/editor/readBlocksForPageQuery';
import ArchiveAction from 'components/ElementActions/ArchiveAction';
import PublishAction from 'components/ElementActions/PublishAction';
import SaveAction from 'components/ElementActions/SaveAction';
import UnpublishAction from 'components/ElementActions/UnpublishAction';

export default () => {
Expand Down Expand Up @@ -60,6 +61,7 @@ export default () => {

// Add elemental editor actions
Injector.transform('element-actions', (updater) => {
updater.component('ElementActions', SaveAction, 'ElementActionsWithSave');
updater.component('ElementActions', ArchiveAction, 'ElementActionsWithArchive');
updater.component('ElementActions', PublishAction, 'ElementActionsWithPublish');
updater.component('ElementActions', UnpublishAction, 'ElementActionsWithUnpublish');
Expand Down
20 changes: 10 additions & 10 deletions client/src/components/ElementActions/AbstractAction.js
Original file line number Diff line number Diff line change
@@ -1,28 +1,28 @@
import React, { PropTypes } from 'react';
import classNames from 'classnames';
import { DropdownItem } from 'reactstrap';

/**
* Renders an action item for the "more actions" dropdown on elements
*/
const AbstractAction = (props) => {
const { disabled, extraClass, title, onClick } = props;
const { className, title } = props;

const itemProps = {
className: classNames(className, 'dropdown-item'),
...props,
};

return (
<button
onClick={onClick}
disabled={disabled}
title={title}
type="button"
className={classNames(extraClass, 'dropdown-item')}
>
<DropdownItem {...itemProps}>
{title}
</button>
</DropdownItem>
);
};

AbstractAction.propTypes = {
disabled: PropTypes.bool,
extraClass: PropTypes.string,
className: PropTypes.string,
onClick: PropTypes.func,
title: PropTypes.string,
};
Expand Down
3 changes: 2 additions & 1 deletion client/src/components/ElementActions/ArchiveAction.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,9 @@ const ArchiveAction = (MenuComponent) => (props) => {

const newProps = {
title: i18n._t('ArchiveAction.ARCHIVE', 'Archive'),
extraClass: 'element-editor__actions-archive',
className: 'element-editor__actions-archive',
onClick: handleClick,
toggle: props.toggle,
};

return (
Expand Down
3 changes: 2 additions & 1 deletion client/src/components/ElementActions/PublishAction.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,9 @@ const PublishAction = (MenuComponent) => (props) => {

const newProps = {
title: i18n._t('PublishAction.PUBLISH', 'Publish'),
extraClass: 'element-editor__actions-publish',
className: 'element-editor__actions-publish',
onClick: handleClick,
toggle: props.toggle,
};

return (
Expand Down
66 changes: 66 additions & 0 deletions client/src/components/ElementActions/SaveAction.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/* global window */
import React from 'react';
import { compose } from 'redux';
import { connect } from 'react-redux';
import AbstractAction from 'components/ElementActions/AbstractAction';
import backend from 'lib/Backend';
import i18n from 'i18n';
import { loadElementSchemaValue } from 'state/editor/loadElementSchemaValue';
import { getSerializedFormData } from 'state/editor/getSerializedFormData';

/**
* Using a REST backend, serialize the current form data and post it to the backend endpoint to save
* the inline edit form's data for the current block.
*/
const SaveAction = (MenuComponent) => (props) => {
const handleClick = (event) => {
event.stopPropagation();

const { id, securityId } = props;

const formData = getSerializedFormData(`Form_ElementForm_${id}`);

const endpointSpec = {
url: loadElementSchemaValue('saveUrl', id),
method: loadElementSchemaValue('saveMethod'),
payloadFormat: loadElementSchemaValue('payloadFormat'),
defaultData: {
SecurityID: securityId
},
};

const endpoint = backend.createEndpointFetcher(endpointSpec);
endpoint(formData).then(() => {
// Update the Apollo query cache with the new form data
const { apolloClient } = window.ss;

// @todo optimistically update the data for the current element instead of
// rerunning the whole query
apolloClient.queryManager.reFetchObservableQueries();
});
};

const newProps = {
title: i18n._t('SaveAction.SAVE', 'Save'),
className: 'element-editor__actions-save',
onClick: handleClick,
};

return (
<MenuComponent {...props}>
{props.children}

<AbstractAction {...newProps} />
</MenuComponent>
);
};

function mapStateToProps(state) {
return {
securityId: state.config.SecurityID,
};
}

export { SaveAction as Component };

export default compose(connect(mapStateToProps), SaveAction);
3 changes: 2 additions & 1 deletion client/src/components/ElementActions/UnpublishAction.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,9 @@ const UnpublishAction = (MenuComponent) => (props) => {

const newProps = {
title: i18n._t('UnpublishAction.UNPUBLISH', 'Unpublish'),
extraClass: 'element-editor__actions-unpublish',
className: 'element-editor__actions-unpublish',
onClick: handleClick,
toggle: props.toggle,
};

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import React from 'react';
import AbstractAction from '../AbstractAction';
import Enzyme, { shallow } from 'enzyme';
import Enzyme, { mount } from 'enzyme';
import Adapter from 'enzyme-adapter-react-15.4/build/index';

Enzyme.configure({ adapter: new Adapter() });
Expand All @@ -13,18 +13,19 @@ describe('AbstractAction', () => {
let wrapper = null;

beforeEach(() => {
wrapper = shallow(
wrapper = mount(
<AbstractAction
onClick={clickHandler}
title="My abstract action"
disabled={false}
extraClass="foo-bar"
className="foo-bar"
toggle={false}
/>
);
});

it('renders a button', () => {
expect(wrapper.find('button').length).toBe(1);
it('renders a DropdownItem', () => {
expect(wrapper.find('DropdownItem').length).toBe(1);
});

it('includes the title text', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ describe('ArchiveAction', () => {
id={123}
isPublished
actions={{ handleArchiveBlock: mockMutation }}
toggle={false}
/>
);
});
Expand Down Expand Up @@ -55,6 +56,7 @@ describe('ArchiveAction', () => {
id={123}
isPublished={false}
actions={{ handleArchiveBlock: mockMutation }}
toggle={false}
/>
);
const mockConfirm = jest.fn();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ describe('PublishAction', () => {
version={234}
isLiveVersion={false}
actions={{ handlePublishBlock: mockMutation }}
toggle={false}
/>
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ describe('UnpublishAction', () => {
id={123}
isPublished
actions={{ handleUnpublishBlock: mockMutation }}
toggle={false}
/>
);

Expand Down
12 changes: 10 additions & 2 deletions client/src/components/ElementEditor/Element.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,15 @@ class Element extends Component {
* Expand the element to show the preview
* If the element is not inline-editable, take user to the GridFieldDetailForm to edit the record
*/
handleExpand() {
handleExpand(event) {
const { element, link } = this.props;

if (event.target.type === 'button') {
// Stop bubbling if the click target was a button within this container
event.stopPropagation();
return;
}

if (element.InlineEditable) {
this.setState({
previewExpanded: !this.state.previewExpanded
Expand All @@ -69,7 +75,7 @@ class Element extends Component {
*/
handleKeyUp(event) {
if (event.keyCode === 13) {
this.handleExpand();
this.handleExpand(event);
}
}

Expand All @@ -80,6 +86,7 @@ class Element extends Component {
ContentComponent,
link,
editTabs,
pageId,
} = this.props;

const { previewExpanded } = this.state;
Expand Down Expand Up @@ -119,6 +126,7 @@ class Element extends Component {
elementType={element.BlockSchema.type}
fontIcon={element.BlockSchema.iconClass}
link={link}
pageId={pageId}
editTabs={editTabs}
previewExpanded={previewExpanded}
expandable={element.InlineEditable}
Expand Down
2 changes: 0 additions & 2 deletions client/src/components/ElementEditor/ElementActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,12 @@ class ElementActions extends Component {
'font-icon-dot-3',
];

// Remove btn-icon-xl make btn-sm
return (
<ActionMenuComponent
id={`element-editor-actions-${id}`}
className={'element-editor-header__actions-dropdown'}
dropdownMenuProps={{ right: true }}
dropdownToggleClassNames={dropdownToggleClassNames}
toggleCallback={(event) => event.stopPropagation()}
>
{ this.renderEditTabs() }
{ this.renderDivider() }
Expand Down
3 changes: 2 additions & 1 deletion client/src/components/ElementEditor/ElementList.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class ElementList extends Component {
* in registerTransforms.js.
*/
renderBlocks() {
const { ElementComponent, blocks } = this.props;
const { ElementComponent, blocks, pageId } = this.props;

// Blocks can be either null or an empty array
if (!blocks) {
Expand All @@ -44,6 +44,7 @@ class ElementList extends Component {
<ElementComponent
key={element.ID}
element={element}
pageId={pageId}
editTabs={this.getEditTabs(element)}
link={element.BlockSchema.actions.edit}
/>
Expand Down
1 change: 0 additions & 1 deletion client/src/components/ElementEditor/Header.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ class Header extends Component {
});
}


/**
* Renders a message indicating the current versioned state of the element
*
Expand Down
15 changes: 5 additions & 10 deletions client/src/components/ElementEditor/InlineEditForm.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,14 @@
import React, { Component, PropTypes } from 'react';
import React, { PureComponent, PropTypes } from 'react';
import classnames from 'classnames';
import FormBuilderLoader from 'containers/FormBuilderLoader/FormBuilderLoader';
import Config from 'lib/Config';

class InlineEditForm extends Component {
getConfig() {
const sectionKey = 'DNADesign\\Elemental\\Controllers\\ElementalAreaController';
return Config.getSection(sectionKey);
}
import { loadElementSchemaValue } from 'state/editor/loadElementSchemaValue';

class InlineEditForm extends PureComponent {
render() {
const { extraClass, elementId, onClick } = this.props;
const { elementId, extraClass, onClick } = this.props;

const classNames = classnames('element-editor-editform', extraClass);
const schemaUrl = `${this.getConfig().form.elementForm.schemaUrl}/${elementId}`;
const schemaUrl = loadElementSchemaValue('schemaUrl', elementId);

const formProps = {
formTag: 'div',
Expand Down
28 changes: 28 additions & 0 deletions client/src/state/editor/getSerializedFormData.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/* global document */
/**
* Get all form inputs (etc) inside the given formName div and return as a serialised object
*
* @param {string} formName
* @returns {object}
*/
export const getSerializedFormData = (formName) => {
const form = document.getElementById(formName);
if (!form) {
return {};
}

const fields = form.querySelectorAll('input, select, textarea, checkbox');

const output = {};
for (let i = 0; i <= fields.length; i++) {
if (fields[i]) {
const formField = fields[i];
const fieldName = formField.name;
if (fieldName !== '') {
output[fieldName] = formField.value;
}
}
}

return output;
};
19 changes: 19 additions & 0 deletions client/src/state/editor/loadElementSchemaValue.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import Config from 'lib/Config';

/**
* Returns the named component from the elementForm's schema data
*
* @param {string} key
* @param {number|null} elementId If provided, will be concatenated on as value is treated as a URL
* @returns {string}
*/
export const loadElementSchemaValue = (key, elementId = null) => {
const sectionKey = 'DNADesign\\Elemental\\Controllers\\ElementalAreaController';
const section = Config.getSection(sectionKey);
const schemaValue = section.form.elementForm[key] || '';

if (elementId) {
return `${schemaValue}/${elementId}`;
}
return schemaValue;
};
Loading