Commit 9978774c authored by Florent Chehab's avatar Florent Chehab

Updated field/form setup to prevent bugs with with styles

Other adaptations with new setup
parent 83ecd677
......@@ -9,8 +9,6 @@ import Typography from "@material-ui/core/Typography";
import CloseIcon from "@material-ui/icons/Close";
import Alert from "./Alert";
// import renderFieldsMixIn from "./editorFunctions/renderFieldsMixIn";
// Form is imported only for type hints
// eslint-disable-next-line no-unused-vars
import Form from "./Form";
......@@ -114,6 +112,12 @@ class Editor extends Component {
}
/**
* Function to handle close editor request from the user.
* It checks if there is data to save or not.
*
* @memberof Editor
*/
handleCloseEditorRequest() {
if (this.formHasChanges()) {
this.alertChangesNotSaved();
......@@ -316,8 +320,6 @@ class Editor extends Component {
}
// TODO move this to form and update
// Object.assign(Editor.prototype, renderFieldsMixIn);
Editor.propTypes = {
classes: PropTypes.object.isRequired,
......
......@@ -2,6 +2,7 @@ import React, { Component } from "react";
import PropTypes from "prop-types";
import areSameObjects from "../../../utils/areSameObjects";
import renderFieldsMixIn from "./renderFieldsMixIn";
/**
* React component that should contain `Field` instances.
......@@ -19,24 +20,35 @@ class Form extends Component {
* This method MUST be used on all field inside a `Form` instance.
*
* Function that returns the value corresponding to `fieldMapping` and
* a reference for the field. This reference is stored in the Form class so that
* we can easily access the fields value from the form.
* a reference to the Form so that the fields can subscribe.
*
* @param {string} fieldMapping
* @param {function} [convertValue=v => v] Method applied to the value from the modelData to get "the value"
* @memberof Form
*/
getReferenceAndValue(fieldMapping) {
const ref = React.createRef();
this.fields[fieldMapping] = ref; // store the reference for later use
return { value: this.props.modelData[fieldMapping], ref };
getReferenceAndValue(fieldMapping, convertValue = v => v) {
// using react ref was to complicated with ref forwarding not working with withStyles.
return { value: convertValue(this.props.modelData[fieldMapping]), form: this, fieldMapping };
}
/**
* Function to be used in fields so that they subscribe to a form
*
* Using ref to field is not working with withStyles ref forwarding issues.
*
* @param {string} fieldMapping
* @param {Field} field
* @memberof Form
*/
fieldSubscribe(fieldMapping, field) {
this.fields[fieldMapping] = field;
}
/**
* Function that returns the fields contained in the form
* as an array of {fieldMapping: string, field: Field}
*
* Works only if the `getReferenceAndValue` was used on the Field props.
* Works only if the `getReferenceAndValue` was used on the Field props and the field subscribed.
*
* @returns {Array}
* @memberof Form
......@@ -44,7 +56,7 @@ class Form extends Component {
getFields() {
return Object.keys(this.fields)
.map(fieldMapping =>
({ fieldMapping, field: this.fields[fieldMapping].current })
({ fieldMapping, field: this.fields[fieldMapping] })
);
}
......@@ -77,6 +89,17 @@ class Form extends Component {
}
/**
* Combine errors constructed with buildError
*
* @param {array} arrayOfErrors
* @returns
* @memberof Form
*/
combineErrors(arrayOfErrors) {
return this.buildError(arrayOfErrors.flatMap(error => error.messages));
}
/**
* Function to build all the errors from the fields of the form.
*
......@@ -130,6 +153,9 @@ class Form extends Component {
}
// Copy all the custom already ready render fields mix in
Object.assign(Form.prototype, renderFieldsMixIn);
Form.propTypes = {
......
import { PureComponent } from "react";
import PropTypes from "prop-types";
import Form from "../Form";
class Field extends PureComponent {
constructor(props) {
super(props);
// make sure to subscribe ! IMPORTANT
props.form.fieldSubscribe(props.fieldMapping, this);
let { value } = props;
if (typeof this.defaultNullValue !== "undefined" && value === null) {
value = this.defaultNullValue;
......@@ -50,6 +54,8 @@ Field.propTypes = {
required: PropTypes.bool.isRequired,
label: PropTypes.string,
value: PropTypes.isRequired,
form: PropTypes.oneOf([Form]).isRequired,
fieldMapping: PropTypes.string.isRequired,
};
export default Field;
import React from "react";
import Typography from "@material-ui/core/Typography";
import getObjModerationLevel from "../../../../utils/getObjModerationLevels";
import SelectField from "../fields/SelectField";
import UsefulLinksField from "../fields/UsefulLinksField";
import MarkdownField from "../fields/MarkdownField";
import TextField from "../fields/TextField";
import MultiSelectField from "../fields/MultiSelectField";
import NumberField from "../fields/NumberField";
import getObjModerationLevel from "../../../utils/getObjModerationLevels";
import SelectField from "./fields/SelectField";
import UsefulLinksField from "./fields/UsefulLinksField";
import MarkdownField from "./fields/MarkdownField";
import TextField from "./fields/TextField";
import MultiSelectField from "./fields/MultiSelectField";
import NumberField from "./fields/NumberField";
import store from "../../../store/index";
import { getLatestRead } from "../../../api/utils";
import __map from "lodash/map";
export default {
renderObjModerationLevelField() {
const { obj_moderation_level } = this.props.modelData;
const possibleObjModeration = getObjModerationLevel(this.getReadData("userData").owner_level, true);
/**
* For field mixins that handle custom props, we need to make use of getReferenceAndValue too
*
* @param {object} props
* @returns
*/
customizeProps(props) {
return Object.assign(props, { ...this.getReferenceAndValue(props.fieldMapping) });
},
renderObjModerationLevelField() {
// hack to access directly the store and get the value we need.
const userData = getLatestRead(store.getState().api.userDataSpecific).data,
possibleObjModeration = getObjModerationLevel(userData.owner_level, true);
if (possibleObjModeration.length > 1) {
return (
<div>
<Typography variant='caption'>Niveau de modération souhaité (en plus TODO </Typography>
<SelectField label={"Niveau de modération pour ce module"}
{...this.getReferenceAndValue("obj_moderation_level")}
required={true}
value={obj_moderation_level}
fieldMapping={"obj_moderation_level"}
options={possibleObjModeration}
formManager={this}
/>
</div>
);
......@@ -37,8 +48,6 @@ export default {
},
renderImportanceLevelField() {
const { importance_level } = this.props.modelData;
//TODO change below use JSON
const options = [
{ "label": "Normal", "value": "-" },
......@@ -50,23 +59,18 @@ export default {
<div>
<Typography variant='caption'>Qualification de l'importance de l'information présentée</Typography>
<SelectField label={"Niveau d'importance"}
{...this.getReferenceAndValue("importance_level")}
required={true}
value={importance_level}
fieldMapping={"importance_level"}
options={options}
formManager={this}
/>
</div>
);
},
renderUsefulLinksField() {
const { useful_links } = this.props.modelData;
return (
<UsefulLinksField label={"Lien(s) utile(s) (ex : vers ces informations)"}
value={useful_links}
formManager={this}
fieldMapping={"useful_links"}
{...this.getReferenceAndValue("useful_links")}
/>
);
},
......@@ -74,83 +78,62 @@ export default {
renderMarkdownField(props) {
return (
<MarkdownField
{...this.addValueToProps(props)}
formManager={this}
{...this.customizeProps(props)}
/>
);
},
renderCommentField() {
const { comment } = this.props.modelData;
return this.renderMarkdownField({
label: "Commentaire associé à ces informations",
maxLength: 500,
value: comment,
formManager: this,
fieldMapping: "comment",
});
},
addValueToProps(props) {
if (typeof props.value == "undefined") {
return Object.assign(props, { value: this.props.modelData[props.fieldMapping] });
} else {
return props;
}
},
renderTextField(props) {
return (
<TextField
{...this.addValueToProps(props)}
formManager={this}
{...this.customizeProps(props)}
/>
);
},
renderTitleField() {
const { title } = this.props.modelData;
return this.renderTextField({
label: "Titre",
required: true,
value: title,
maxLength: 150,
fieldMapping: "title",
});
},
renderUniversitiesField() {
const { modelData } = this.props;
const { outsideData } = this.props;
const universities = __map(outsideData.universities,
(univ) => { return { label: univ.name, value: univ.id, disabled: false }; }
);
const { outsideData } = this.props,
universities = __map(outsideData.universities, // TODO __map required ?
(univ) => { return { label: univ.name, value: univ.id, disabled: false }; }
);
return (
<MultiSelectField label={"Universités concernées"}
{...this.getReferenceAndValue("universities")}
required={true}
value={modelData.universities}
options={universities}
formManager={this}
fieldMapping={"universities"}
/>
);
},
renderCountriesField() {
const { modelData } = this.props;
const { outsideData } = this.props;
const countries = __map(outsideData.countries,
(country) => { return { label: country.name, value: country.id, disabled: false }; }
);
const { outsideData } = this.props,
countries = __map(outsideData.countries, // TODO __map required ?
(country) => { return { label: country.name, value: country.id, disabled: false }; }
);
return (
<MultiSelectField label={"Pays concernés"}
{...this.getReferenceAndValue("countries")}
required={true}
value={modelData.countries}
options={countries}
formManager={this}
fieldMapping={"countries"}
/>
);
},
......@@ -158,8 +141,7 @@ export default {
renderSelectField(props) {
return (
<SelectField
{...this.addValueToProps(props)}
formManager={this}
{...this.customizeProps(props)}
/>
);
},
......@@ -179,10 +161,9 @@ export default {
renderNumberField(props) {
return (
<NumberField
{...this.addValueToProps(props)}
formManager={this}
{...this.customizeProps(props)}
/>
);
},
};
\ No newline at end of file
};
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment