error validation

This commit is contained in:
Delucse 2020-09-19 09:38:38 +02:00
parent b20bde4e83
commit 111b924988
10 changed files with 268 additions and 73 deletions

View File

@ -1,4 +1,4 @@
import { BUILDER_CHANGE, BUILDER_TITLE, BUILDER_ID, BUILDER_ADD_STEP, BUILDER_DELETE_STEP, BUILDER_CHANGE_STEP, BUILDER_CHANGE_ORDER, BUILDER_DELETE_PROPERTY } from './types';
import { BUILDER_CHANGE, BUILDER_ERROR, BUILDER_TITLE, BUILDER_ID, BUILDER_ADD_STEP, BUILDER_DELETE_STEP, BUILDER_CHANGE_STEP, BUILDER_CHANGE_ORDER, BUILDER_DELETE_PROPERTY } from './types';
export const changeTutorialBuilder = () => (dispatch) => {
dispatch({
@ -35,9 +35,19 @@ export const addStep = (index) => (dispatch, getState) => {
type: BUILDER_ADD_STEP,
payload: steps
});
dispatch(addErrorStep(index));
dispatch(changeTutorialBuilder());
};
export const addErrorStep = (index) => (dispatch, getState) => {
var error = getState().builder.error;
error.steps.splice(index, 0, {});
dispatch({
type: BUILDER_ERROR,
payload: error
});
};
export const removeStep = (index) => (dispatch, getState) => {
var steps = getState().builder.steps;
steps.splice(index, 1);
@ -45,9 +55,19 @@ export const removeStep = (index) => (dispatch, getState) => {
type: BUILDER_DELETE_STEP,
payload: steps
});
dispatch(removeErrorStep(index));
dispatch(changeTutorialBuilder());
};
export const removeErrorStep = (index) => (dispatch, getState) => {
var error = getState().builder.error;
error.steps.splice(index, 1);
dispatch({
type: BUILDER_ERROR,
payload: error
});
};
export const changeContent = (index, property, content) => (dispatch, getState) => {
var steps = getState().builder.steps;
var step = steps[index];
@ -79,5 +99,83 @@ export const changeStepIndex = (fromIndex, toIndex) => (dispatch, getState) => {
type: BUILDER_CHANGE_ORDER,
payload: steps
});
dispatch(changeErrorStepIndex(fromIndex, toIndex));
dispatch(changeTutorialBuilder());
};
export const changeErrorStepIndex = (fromIndex, toIndex) => (dispatch, getState) => {
var error = getState().builder.error;
var errorStep = error.steps[fromIndex];
error.steps.splice(fromIndex, 1);
error.steps.splice(toIndex, 0, errorStep);
dispatch({
type: BUILDER_ERROR,
payload: error
});
};
export const setError = (index, property) => (dispatch, getState) => {
var error = getState().builder.error;
console.log(index);
if(index !== undefined){
error.steps[index][property] = true;
}
else {
error[property] = true;
}
dispatch({
type: BUILDER_ERROR,
payload: error
});
dispatch(changeTutorialBuilder());
};
export const deleteError = (index, property) => (dispatch, getState) => {
var error = getState().builder.error;
if(index !== undefined){
delete error.steps[index][property];
}
else {
delete error[property];
}
dispatch({
type: BUILDER_ERROR,
payload: error
});
dispatch(changeTutorialBuilder());
};
export const setSubmitError = () => (dispatch, getState) => {
var builder = getState().builder;
if(builder.id === ''){
dispatch(setError(undefined, 'id'));
}
if(builder.title === ''){
dispatch(setError(undefined, 'title'));
}
for(var i = 0; i < builder.steps.length; i++){
if(i === 0 && builder.steps[i].hardware.length < 1){
dispatch(setError(i, 'hardware'));
}
if(builder.steps[i].headline === ''){
dispatch(setError(i, 'headline'));
}
if(builder.steps[i].text === ''){
dispatch(setError(i, 'text'));
}
}
};
export const checkError = () => (dispatch, getState) => {
dispatch(setSubmitError());
var error = getState().builder.error;
if(error.id || error.title){
return false;
}
for(var i = 0; i < error.steps.length; i++){
if(Object.keys(error.steps[i]).length > 0){
return false
}
}
return true;
}

View File

@ -24,3 +24,4 @@ export const BUILDER_DELETE_STEP = 'BUILDER_DELETE_STEP';
export const BUILDER_CHANGE_STEP = 'BUILDER_CHANGE_STEP';
export const BUILDER_CHANGE_ORDER = 'BUILDER_CHANGE_ORDER';
export const BUILDER_DELETE_PROPERTY = 'BUILDER_DELETE_PROPERTY';
export const BUILDER_ERROR = 'BUILDER_ERROR';

View File

@ -1,7 +1,7 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { changeContent, deleteProperty } from '../../../actions/tutorialBuilderActions';
import { changeContent, deleteProperty, setError, deleteError } from '../../../actions/tutorialBuilderActions';
import moment from 'moment';
import localization from 'moment/locale/de';
@ -11,7 +11,7 @@ import BlocklyWindow from '../../Blockly/BlocklyWindow';
import { withStyles } from '@material-ui/core/styles';
import Switch from '@material-ui/core/Switch';
import FormControlLabel from '@material-ui/core/FormControlLabel';
import FormLabel from '@material-ui/core/FormLabel';
import FormHelperText from '@material-ui/core/FormHelperText';
import Button from '@material-ui/core/Button';
import Grid from '@material-ui/core/Grid';
@ -42,9 +42,29 @@ class BlocklyExample extends Component {
};
}
componentDidUpdate(props){
componentDidMount(){
if(this.props.task){
this.props.setError(this.props.index, 'xml');
}
}
componentDidUpdate(props, state){
if(props.task !== this.props.task || props.value !== this.props.value){
this.setState({checked: this.props.task ? this.props.task : this.props.value ? true : false});
this.setState({checked: this.props.task ? this.props.task : this.props.value ? true : false},
() => this.isError()
);
}
if(state.checked !== this.state.checked){
this.isError();
}
}
isError = () => {
if(this.state.checked && !this.props.value){
this.props.setError(this.props.index, 'xml');
}
else {
this.props.deleteError(this.props.index, 'xml');
}
}
@ -75,8 +95,8 @@ class BlocklyExample extends Component {
}
/>
{this.state.checked ? !this.props.value ?
<FormLabel className={this.props.classes.errorColor}>Es ist noch keine Eingabe gemacht worden.</FormLabel>
: <FormLabel>Die letzte Einreichung erfolgte um {this.state.input} Uhr.</FormLabel>
<FormHelperText style={{lineHeight: 'initial', marginBottom: '10px'}} className={this.props.classes.errorColor}>Reiche deine Blöcke ein, indem du auf den rot gefärbten Button klickst.</FormHelperText>
: <FormHelperText style={{lineHeight: 'initial', marginBottom: '10px'}}>Die letzte Einreichung erfolgte um {this.state.input} Uhr.</FormHelperText>
: null}
{this.state.checked ?
<div>
@ -104,6 +124,8 @@ class BlocklyExample extends Component {
BlocklyExample.propTypes = {
changeContent: PropTypes.func.isRequired,
deleteProperty: PropTypes.func.isRequired,
setError: PropTypes.func.isRequired,
deleteError: PropTypes.func.isRequired,
xml: PropTypes.string.isRequired
};
@ -112,4 +134,4 @@ const mapStateToProps = state => ({
});
export default connect(mapStateToProps, { changeContent, deleteProperty })(withStyles(styles, {withTheme: true})(BlocklyExample));
export default connect(mapStateToProps, { changeContent, deleteProperty, setError, deleteError })(withStyles(styles, {withTheme: true})(BlocklyExample));

View File

@ -1,21 +1,14 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { checkError } from '../../../actions/tutorialBuilderActions';
import Breadcrumbs from '../../Breadcrumbs';
import Id from './Id';
import Title from './Textfield';
import Step from './Step';
import Typography from '@material-ui/core/Typography';
import Button from '@material-ui/core/Button';
import TextField from '@material-ui/core/TextField';
import OutlinedInput from '@material-ui/core/OutlinedInput';
import InputLabel from '@material-ui/core/InputLabel';
import FormControl from '@material-ui/core/FormControl';
import { faPlus, faMinus } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
class Builder extends Component {
@ -27,8 +20,8 @@ class Builder extends Component {
<h1>Tutorial-Builder</h1>
<Id />
<Title value={this.props.title} property={'title'} label={'Titel'}/>
<Id error={this.props.error} value={this.props.id}/>
<Title value={this.props.title} property={'title'} label={'Titel'} error={this.props.error}/>
{this.props.steps.map((step, i) =>
<Step step={step} index={i} />
@ -36,7 +29,7 @@ class Builder extends Component {
)}
<Button variant='contained' color='primary' onClick={() => {alert('hi')}}>Submit</Button>
<Button variant='contained' color='primary' onClick={() => {var error = this.props.checkError(); alert(error);}}>Tutorial-Vorlage erstellen</Button>
</div>
@ -49,15 +42,19 @@ class Builder extends Component {
}
Builder.propTypes = {
checkError: PropTypes.func.isRequired,
title: PropTypes.string.isRequired,
steps: PropTypes.array.isRequired,
change: PropTypes.number.isRequired
change: PropTypes.number.isRequired,
error: PropTypes.object.isRequired
};
const mapStateToProps = state => ({
title: state.builder.title,
id: state.builder.id,
steps: state.builder.steps,
change: state.builder.change
change: state.builder.change,
error: state.builder.error
});
export default connect(mapStateToProps, null)(Builder);
export default connect(mapStateToProps, { checkError })(Builder);

View File

@ -1,7 +1,7 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { changeContent } from '../../../actions/tutorialBuilderActions';
import { changeContent, setError, deleteError } from '../../../actions/tutorialBuilderActions';
import hardware from '../../../data/hardware.json';
@ -11,6 +11,7 @@ import withWidth, { isWidthDown } from '@material-ui/core/withWidth';
import GridList from '@material-ui/core/GridList';
import GridListTile from '@material-ui/core/GridListTile';
import GridListTileBar from '@material-ui/core/GridListTileBar';
import FormHelperText from '@material-ui/core/FormHelperText';
const styles = theme => ({
multiGridListTile: {
@ -31,6 +32,11 @@ const styles = theme => ({
width: 'calc(100% - 4px)',
height: 'calc(100% - 4px)',
border: `2px solid ${theme.palette.primary.main}`
},
errorColor: {
color: theme.palette.error.dark,
lineHeight: 'initial',
marginBottom: '10px'
}
});
@ -43,13 +49,22 @@ class Requirements extends Component {
}
else {
hardwareArray.push(hardware);
if(this.props.error.steps[this.props.index].hardware){
this.props.deleteError(this.props.index, 'hardware');
}
}
this.props.changeContent(this.props.index, 'hardware', hardwareArray);
if(hardwareArray.length === 0){
this.props.setError(this.props.index, 'hardware');
}
}
render() {
var cols = isWidthDown('md', this.props.width) ? isWidthDown('sm', this.props.width) ? isWidthDown('xs', this.props.width) ? 2 : 3 : 4 : 6;
return (
<div>
<FormHelperText style={this.props.error.steps[this.props.index].hardware ? {lineHeight: 'initial', marginTop: '5px'} : {marginTop: '5px', lineHeight: 'initial', marginBottom: '10px'}}>Beachte, dass die Reihenfolge des Auswählens maßgebend ist.</FormHelperText>
{this.props.error.steps[this.props.index].hardware ? <FormHelperText className={this.props.classes.errorColor}>Wähle mindestens eine Hardware aus.</FormHelperText> : null}
<GridList cellHeight={100} cols={cols} spacing={10}>
{hardware.map((picture,i) => (
<GridListTile key={i} onClick={() => this.onChange(picture.id)} classes={{tile: this.props.value.filter(value => value === picture.id).length > 0 ? this.props.classes.active : this.props.classes.border}}>
@ -67,16 +82,20 @@ class Requirements extends Component {
</GridListTile>
))}
</GridList>
</div>
);
};
}
Requirements.propTypes = {
changeContent: PropTypes.func.isRequired
changeContent: PropTypes.func.isRequired,
setError: PropTypes.func.isRequired,
deleteError: PropTypes.func.isRequired,
change: PropTypes.number.isRequired
};
const mapStateToProps = state => ({
change: state.builder.change
});
export default connect(mapStateToProps, { changeContent })(withStyles(styles, { withTheme: true })(withWidth()(Requirements)));
export default connect(mapStateToProps, { changeContent, setError, deleteError })(withStyles(styles, { withTheme: true })(withWidth()(Requirements)));

View File

@ -1,5 +1,9 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { tutorialId, setError, deleteError } from '../../../actions/tutorialBuilderActions';
import { withStyles } from '@material-ui/core/styles';
import Button from '@material-ui/core/Button';
import OutlinedInput from '@material-ui/core/OutlinedInput';
import InputLabel from '@material-ui/core/InputLabel';
@ -9,29 +13,40 @@ import FormHelperText from '@material-ui/core/FormHelperText';
import { faPlus, faMinus } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
class Id extends Component {
state = {
id: 0,
error: false
const styles = theme => ({
errorColor: {
color: theme.palette.error.dark
}
});
class Id extends Component {
handleChange = (e) => {
var value = parseInt(e.target.value);
if(Number.isInteger(value)){
this.setState({id: value, error: false});
if(Number.isInteger(value) && value > 0){
this.props.tutorialId(value);
if(this.props.error.id){
this.props.deleteError(undefined, 'id');
}
}
else {
this.setState({id: e.target.value, error: true});
this.props.tutorialId(value);
this.props.setError(undefined,'id');
}
};
handleCounter = (step) => {
if(!this.state.id){
this.setState({id: 0+step});
if(this.props.value+step < 1){
this.props.setError(undefined,'id');
}
else if(this.props.error.id){
this.props.deleteError(undefined, 'id');
}
if(!this.props.value){
this.props.tutorialId(0+step);
}
else {
this.setState({id: this.state.id+step});
this.props.tutorialId(this.props.value+step);
}
}
@ -41,8 +56,8 @@ class Id extends Component {
<InputLabel htmlFor="id">ID</InputLabel>
<OutlinedInput
style={{borderRadius: '25px', padding: '0 0 0 10px', width: '200px'}}
error={this.state.error}
value={this.state.id}
error={this.props.error.id}
value={this.props.value}
name='id'
label='ID'
id='id'
@ -53,6 +68,7 @@ class Id extends Component {
endAdornment={
<div style={{display: 'flex'}}>
<Button
disabled={this.props.value === 1 || !this.props.value}
onClick={() => this.handleCounter(-1)}
variant='contained'
color='primary'
@ -71,10 +87,20 @@ class Id extends Component {
</div>
}
/>
{this.state.error ? <FormHelperText style={{color: 'red'}}>Es muss eine positive ganzzahlige Zahl sein.</FormHelperText> : null}
{this.props.error.id ? <FormHelperText className={this.props.classes.errorColor}>Gib eine positive ganzzahlige Zahl ein.</FormHelperText> : null}
</FormControl>
);
};
}
export default Id;
Id.propTypes = {
tutorialId: PropTypes.func.isRequired,
setError: PropTypes.func.isRequired,
deleteError: PropTypes.func.isRequired
};
const mapStateToProps = state => ({
change: state.builder.change
});
export default connect(mapStateToProps, { tutorialId, setError, deleteError })(withStyles(styles, { withTheme: true })(Id));

View File

@ -9,6 +9,7 @@ import FormGroup from '@material-ui/core/FormGroup';
import Checkbox from '@material-ui/core/Checkbox';
import FormControlLabel from '@material-ui/core/FormControlLabel';
import FormLabel from '@material-ui/core/FormLabel';
import FormHelperText from '@material-ui/core/FormHelperText';
import FormControl from '@material-ui/core/FormControl';
class Requirements extends Component {
@ -29,7 +30,7 @@ class Requirements extends Component {
return (
<FormControl style={{marginBottom: '10px'}}>
<FormLabel style={{color: 'black'}}>Voraussetzungen</FormLabel>
<FormLabel style={{marginTop: '5px'}}>Beachte, dass die Reihenfolge des Anhakens maßgebend ist.</FormLabel>
<FormHelperText style={{marginTop: '5px'}}>Beachte, dass die Reihenfolge des Anhakens maßgebend ist.</FormHelperText>
<FormGroup>
{tutorials.map((tutorial, i) =>
<FormControlLabel

View File

@ -13,9 +13,6 @@ import { withStyles } from '@material-ui/core/styles';
import Typography from '@material-ui/core/Typography';
import IconButton from '@material-ui/core/IconButton';
import Tooltip from '@material-ui/core/Tooltip';
import OutlinedInput from '@material-ui/core/OutlinedInput';
import InputLabel from '@material-ui/core/InputLabel';
import FormControl from '@material-ui/core/FormControl';
import { faPlus, faAngleDoubleUp, faAngleDoubleDown, faTrash } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
@ -88,15 +85,15 @@ class Step extends Component {
</div>
<div style={{width: '100%', marginLeft: '54px'}}>
<StepType value={this.props.step.type} index={index} />
<Textfield value={this.props.step.headline} property={'headline'} label={'Überschrift'} index={index}/>
<Textfield value={this.props.step.text} property={'text'} label={this.props.step.type === 'task' ? 'Aufgabenstellung' : 'Instruktionen'} index={index} multiline/>
<Textfield value={this.props.step.headline} property={'headline'} label={'Überschrift'} index={index} error={this.props.error} errorText={`Gib eine Überschrift für die ${this.props.step.type === 'task' ? 'Aufgabe' : 'Anleitung'} ein.`} />
<Textfield value={this.props.step.text} property={'text'} label={this.props.step.type === 'task' ? 'Aufgabenstellung' : 'Instruktionen'} index={index} multiline error={this.props.error} errorText={`Gib Instruktionen für die ${this.props.step.type === 'task' ? 'Aufgabe' : 'Anleitung'} ein.`}/>
{index === 0 ?
<div>
<Requirements value={this.props.step.requirements} index={index}/>
<Hardware value={this.props.step.hardware} index={index} />
<Hardware value={this.props.step.hardware} index={index} error={this.props.error}/>
</div>
: null}
<BlocklyExample value={this.props.step.xml} index={index} task={this.props.step.type === 'task'}/>
<BlocklyExample value={this.props.step.xml} index={index} task={this.props.step.type === 'task'} />
</div>
</div>
</div>
@ -109,12 +106,14 @@ Step.propTypes = {
removeStep: PropTypes.func.isRequired,
changeStepIndex: PropTypes.func.isRequired,
steps: PropTypes.array.isRequired,
change: PropTypes.number.isRequired
change: PropTypes.number.isRequired,
error: PropTypes.object.isRequired,
};
const mapStateToProps = state => ({
steps: state.builder.steps,
change: state.builder.change
change: state.builder.change,
error: state.builder.error
});
export default connect(mapStateToProps, { addStep, removeStep, changeStepIndex })(withStyles(styles, {withTheme: true})(Step));

View File

@ -1,45 +1,62 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { tutorialTitle, changeContent } from '../../../actions/tutorialBuilderActions';
import { tutorialTitle, changeContent, setError, deleteError } from '../../../actions/tutorialBuilderActions';
import Button from '@material-ui/core/Button';
import { withStyles } from '@material-ui/core/styles';
import OutlinedInput from '@material-ui/core/OutlinedInput';
import InputLabel from '@material-ui/core/InputLabel';
import FormControl from '@material-ui/core/FormControl';
import FormHelperText from '@material-ui/core/FormHelperText';
const styles = theme => ({
multiline: {
padding: '18.5px 14px 18.5px 24px'
},
errorColor: {
color: theme.palette.error.dark
}
});
class Textfield extends Component {
// handleChange = (e) => {
// var value = e.target.value;
// if(value.replace(/\s/g,'') !== ''){
// this.setState({[e.target.name]: value, error: false});
// }
// else {
// this.setState({[e.target.name]: value, error: true});
// }
// };
handleChange = (e) => {
var value = e.target.value;
if(this.props.property === 'title'){
this.props.tutorialTitle(value);
}
else {
this.props.changeContent(this.props.index, this.props.property, value);
}
if(value.replace(/\s/g,'') === ''){
this.props.setError(this.props.index, this.props.property);
}
else{
this.props.deleteError(this.props.index, this.props.property);
}
};
render() {
return (
<FormControl variant="outlined" fullWidth style={{marginBottom: '10px'}}>
<InputLabel htmlFor={this.props.property}>{this.props.label}</InputLabel>
<OutlinedInput
style={this.props.multiline ? {borderRadius: '25px', padding: '18.5px 14px 18.5px 24px'} : {borderRadius: '25px', padding: '0 0 0 10px'}}
/* error={this.state.error}*/
style={{borderRadius: '25px'}}
classes={{multiline: this.props.classes.multiline}}
error={this.props.index !== undefined ? this.props.error.steps[this.props.index][this.props.property] : this.props.error[this.props.property]}
value={this.props.value}
label={this.props.label}
id={this.props.property}
multiline={this.props.multiline}
rows={2}
rowsMax={10}
onChange={(e) => {this.props.property === 'title' ?
this.props.tutorialTitle(e.target.value)
: this.props.changeContent(this.props.index, this.props.property, e.target.value)
}}
onChange={(e) => this.handleChange(e)}
/>
{/* {this.state.error ? <FormHelperText style={{color: 'red'}}>Gib einen Titel für das Tutorial ein.</FormHelperText> : null}*/}
{this.props.index !== undefined ?
this.props.error.steps[this.props.index][this.props.property] ? <FormHelperText className={this.props.classes.errorColor}>{this.props.errorText}</FormHelperText>
: null
: this.props.error[this.props.property] ? <FormHelperText className={this.props.classes.errorColor}>Gib einen Titel für das Tutorial ein.</FormHelperText>
: null}
</FormControl>
);
};
@ -47,7 +64,14 @@ class Textfield extends Component {
Textfield.propTypes = {
tutorialTitle: PropTypes.func.isRequired,
changeContent: PropTypes.func.isRequired
changeContent: PropTypes.func.isRequired,
error: PropTypes.object.isRequired,
change: PropTypes.number.isRequired
};
export default connect(null, { tutorialTitle, changeContent })(Textfield);
const mapStateToProps = state => ({
error: state.builder.error,
change: state.builder.change
});
export default connect(mapStateToProps, { tutorialTitle, changeContent, setError, deleteError })(withStyles(styles, { withTheme: true })(Textfield));

View File

@ -1,9 +1,9 @@
import { BUILDER_CHANGE, BUILDER_TITLE, BUILDER_ID, BUILDER_ADD_STEP, BUILDER_DELETE_STEP, BUILDER_CHANGE_STEP,BUILDER_CHANGE_ORDER, BUILDER_DELETE_PROPERTY } from '../actions/types';
import { BUILDER_CHANGE, BUILDER_ERROR, BUILDER_TITLE, BUILDER_ID, BUILDER_ADD_STEP, BUILDER_DELETE_STEP, BUILDER_CHANGE_STEP,BUILDER_CHANGE_ORDER, BUILDER_DELETE_PROPERTY } from '../actions/types';
const initialState = {
change: 0,
title: '',
id: 0,
id: '',
steps: [
{
id: 1,
@ -13,7 +13,10 @@ const initialState = {
hardware: [],
requirements: []
}
]
],
error: {
steps: [{}]
}
};
export default function(state = initialState, action){
@ -42,6 +45,11 @@ export default function(state = initialState, action){
...state,
steps: action.payload
};
case BUILDER_ERROR:
return {
...state,
error: action.payload
}
default:
return state;
}