Building a Mailbox Editor using ReactJS and Bootstrap

Published Dec 11, 2015Last updated Apr 12, 2017
Building a Mailbox Editor using ReactJS and Bootstrap

Introduction

Today we’re going to start hacking on Facebook’s React library. To do so, we’ll be building a simple but reusable Mailbox Editor that you can drop into an administration interface for instance.

The Mailbox Editor itself will be a React.js component that will allow us to write and format an e-mail. The sending part will not be covered in this tutorial.

Notes:

  • All the code for this post can be found on Github.
  • The main goal is to learn the fundamentals of React.js such as JSX, State, Props, Events handling and child components.
    Morevover, you'll see how easy it is to build a reusable component with React.js.
  • I also included jQuery here because I want to simplify the code of our Mailbox editor and focus on fundamentals concepts, but it's NOT mandatory for React to work.

However, if you want to go further with React, then subscribe to my newsletter by following this link : SUBSCRIBE!

Here's a preview :

enter image description here

Getting Started - Page Setup

First things first, we need to create the HTML file for the entry point of our Mailbox Editor. Create an index.html file with the following content, and let’s see what goes inside it :

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <title>React MailBox Editor</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.0/react.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.0/react-dom.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.25/browser.min.js"></script>
  </head>
  <body>
    <div class="container"></div>
  </body>
  
  <script type="text/babel"></script>
  
</html>

Here, we are simply including the React.js library (react.js and react-dom.js). All scripts are available via CDNJS.

Because we’ll make use of JSX to write our component, we need to include Babel (browser.min.js) to translate it to plain JavaScript. Moreover, our JSX code has to be written inside the following script tag :

<script type="text/babel"></script>

Now that we have this set up, let’s create our first React component. Add the following code inside the script tag :

var Mailbox = React.createClass({
  render: function () {
    return <h1>Hello World!</h1>;
  }
});

var options = {};

var element = React.createElement(Mailbox, options);
ReactDOM.render(element, document.querySelector('.container'));

For now our component is pretty basic. It only renders a “Hello World!" h1 tag. Open your browser and see the result.

Fine. Now, let’s start building our editor for writing emails.

The Mailbox Editor

We’ll implement the Mailbox Editor as a separate component, but inside the same file.

Before we define anything, let’s include an external plugin to ease our implementation. We’ll make use of Trumbowyg, which is a lightweight WYSIWYG editor based on jQuery.
Install Trumbowyg with Bower, and load jQuery and Trumbowyg at the top of our index.html file.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <title>React MailBox Editor</title>
    <script src="bower_components/jquery/dist/jquery.min.js"></script>
    <script src="bower_components/trumbowyg/dist/trumbowyg.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.0/react.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.0/react-dom.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.25/browser.min.js"></script>
    <link rel="stylesheet" href="bower_components/trumbowyg/dist/ui/trumbowyg.min.css">
  </head>
  <body>
    <div class="container"></div>
  </body>

  <script type="text/babel">
    ...
  </script>
  
</html>

Then add the following code to our JSX code.

var Editor = React.createClass({
  getDefaultProps: function() {
    return {
      placeholder: 'Enter your message here...',
      body: ""
    };
  },
  componentDidMount: function () {
    $('#editor').trumbowyg({
      fullscreenable: false
    });
    
    $('#editor').trumbowyg('html', this.props.body);
  },
  render: function () {
    return <div id="editor" placeholder={this.props.placeholder}></div>;
  }
});

This creates our React.js class which renders a simple div. In our componentDidMount method, we’re calling trumbowyg to transform the div into the WYSIWYG editor. Finally, we render our editor component inside of our application as follow :

var Mailbox = React.createClass({
   render: function () {
     return <Editor />;
   }
 });

Now, refresh your browser !

Design the Form

So we have a nice editor here, but it isn’t all that great if we can’t add recipients to our emails. Let’s design the form to do it. First, we will include Bootstrap to our app via Bower. Update the following lines to our index.html file.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <title>React MailBox Editor</title>
    <script src="bower_components/jquery/dist/jquery.min.js"></script>
    <script src="bower_components/trumbowyg/dist/trumbowyg.min.js"></script>
    <script src="bower_components/bootstrap/dist/js/bootstrap.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.0/react.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.0/react-dom.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.25/browser.min.js"></script>
    <link rel="stylesheet" href="bower_components/trumbowyg/dist/ui/trumbowyg.min.css">
    <link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.min.css">
  </head>
  <body>
    <div class="container"></div>
  </body>
  
  <script type="text/babel">
    ...
  </script>

</html>

The form will be added to the app inside our main component, Maibox, as follow :

var Mailbox = React.createClass({
  getInitialState: function ({
    return {
      "emailTo": "",
      "emailCC": "",
      "emailBCC": "",
      "emailSubject": "",
      "emailBody": ""
    }
  },
  render: function () {
    return (
      <div className="panel panel-default">
        <div className="panel-heading">
          <div className="row">
            <div className="col-xs-8 center">
              <div className="inbox-title">
                <span className="glyphicon glyphicon-envelope center"></span>
                <h2 className="center">{this.props.title}</h2>
              </div>
            </div>
            <div className="col-xs-4 center">
              <div className="inbox-avatar text-right">
                <img src={this.props.urlImage} />
                <div className="inbox-avatar-name"><a href={this.props.link}>{this.props.username}</a></div>
              </div>
            </div>
          </div>
          <hr />
          <div className="row">
            <div className="col-xs-12">
              <form className="form-horizontal">
                <div className="form-group">
                  <label htmlFor="email-to" className="col-sm-1 control-label">To</label>
                  <div className="col-sm-11">
                    <input type="email"
                            className="form-control"
                            id="email-to"
                            value={this.state.emailTo}
                            placeholder="Ex: hello@example.com"
                            onChange={this.handleEmailToChange}/>
                  </div>
                </div>
                <div className="form-group">
                  <label htmlFor="email-cc" className="col-sm-1 control-label">CC</label>
                  <div className="col-sm-11">
                    <input type="email"
                          className="form-control"
                          id="email-cc"
                          value={this.state.emailCC}
                          onChange={this.handleEmailCCChange}/>
                  </div>
                </div>
                <div className="form-group">
                  <label htmlFor="email-bcc" className="col-sm-1 control-label">BCC</label>
                  <div className="col-sm-11">
                    <input type="email"
                           className="form-control"
                           id="email-bcc"
                           value={this.state.emailBCC}
                           onChange={this.handleEmailBCCChange}/>
                  </div>
                </div>
                <div className="form-group">
                  <label htmlFor="email-subject" className="col-sm-1 control-label">Subject</label>
                  <div className="col-sm-11">
                    <input type="email"
                           className="form-control"
                           id="email-subject"
                           value={this.state.emailSubject}
                           onChange={this.handleEmailSubjectChange}/>
                  </div>
                </div>
              </form>
            </div>
          </div>
        </div>
        <div className="panel-body">
          <Editor onChange={this.handleEditorChange} />
        </div>
        <div className="panel-footer">
        </div>
      </div>
    );
  },
  handleEditorChange: function (emailBody) {
    this.setState({ "emailBody": emailBody });
  },
  handleEmailToChange: function (e) {
    this.setState({ "emailTo": e.target.value });
  },
  handleEmailCCChange: function (e) {
    this.setState({ "emailCC": e.target.value });
  },
  handleEmailBCCChange: function (e) {
    this.setState({ "emailBCC": e.target.value });
  },
  handleEmailSubjectChange: function (e) {
    this.setState({ "emailSubject": e.target.value });
  }
});

var options = {
  username: "Grégory D'Angelo",
  link: "https://gdangelo.fr",
  urlImage: "https://gdangelo.fr/images/avatar.jpg",
  title: "React Mailbox Editor"
};

var element = React.createElement(Mailbox, options);
ReactDOM.render(element, document.querySelector('.container'));

The form contains all the necessary text fields. Here, we’re using Bootstrap to design it.

In our getInitialState method, we’re setting all recipient fields to an empty string. Any changes on text fields will trigger an update of the state through onChange event and this.setState method. Futhermore, an onChange event has been added to our editor to store in Mailbox state the text written by the user.

var Editor = React.createClass({
  getDefaultProps: function() { ... },  
  componentDidMount: function () {
    var that = this;
    $('#editor').trumbowyg({
      fullscreenable: false
    })
    .on('tbwchange', function (){
      that.props.onChange($('#editor').trumbowyg('html'));
    });
    
    $('#editor').trumbowyg('html', this.props.body);
  },
  render: function () {
    return <div id="editor" placeholder={this.props.placeholder}></div>;
  }
});

var Mailbox = React.createClass({
      ...
      render: function () {
        return (
            ...
            <div className="panel-body">
              <Editor onChange={this.handleEditorChange} />
            </div>
            ...
        );
      },
      handleEditorChange: function (emailBody) {
        this.setState({ "emailBody": emailBody });
      },
      ...
});

To style our MailboxEditor, you can add the following styles and load it in our index.html file.

/* -----------------------------------*/
/* -------->>> INDEX.html <<<---------*/
/* -----------------------------------*/

/* General */
body{
  background-color: #e9f0f5;
}
img{
  border-radius: 20px;
  height: 40px;
  width: 40px;
}
label{
  color: #89949B;
}
.container{
  margin-top: 10px;
}
.glyphicon{
  font-size: 3.5em;
}
.center{
  display: inline-block;
  float: none;
  vertical-align: middle;
}

/* Editor */
.trumbowyg{
  border: none;
  margin: 0;
  width: 100%;
}
.trumbowyg-button-pane{
  background-color: white;
}

/* Form Structure */
.panel{
  border: none;
}
.panel .panel-heading{
  background-color: white;
  padding: 20px;
}
.panel .panel-body{
  padding: 0;
}
.panel .panel-footer{
  background-color: white;
  padding: 20px;
}

.inbox-title h2{
  display: inline-block;
  padding-left: 12.5px;
  text-align: left;
  vertical-align: middle;
}
.inbox-avatar-name{
  display: inline-block;
  font-size: 1.1em;
  padding-left: 12.5px;
  text-align: left;
  vertical-align: middle;
}
.inbox-avatar-name a{
  color: #ccc;
}

.form-horizontal{
  padding-left: 60px;
}
.form-control{
  border: 2px solid #ccc;
  border-radius: 0;
}

/* Buttons */
.btn{
  border: 1px solid #ccc;
  border-radius: 20px;
  padding: 4px 16px;
}
.btn-primary{
  background-color: #fff;
  color: #ccc;
}
.btn-primary.outline{
  border-color: #79B0EC;
  color: #79B0EC;
}
.btn-primary:hover,
.btn-primary:focus{
  background-color: #79B0EC;
  border-color: #79B0EC;
  color: #fff;
}

.btn-danger{
  background-color: #fff;
  color: #ccc;
}
.btn-danger.outline{
  border-color: #d9534f;
  color: #d9534f;
}
.btn-danger:hover,
.btn-danger:focus{
  background-color: #d9534f;
  border-color: #d9534f;
  color: #fff;
}

.btn-success{
  background-color: #fff;
  color: #ccc;
}
.btn-success.outline{
  border-color: #55C9A6;
  color: #55C9A6;
}
.btn-success:hover,
.btn-success:focus{
  background-color: #55C9A6;
  border-color: #55C9A6;
  color: #fff;
}

Now check it out in the browser.

Making it Interactive

In order to simulate some actions that can happen on maibox system, such as mail sending, we’ll include buttons. When triggered, the save and send buttons call the corresponding handleClick* method, which opens a modal to display a message. The cancel button only discards all the changes in the editor and text fields.

var Modal = React.createClass({
    render: function () {
      return (
        <div className="modal fade" tabIndex="-1" role="dialog">
          <div className="modal-dialog">
            <div className="modal-content">
              <div className="modal-header">
                <button type="button" className="close" data-dismiss="modal" ><span>&times;</span></button>
                <h4 className="modal-title">{this.props.title}</h4>
              </div>
              <div className="modal-body">
                {this.props.body}
              </div>
              <div className="modal-footer">
                <div className="btn-toolbar pull-right" role="toolbar">
                  <div className="btn-group" role="group">
                    <button type="button" className="btn btn-danger outline" data-dismiss="modal">Close</button>
                  </div>
                </div>
                <div className="clearfix" />
              </div>
            </div>
          </div>
        </div>
      );
    }
});

var Mailbox = React.createClass({
  ...
  render: function () {
    return (
        ...
        <div className="panel-footer">
          <div className="btn-toolbar pull-right" role="toolbar">
            <div className="btn-group" role="group">
              <button type="button" onClick={this.handleCancelClick} className="btn btn-danger">CANCEL</button>
              <button type="button" onClick={this.handleSaveClick} className="btn btn-success">SAVE</button>
            </div>
            <div className="btn-group" role="group">
              <button type="button" onClick={this.handleSendClick} className="btn btn-primary outline">SEND</button>
            </div>
            <Modal ref="modalSend" title="Email sent!" body="Your email has been successfully sent!" />
            <Modal ref="modalSave" title="Email saved!" body="Your email has been successfully saved!" />
          </div>
          <div className="clearfix" />
        </div>
      </div>
    );
  },
  ...
  handleCancelClick: function () {
    this.setState({
      "emailTo": "",
      "emailCC": "",
      "emailBCC": "",
      "emailSubject": "",
      "emailBody": ""
    });
  },
  handleSaveClick: function () {
    $(ReactDOM.findDOMNode(this.refs.modalSave)).modal();
  },
  handleSendClick: function () {
    $(ReactDOM.findDOMNode(this.refs.modalSend)).modal();
  }
});

Plus, the emailBody state is passed down from the Mailbox component to his child Editor component. Hence, through the componentWillReceiveProps method we are able to remove text from the Editor when the user click the cancel button from the Mailbox component.

var Editor = React.createClass({
  ...
  componentWillReceiveProps: function (nextProps) {
  	if (nextProps.body == ""){
          $('#editor').trumbowyg('empty');
        }
  },
  ...
});
    
var Mailbox = React.createClass({
  ...
  render: function () {
    return (
        ...
        <div className="panel-body">
        	<Editor onChange={this.handleEditorChange}
                    body={this.state.emailBody} />
        </div>
        ...
    );
  },
  ...
});

Wrap Up

If you have been following along with the source or building from scratch, open the index.html in your browser and play with our Mailbox Editor.

Don’t stop there though, fork the Github repo and try to add some new features to our Mailbox Editor.

If you enjoyed this tutorial and you want to know more about React, then subscribe to my newsletter by following this link : SUBSCRIBE!

Discover and read more posts from gdangelo
get started
Enjoy this post?

Leave a like and comment for gdangelo

2
1
1Reply
William
a year ago

It doesn’t work for me, even after I updated the JS sources

Get curated posts in your inbox

Read more posts to become a better developer