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

SPIKE Inline validation #1148

Closed
Closed
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
3,867 changes: 3,857 additions & 10 deletions client/dist/js/bundle.js

Large diffs are not rendered by default.

482 changes: 481 additions & 1 deletion client/dist/styles/bundle.css

Large diffs are not rendered by default.

31 changes: 28 additions & 3 deletions client/src/components/ElementActions/SaveAction.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import React, { useContext } from 'react';
import { compose } from 'redux';
import { connect } from 'react-redux';
import AbstractAction from 'components/ElementActions/AbstractAction';
Expand All @@ -7,12 +7,18 @@ import i18n from 'i18n';
import { loadElementSchemaValue } from 'state/editor/loadElementSchemaValue';
import { loadElementFormStateName } from 'state/editor/loadElementFormStateName';
import { initialize } from 'redux-form';
import { ElementContext } from 'components/ElementEditor/Element';
import { submit } from 'redux-form'



/**
* 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 failureHandlers = useContext(ElementContext);

if (!props.expandable || props.type.broken) {
// Some elemental blocks can not be edited inline (e.g. User form blocks)
// We don't want to add a "Save" action for those blocks.
Expand All @@ -24,7 +30,7 @@ const SaveAction = (MenuComponent) => (props) => {
const handleClick = (event) => {
event.stopPropagation();

const { element, type, securityId, formData, reinitialiseForm } = props;
const { element, type, securityId, formData, reinitialiseForm, submitForm } = props;
const { jQuery: $ } = window;
const noTitle = i18n.inject(
i18n._t(
Expand All @@ -34,6 +40,14 @@ const SaveAction = (MenuComponent) => (props) => {
{ type: type.title }
);

// https://redux-form.com/8.3.0/examples/remotesubmit/
submitForm();
Copy link
Member Author

@emteknetnz emteknetnz Feb 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There was a callback below this to show toast notifications, however I don't see us getting this to work because redux-forms 'submit()' function used for triggering a remote submit isn't thenable nor does it allow for adding a callback or have any other method that allows us to do stuff 'onAfterSuccess / onAfterFailure'

Make me inlined to say that remote submit isn't feasible and we need to put the save/submit button actually on the form


return;

// === The following is wrong, should using the Formbuilder to submit the form ===
// === instead of doing a submissions from the button ==

const endpointSpec = {
url: loadElementSchemaValue('saveUrl', element.id),
method: loadElementSchemaValue('saveMethod'),
Expand Down Expand Up @@ -70,7 +84,7 @@ const SaveAction = (MenuComponent) => (props) => {
type: 'success'
});
})
.catch(() => {
.catch(e => {
$.noticeAdd({
text: i18n.inject(
i18n._t(
Expand All @@ -82,6 +96,11 @@ const SaveAction = (MenuComponent) => (props) => {
stay: false,
type: 'error'
});
e.response.json()
.then(formSchema => {
// failurreHandlers is defined in Element.js and passed via ElementContext
failureHandlers.onFailedSave(formSchema);
});
});
};

Expand Down Expand Up @@ -121,6 +140,12 @@ function mapDispatchToProps(dispatch, ownProps) {
return {
reinitialiseForm(savedData) {
dispatch(initialize(`element.${formName}`, savedData));
},
submitForm() {
console.log('submitting form for elmenet with ID 1 - THIS IS HARDCODED TO element #1');
// dispatch() is a param of mapDispatchToProps()
// submit() is an imported function from 'redux-form'
dispatch(submit('element.ElementForm_1'));
}
};
}
Expand Down
3 changes: 3 additions & 0 deletions client/src/components/ElementEditor/Content.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class Content extends PureComponent {
handleLoadingError,
formDirty,
broken,
formSchema,
} = this.props;

return (
Expand All @@ -44,6 +45,7 @@ class Content extends PureComponent {
activeTab={activeTab}
onFormInit={onFormInit}
handleLoadingError={handleLoadingError}
formSchema={formSchema}
/>
}
{formDirty &&
Expand All @@ -69,6 +71,7 @@ Content.propTypes = {
InlineEditFormComponent: PropTypes.elementType,
handleLoadingError: PropTypes.func,
broken: PropTypes.bool,
formSchema: PropTypes.object,
};

Content.defaultProps = {};
Expand Down
92 changes: 58 additions & 34 deletions client/src/components/ElementEditor/Element.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* global window */

import React, { Component } from 'react';
import React, { Component, createContext } from 'react';
import PropTypes from 'prop-types';
import { elementType } from 'types/elementType';
import { elementTypeType } from 'types/elementTypeType';
Expand All @@ -16,11 +16,17 @@ import { DragSource, DropTarget } from 'react-dnd';
import { getEmptyImage } from 'react-dnd-html5-backend';
import { elementDragSource, isOverTop } from 'lib/dragHelpers';

export const ElementContext = createContext(null);

/**
* The Element component used in the context of an ElementEditor shows the summary
* of an element's details when used in the CMS, including ID, Title and Summary.
*/
class Element extends Component {

// Temporarily turning this off to prevent the BaseElement::validate() error from being caught and displayed
// in a non-useful way
//
static getDerivedStateFromError() {
return { childRenderingError: true };
}
Expand All @@ -39,6 +45,7 @@ class Element extends Component {
initialTab: '',
loadingError: false,
childRenderingError: false,
formSchema: {}
};
}

Expand Down Expand Up @@ -215,6 +222,18 @@ class Element extends Component {
}
}

getFailureHandlers() {
// using method to create object rather then defining object directly in render()
// to prevent linting warning about "consider useMemo() instead"
return {
onFailedSave: (formSchema) => {
this.setState({
formSchema
});
}
};
}

render() {
const {
element,
Expand All @@ -231,7 +250,7 @@ class Element extends Component {
onDragEnd,
} = this.props;

const { childRenderingError, previewExpanded } = this.state;
const { childRenderingError, previewExpanded, formSchema } = this.state;

if (!element.id) {
return null;
Expand All @@ -248,6 +267,8 @@ class Element extends Component {
this.getVersionedStateClassName()
);

const failureHandlers = this.getFailureHandlers();

const content = connectDropTarget(<div
className={elementClassNames}
onClick={this.handleExpand}
Expand All @@ -257,40 +278,42 @@ class Element extends Component {
title={this.getLinkTitle(type)}
key={element.id}
>
<HeaderComponent
element={element}
type={type}
areaId={areaId}
expandable={type.inlineEditable}
link={link}
previewExpanded={previewExpanded && !childRenderingError}
handleEditTabsClick={this.handleTabClick}
activeTab={activeTab}
disableTooltip={isDragging}
onDragEnd={onDragEnd}
/>

{
!childRenderingError &&
<ContentComponent
id={element.id}
fileUrl={element.blockSchema.fileURL}
fileTitle={element.blockSchema.fileTitle}
content={this.getSummary(element, type)}
previewExpanded={previewExpanded && !isDragging}
<ElementContext.Provider value={failureHandlers}>
<HeaderComponent
element={element}
type={type}
areaId={areaId}
expandable={type.inlineEditable}
link={link}
previewExpanded={previewExpanded && !childRenderingError}
handleEditTabsClick={this.handleTabClick}
activeTab={activeTab}
onFormInit={() => this.updateFormTab(activeTab)}
handleLoadingError={this.handleLoadingError}
broken={type.broken}
disableTooltip={isDragging}
onDragEnd={onDragEnd}
failureHandlers={failureHandlers}
/>
}

{
childRenderingError &&
<div className="alert alert-danger mt-2">
{i18n._t('ElementalElement.CHILD_RENDERING_ERROR', 'Something went wrong with this block. Please try saving and refreshing the CMS.')}
</div>
}
{
!childRenderingError &&
<ContentComponent
id={element.id}
fileUrl={element.blockSchema.fileURL}
fileTitle={element.blockSchema.fileTitle}
content={this.getSummary(element, type)}
previewExpanded={previewExpanded && !isDragging}
activeTab={activeTab}
onFormInit={() => this.updateFormTab(activeTab)}
handleLoadingError={this.handleLoadingError}
broken={type.broken}
formSchema={formSchema}
/>
}
{
childRenderingError &&
<div className="alert alert-danger mt-2">
{i18n._t('ElementalElement.CHILD_RENDERING_ERROR', 'Something went wrong with this block. Please try saving and refreshing the CMS.')}
</div>
}
</ElementContext.Provider>
</div>);

if (!previewExpanded) {
Expand Down Expand Up @@ -342,6 +365,7 @@ function mapDispatchToProps(dispatch, ownProps) {
};
}


Element.propTypes = {
element: elementType,
type: elementTypeType.isRequired,
Expand Down
2 changes: 1 addition & 1 deletion client/src/components/ElementEditor/ElementActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ ElementActions.propTypes = {
name: PropTypes.string,
})),
handleEditTabsClick: PropTypes.func.isRequired,
expandable: PropTypes.bool
expandable: PropTypes.bool,
};

ElementActions.defaultProps = {
Expand Down
21 changes: 17 additions & 4 deletions client/src/components/ElementEditor/InlineEditForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import FormBuilder from 'components/FormBuilder/FormBuilder';
import FormBuilderLoader from 'containers/FormBuilderLoader/FormBuilderLoader';
import FormBuilderModal from 'components/FormBuilderModal/FormBuilderModal';
import { loadElementSchemaValue } from 'state/editor/loadElementSchemaValue';
import i18n from 'i18n';
import { loadElementFormStateName } from 'state/editor/loadElementFormStateName';
Expand Down Expand Up @@ -42,20 +44,28 @@ class InlineEditForm extends PureComponent {
}

render() {
const { elementId, extraClass, onClick, onFormInit, formHasState } = this.props;
const { elementId, extraClass, onClick, onFormInit, formHasState, formSchema } = this.props;
const { loadingError } = this.state;

const classNames = classnames('element-editor-editform', extraClass);
const schemaUrl = loadElementSchemaValue('schemaUrl', elementId);

// formTag needs to be a form rather than a div so that the php FormAction that turns into
// a <button type="submit>" submits this <form>, rather than the <form> for the parent page EditForm
const formTag = 'form';

const formProps = {
formTag: 'div',
formTag,
schemaUrl,
identifier: 'element',
refetchSchemaOnMount: !formHasState,
onLoadingError: this.handleLoadingError
};

if (typeof formSchema !== 'undefined' && Object.keys(formSchema).length > 0) {
formProps.schema = formSchema;
}

if (loadingError) {
formProps.loading = false;
}
Expand All @@ -66,7 +76,9 @@ class InlineEditForm extends PureComponent {

return (
<div className={classNames} onClick={onClick} role="presentation">
<FormBuilderLoader {...formProps} />
<FormBuilderLoader {...formProps}>
<div>lorem ipsum</div>
</FormBuilderLoader>
</div>
);
}
Expand All @@ -77,10 +89,11 @@ InlineEditForm.propTypes = {
onClick: PropTypes.func,
elementId: PropTypes.string,
handleLoadingError: PropTypes.func,
formSchema: PropTypes.object,
};

function mapStateToProps(state, ownProps) {
const formName = loadElementFormStateName(ownProps.elementId);
const formName = loadElementFormStateName(ownProps.elementId); // ElementForm_3

return {
formHasState: state.form.formState && state.form.formState.element &&
Expand Down
Loading