A simple React TodoList in blog (testing JS code embedding)

Posted on July 24, 2018

This blog is to (1) implement a simple TodoList in React and (2) test the JS embedding framework. Previously, the CKeditor (and Jinja2) will block and consume the in-text Javascript in a terrible way, which makes it difficult to embed JS script in the text when editing.
In the updated design, an additional field is added especially for the script. I kept the original design where administrators can share some common scripts (like code-highlighting scripts) among different blogs by adding common scripts to the dataset and simply selecting them when editing.

Update2: 
Using ReactCSSTransitionGroup to implement a dynamic adding/deleting. https://reactjs.org/docs/animation.html

TodoList:

 

Source code:

   <style>
        .todoItem-enter {
            opacity: 0.01;
            max-height: 0px;
            overflow: hidden;
        }
        .todoItem-enter.todoItem-enter-active {
            opacity: 1;
            max-height: 200px; 
            transition: all 300ms ease-in;
        }
        .todoItem-leave {
            opacity: 1;
            max-height: 200px; 
        }
        .todoItem-leave.todoItem-leave-active {
            opacity: 0.01;
            max-height: 0; 
            transition: all 300ms ease-out;
        }
    </style>

    <script type="text/babel">
        class TodoItem extends React.Component {
            constructor(props) {
                super(props);
                this.state = {checked: false};
                this.toggle = this.toggle.bind(this);
                this.onDelete = this.onDelete.bind(this);
            }

            toggle(e) {
                if (e.target.classList.contains('btn')) return;
                this.setState((prevState) => ({ checked: !prevState.checked }) );
            }

            onDelete() {
                this.props.onDelete(this.props.index);
            }

            render() {
                return (
                <div className={"m-2 d-flex flex-row align-items-center border rounded "+
                            (this.state.checked?"border-success":"border-danger")}
                            onClick={this.toggle}>
                    <div className="pl-2 p-1" >
                        {this.state.checked?
                            (<i className="far fa-check-square fa-lg"></i>)
                            :
                            (<i className="far fa-square fa-lg"></i>)
                        }
                    </div>
                    <div className="p-1 w-100"> 
                        {this.state.checked?
                            (<del>{this.props.text}</del>)
                            :
                            this.props.text
                        }
                    </div>
                    <div className="ml-auto p-1">
                        <button className="btn btn-sm btn-outline-warning"
                            onClick={this.onDelete}
                        >delete</button>
                    </div>
                </div>
                );
            }
        }

        const ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;
        class TodoList extends React.Component {
            constructor(props) {
                super(props);
            }
            render() {
                return (
                    <div>

                <ReactCSSTransitionGroup
                    transitionName="todoItem"
                    transitionEnterTimeout={300}
                    transitionLeaveTimeout={300}>


                        { this.props.todos.map((todo, index) =>
                            <TodoItem key={todo.addTime} index={index} text={todo.text}
                                    onDelete={this.props.deleteTodoItem}/>
                        )}
                </ReactCSSTransitionGroup>
                    </div>
                );
            }
        }

        class TodoInput extends React.Component {
            constructor(props) {
                super(props);
                this.state = {text:''};
                this.onTextChange = this.onTextChange.bind(this);
                this.onAdd = this.onAdd.bind(this);
                this.onKeyPress = this.onKeyPress.bind(this);
            }

            onKeyPress(e) {
                if (e.key=='Enter') 
                    this.onAdd();
            }

            onTextChange(e) {
                this.setState({ text: e.target.value });
            }

            onAdd() {
                if (this.state.text.length == 0) return;
                this.props.addTodoItem(
                    (new Date()).getTime().toString(),
                    this.state.text
                );
                this.setState({text:''});
            }

            render() {
                return (
                    <div className="m-2 d-flex flex-row align-items-center border rounded border-success">
                        <div className="p-1 pl-2 w-100">
                            <input className="form-control" value={this.state.text}
                                onKeyPress={this.onKeyPress}
                                onChange={this.onTextChange} />
                        </div>
                        <div className="ml-auto p-1">
                            <button className="btn btn-outline-success btn-sm"
                                onClick={this.onAdd} >Add</button>
                        </div>

                    </div>
                );
            }
        }

        class App extends React.Component {
            constructor(props) {
                super(props);
                this.state = { todos: this.props.todos };
                this.deleteTodoItem = this.deleteTodoItem.bind(this);
                this.addTodoItem = this.addTodoItem.bind(this);
            }

            deleteTodoItem(index) {
                this.setState(
                    (prevState) => ({
                        todos:
                        [...prevState.todos.slice(0, index),
                         ...prevState.todos.slice(index+1)]
                    })
                );
            }

            addTodoItem(addTime, text) {
                this.setState(
                    (prevState) => ({
                        todos: [...prevState.todos, {addTime, text}]
                    }) );
            }

            render() {
                return (
                    <div>
                        <TodoList todos={this.state.todos}
                                deleteTodoItem={this.deleteTodoItem}/>
                        <TodoInput addTodoItem={this.addTodoItem} />
                    </div>
                )
            }
        }

        const items = [{addTime:1, text:"1st thing"}
                     , {addTime:2, text:"2nd thing"}
                     , {addTime:3, text:"3rd thing"}];
        ReactDOM.render(
            // <TodoItem text={"asdf"} />,
            // <TodoList todos={items} />,
            <App todos={items} />,
            document.getElementById("react-root")
        );

    </script>