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

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