Source

Body/Project/Data/AttributeDomain.js

import React from "react";
import List from "@material-ui/core/List";
import ListItem from "@material-ui/core/ListItem";
import ListItemText from "@material-ui/core/ListItemText";
import DeleteIcon from "@material-ui/icons/Delete";
import ArrowDownwardIcon from "@material-ui/icons/ArrowDownward";
import ArrowUpwardIcon from "@material-ui/icons/ArrowUpward";
import { withStyles } from '@material-ui/styles';
import { StyledButton, StyledIconButton } from "../../../Utils/Buttons";
import { StyledDefaultTextField } from './StyledComponents';

const styles = ({
    root: {
        marginTop: 15,
        marginBottom: 0
    }
});

/**
 * @constructor
 * @category Utils
 * @subcategory Inputs
 * @param {Object} props
 * @param {Array} props.defaultValue - Array containing domain elements
 * @param {function} props.setDomainElements - Method fired when adding, removing, editing the domain element or changing the order of domain elements.
 * @returns {React.Component}
 */
class AttributeDomain extends React.Component {
    constructor(props) {
        super(props);

        this.max = 0;
        if(props.defaultValue.length > 0) {
            props.defaultValue.forEach(x => { if(x.id > this.max) this.max = x.id})
            this.max++;
        }

        this.state = {
            domainElements: props.defaultValue,
            id: this.max,
        };

        this.props.setDomainElements(props.defaultValue);
        this.timer = null;
    }

    /**
     * Method responsible for starting event (after inserting character in the text field) after 300ms.
     * This way it won't be lagging if many characters are quickly entered into the textfield.
     *
     * @function
     * @memberOf Attribute Domain
     * @param {Object} e - Represents an event that takes place in DOM tree.
     */
    startTime = (e) => {
        e.persist();

        clearTimeout(this.timer);
        this.timer = setTimeout(() => this.textFieldOnChange(e),300);
    }

    /**
     * Method runs on change in the domain element.
     *
     * @function
     * @memberOf Attribute Domain
     * @param {Object} e - Represents an event that takes place in DOM tree.
     */
    textFieldOnChange = (e) => {
        for (let i in this.state.domainElements) {
            if (this.state.domainElements[i].id == e.target.id) {
                const newText = e.target.value;
                this.setState(prevState => {
                        let newDomainElements = [...prevState.domainElements];
                        newDomainElements[i].text = newText;
                        return {
                            domainElements: newDomainElements
                        };
                    },
                    () => this.props.setDomainElements(this.state.domainElements)
                );
            }
        }
    };

    /**
     * Method runs if the domain element is removed.
     *
     * @function
     * @memberOf Attribute Domain
     * @param {Object} e - Represents an event that takes place in DOM tree.
     */
    onClickRemoveElement = (e) => {
        const id = e.currentTarget.value;
        this.setState(
            prevState => {
                const tmp = [...prevState.domainElements];
                for (let i in tmp) {
                    if (tmp[i].id == id) {
                        tmp.splice(i, 1);
                        break;
                    }
                }
                return { domainElements: tmp };
            },
            () => this.props.setDomainElements(this.state.domainElements)
        );
    };

    /**
     * Method runs if the domain element is added.
     *
     * @function
     * @memberOf Attribute Domain
     * @param {Object} e - Represents an event that takes place in DOM tree.
     */
    onClickAddElement = (e) => {
        const newElement = { id: this.state.id, text: "" };
        this.setState(
            prevState => {
                return {
                    domainElements: [...prevState.domainElements, newElement],
                    id: prevState.id + 1
                };
            },
            () => this.props.setDomainElements(this.state.domainElements)
        );
    };

    /**
     * Method runs if the order of domain elements changes (in this case arrow up has been clicked).
     * Example: Let's say we have 4 elements: a,b,c,d
     * If the up arrow is clicked on the "b" element order will look as follows: b,a,c,d.
     * So the element below goes upper, and upper element goes below. (like in the switch)
     * If the first element is clicked, i.e. "a" then the order will look as follows: b,c,d,a
     * so the first element goes at the bottom (like in the queue) and rest of elements goes up.
     *
     * @function
     * @memberOf Attribute Domain
     * @param {Object} e - Represents an event that takes place in DOM tree.
     */
    switchUpward = (e) => {
        if (this.state.domainElements.length > 1) {
            const id = e.currentTarget.value;
            const newDomainElements = this.state.domainElements;
            if (newDomainElements[0].id == id) {
                //if this is the first element, then go last
                newDomainElements.push(newDomainElements[0]);
                newDomainElements.splice(0, 1);
            } else {
                //if any other element then go up
                for (let i in newDomainElements) {
                    if (newDomainElements[i].id == id) {
                        const prevElement = newDomainElements[i - 1];
                        newDomainElements[i - 1] = newDomainElements[i];
                        newDomainElements[i] = prevElement;
                        break;
                    }
                }
            }

            this.setState(
                {
                    domainElements: newDomainElements
                },
                () => this.props.setDomainElements(this.state.domainElements)
            );
        }
    };

    /**
     * Method runs if the order of domain elements changes (in this case arrow down has been clicked).
     * Example: Let's say we have 4 elements: a,b,c,d
     * If the down arrow is clicked on the "b" element order will look as follows: a,c,b,d.
     * So the element below goes upper, and upper element goes below. (like in the switch)
     * If the last element is clicked, i.e. "d" then the order will look as follows: d,a,b,c
     * so the last element goes at the top (like in the queue) and rest of elements goes down.
     *
     * @function
     * @memberOf Attribute Domain
     * @param {Object} e - Represents an event that takes place in DOM tree.
     */
    switchDownward = (e) => {
        if (this.state.domainElements.length > 1) {
            const id = e.currentTarget.value;
            const newDomainElements = this.state.domainElements;
            if (newDomainElements[newDomainElements.length - 1].id == id) {
                //if this is the last element, then go first
                newDomainElements.unshift(
                    newDomainElements[newDomainElements.length - 1]
                );
                newDomainElements.pop();
            } else { //if any other element then go down
                for (let i = 0; i < newDomainElements.length; i++) {
                    if (newDomainElements[i].id == id) {
                        const prevElement = newDomainElements[i];
                        newDomainElements[i] = newDomainElements[i + 1];
                        newDomainElements[i + 1] = prevElement;
                        break;
                    }
                }
            }

            this.setState(
                {
                    domainElements: newDomainElements
                },
                () => this.props.setDomainElements(this.state.domainElements)
            );
        }
    };

    /**
     * Method prepares array of html elements which will be displayed through the render method.
     * It is responsible for displaying all the domain elements (whole rows)
     *
     * @function
     * @memberOf Attribute Domain
     */
    renderElements = classes => {
        const elements = [];
        this.state.domainElements.forEach((x, num) => {
            elements.push(
                <ListItem key={x.id.toString()}>
                    <ListItemText primary={num + 1} classes={{ root: classes.root }} />
                    <StyledDefaultTextField
                        variant = "standard"
                        id={x.id.toString()}
                        label="Domain element"
                        onChange={this.startTime}
                        defaultValue={x.text}
                        autoComplete="off"
                        required
                    />

                    <StyledIconButton
                        aria-label="up"
                        className={classes.root}
                        color={"primary"}
                        onClick={this.switchUpward}
                        size="small"
                        value={x.id}
                    >
                        <ArrowUpwardIcon fontSize="inherit" />
                    </StyledIconButton>
                    <StyledIconButton
                        aria-label="down"
                        className={classes.root}
                        color={"primary"}
                        disableRipple={false}
                        disableFocusRipple={false}
                        onClick={this.switchDownward}
                        size="small"
                        value={x.id}
                    >
                        <ArrowDownwardIcon fontSize="inherit" />
                    </StyledIconButton>

                    <StyledIconButton
                        aria-label="delete"
                        className={classes.root}
                        color={"primary"}
                        edge="start"
                        onClick={this.onClickRemoveElement}
                        size="small"
                        value={x.id}
                    >
                        <DeleteIcon />
                    </StyledIconButton>
                </ListItem>

            );
        });
        return <List dense={false} style={{overflow: "auto"}} > {elements} </List>;
    };

    render() {
        const { classes } = this.props;
        return (
            <React.Fragment>
                <StyledButton
                    color={"primary"}
                    disableElevation={true}
                    onClick={this.onClickAddElement}
                    style={{display: "flex", margin: "0 auto"}}
                    variant={"contained"}
                >
                    Add domain element
                </StyledButton>

                {this.renderElements(classes)}
            </React.Fragment>
        );
    }
}

AttributeDomain.defaultProps = {
    defaultValue: [],
}
export default withStyles(styles)(AttributeDomain);