We will include and modularize our Bootstrap application that we have developed so far. In this application, we can see the static user profile raising helpdesk tickets online and rendering React components server-side. We have not used any database so we are storing our tickets in the browser's local storage. We can see the submission of the tickets in view tickets.
For your reference, I have included the Mongodb configuration and connection setup with db in the code snippet that you can get along with this book. Also, I have included the mongoose schema for the Add Ticket Form so you can play with them.
First, let's open the entry point of the script file index.js
in the src
folder and import
the React modules.
import React from 'react'; import ReactDOM from 'react-dom'; import { Router, Route, Link, IndexRoute,IndexLink, browserHistory } from 'react-router'
In version 15.4.0, React
and ReactDOM
are separated into different packages. React.render()
is deprecated in favor of ReactDOM.render()
in React 0.14, and the developers have also removed DOM-specific APIs from React completely in React 15.
In React 15.4.0, they have finally moved ReactDOM implementation to the ReactDOM package. The React package will now contain only renderer-agnostic code such as React.Component
and React.createElement()
.
Go to this blog to get the latest updates about React:
https://facebook.github.io/react/blog/
Now we need to import the Bootstrap, CSS, and JS files:
import '../css/custom.css'; import '../vendor/css/base.css'; import '../vendor/css/bootstrap.min.css'; import '../vendor/js/bootstrap.min.js';
Now let's start the server with the following command and see if our code and configuration can build or not:
nodemon start
It monitors the changes in your application files and restarts the server.
Or if we have not installed nodemon
then the command should be:
node server.js
The server is started in webpack to build your code bundle to the server client browser. If everything goes smoothly, you can get this info when the build is complete:
For now our page is blank. There is nothing to show because we have not included any component in our page yet.
Let's create one component for Bootstrap navigation with the name navbar.js
in the component folder.
module.exports.PageLayout = React.createClass({ })
module.exports
is a special object in Node.js and is included in every JS file. It exposes your functions, variables, and anything you have written inside module.exports
as a module that makes your code reusable and easy to share.
Let's add our Bootstrap navigation component inside this with the container
layout to render the page content:
render: function() { return ( <main> <div className="navbar navbar-default navbar-static-top" role="navigation"> <div className="container"> <div className="navbar-header"> <button type="button" className="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse"> <span className="sr-only">Toggle navigation</span> <span className="icon-bar"></span> <span className="icon-bar"></span> <span className="icon-bar"></span> </button> <Link className="navbar-brand" to="/">EIS</Link> </div> <div className="navbar-collapse collapse"> <ul className="nav navbar-nav"> <li><IndexLink activeClassName="active" to="/"> Home</IndexLink></li> <li><Link to="/edit" activeClassName="active"> Edit Profile</Link></li> <li className="dropdown"> <Link to="#" className="dropdown-toggle" data-toggle="dropdown">Help Desk <b className="caret"> </b></Link> <ul className="dropdown-menu"> <li><Link to="/alltickets">View Tickets</Link></li> <li><Link to="/newticket">New Ticket</Link></li> </ul> </li> </ul> </div> </div> </div>
Our page navigation container
ends here.
Here we are starting the main container
of the page where we can render the page content by using props
:
<div className="container">
<h1>Welcome to EIS</h1>
<hr/>
<div className="row">
<div className="col-md-12 col-lg-12">
{this.props.children}
</div>
</div>
</div>
</main>
);
}
Let's continue to add the home page content and prepare our first layout:
const RightSection = React.createClass({ render: function() { return (<div className="col-sm-9 profile-desc" id="main"> <div className="results"> <PageTitle/> <HomePageContent/> </div> </div>) } }) // include Left section content in ColumnLeft component with the wrapper of bootstrap responsive classes classes const ColumnLeft = React.createClass({ render: function() { return ( ) } }) const LeftSection = React.createClass({ render: function() { return ( //Left section content ) } }) const TwoColumnLayout = React.createClass({ render: function() { return ( <div> <ColumnLeft/> <RightSection/> </div> ) } })
Here we are including the page title and home page content in this component:
const PageTitle = React.createClass({ render: function() { return ( <h2>//page content</h2> ); } }); const HomePageContent = React.createClass({ render: function() { return ( <p>//page content</p> ); } });
Now we need to configure the routing to render the component in the UI:
ReactDOM.render(( <Router history={browserHistory}> <Route path="/" component={PageLayout}> <IndexRoute component={TwoColumnLayout}/> </Route> </Router> ), document.getElementById('root'));
We need to repeat the same flow with the other components and pages:
Our page looks great; we have successfully integrated our first page with Node.js.
Let's move to our main component and add a ticket in the help desk section.
Create a file with the name of addTicketForm.js
and include the following code:
import React from 'react'; import ReactDOM from 'react-dom';
Including the React
module is important in every file where we have React code:
var max_Char='140'; var style = {color: "#ffaaaa"}; module.exports.AddTicket = React.createClass({ getInitialState: function() { return {value: '', char_Left: max_Char}; }, handleChange: function(event) { var input = event.target.value; this.setState({value: input.substr(0, max_Char),char_Left: max_Char - input.length}); if (input.length == max_Char){ alert("You have reached the max limit") } },
In the preceding code, we are controlling the textarea
component with the same code we created in Chapter 5, jQuery Bootstrap Component with React.
handleSubmitEvent: function (event) { event.preventDefault(); var values = { date: new Date(), email: this.refs.email.value.trim(), issueType: this.refs.issueType.value, department: this.refs.department.value, comment: this.state.value }; this.props.addTicketList(values); localStorage.setItem('Ticket', JSON.stringify(values)); },
Before we were just displaying in the AddTicket
UI after submitting the form. Now we are using the local storage to save the tickets.
render: function() { return ( <form onSubmit={this.handleSubmitEvent}>
Here you need to put in the other form elements that we added before:
<div className="form-group"> <label htmlFor="comments">Comments <span style={style}>*</span> </label>(<span>{this.state.char_Left}</span> characters left) <textarea className="form-control" value={this.state.value} maxLength={max_Char} ref="comments" onChange={this.handleChange} /> </div> <div className="btn-group"> <button type="submit" className="btn btn-primary">Submit</button> <button type="reset" className="btn btn-link">cancel</button> </div> </form> ); } });
Next we need to create addTicketList.js
where we are wrapping this JSX form into the component:
<AddTicket addTicketList={this.addTicketList} />
Also we need to create listView.js
to display the list which after the user submits at the same time:
import { AddTicket } from "./addTicketForm.js"; import { List } from "./listView.js";
Here we have imported the AddTicket
module that we created before and created another module, addTicketForm
, to manage the form state for the update:
module.exports.AddTicketsForm = React.createClass({ getInitialState: function () { return { list: {} }; }, updateList: function (newList) { this.setState({ list: newList }); }, addTicketList: function (item) { var list = this.state.list; list[item] = item; this.updateList(list); }, render: function () { var items = this.state.list; return ( <div className="container"> <div className="row"> <div className="col-sm-6"> <List items={items} /> <AddTicket addTicketList={this.addTicketList} /> </div> </div> </div> );
In the render
method, we are passing the form and list
items into the component:
} }); listView.js import { ListPanel } from "./ListUI.js";
In the ListPanel
, we have actual JSX code that renders the tickets to the UI after the user submits and creates the module that we have included in addTicketList.js
:
module.exports.List = React.createClass({ getListOfIds: function (items) { return Object.keys(items); }, createListElements: function (items) { var item; return ( this .getListOfIds(items) .map(function createListItemElement(itemId,id) { item = items[itemId]; return (<ListPanel key={id} item={item} />); }.bind(this)) .reverse() ); }, render: function () { var items = this.props.items; var listItemElements = this.createListElements(items); return ( <div className={listItemElements.length > 0 ? "":""}> {listItemElements.length > 0 ? listItemElements : ""}
Here we are rendering the listItemElements
into the DOM:
</div> ); } });
Now let's create ListUI.js
, the last module, which will complete the functionality of the form component:
module.exports.ListPanel = React.createClass({ render: function () { var item = this.props.item; return ( <div className="panel panel-default"> <div className="panel-body"> Emailid: {item.email}<br/> IssueType: {item.issueType}<br/> IssueType: {item.department}<br/> Message: {item.comment} </div> <div className="panel-footer"> {item.date.toString()} </div> </div> ); } });
Let's see how the output in the browser looks.
Make sure you have included the following code in your router with the URL:
<Route path="/newticket" component={AddTicketsForm} />
Observe the following screenshot:
Looks good. Now let's fill in this form, submit it, and view the output:
That's awesome; our form works as expected.
You can also see the submit Ticket in the browser's local storage with the Key and Value format of the JSON notation:
Developer Tools > Application > Storage > Local Storage
Observe the following screenshot:
Now we need to get this JSON Ticket from the local storage and display it to the user in the View Tickets section.
Let's create another module to get the tickets and render it into the Bootstrap responsive table. The file
allTickets.js
will look as follows:
module.exports.allTickets = React.createClass({ getInitialState: function() { return { value :JSON.parse(localStorage.getItem( 'Ticket' )) || 1}; },
In the initial state of the component, we are using localStorage.getItem
to get the tickets
and parse them into the JSON to set the state:
getListOfIds: function (tickets) { return Object.keys(tickets); }, createListElements: function (tickets) { var ticket; return ( this .getListOfIds(tickets) .map(function createListItemElement(ticket,id) { ticket = tickets[ticket]; return (<ticketTable key={id} ticket={ticket}/>) }.bind(this)) ); },
Using the same approach we used in adding the ticket, we are mapping the ticket key
and the value into the React component by props
:
render: function() { var ticket = this.state.value;
In the render
method, we are assigning the state
value into the ticket
variable that we are passing into the createListElements
function:
var listItemElements = this.createListElements(ticket); return ( <div> <div className={listItemElements.length > 0 ? "":"bg-info"}> {listItemElements.length > 0 ? "" : "You have not raised any ticket yet."}
We are using the JavaScript ternary operator to check if we have any ticket
or, if not, to display the message in the UI.
</div> <table className="table table-striped table-responsive"> <thead> <tr> <th>Date</th> <th>Email ID</th> <th>Issue Type</th> <th>Department</th> <th>Message</th> </tr> </thead> <tbody> <tr> {listItemElements.length > 0 ? listItemElements : ""} </tr> </tbody> </table> </div> // In the preceding code, we are creating the table header and appending the ticket list items. ); } });
Now we need to create the component that includes the <td>
and inherits the ticket
data. ticketTable.js
will look as follows:
module.exports.ticketTable = React.createClass({ render: function () { var ticket = this.props.ticket; return ( <td>{ticket}</td> ); } });
And also we need to import this module in the allTickets.js
file:
const table = require("./ticketTable.js");
You may notice that I have used the const
object rather than using import
. You can also use var
instead. const
refers to constants; they are block-scoped, much like variables. The value of a constant cannot change and be reassigned, and it can't be redeclared.
For example:
const MY_CONST = 10; // This will throw an error because we have reassigned again. MY_CONST = 20; // will print 10 console.log("my favorite number is: " + MY_CONST); // const also works on objects const MY_OBJECT = {"key": "value"};
Here is our final router config:
ReactDOM.render(( <Router history={browserHistory}> <Route path="/" component={PageLayout}> <IndexRoute component={TwoColumnLayout}/> <Route path="/profile" component={Profile} /> <Route path="/alltickets" component={allTickets} /> <Route path="/newticket" component={AddTicketsForm} /> </Route> <Route path="*" component={NoMatch}/> </Router> ), document.getElementById('root'));
Let's look at the following key points:
.table-striped
in
<table class="table table-striped">
for zebra stripping in table rows.table-bordered
to add borders in whole and cells
.table-hover
to enable a hover state on table rows.table-condensed
to reduce the cell padding.active
, .success
, .info
, .warning
, .danger
) to add a background color to table rows or cellsApply these classes on the table and see how they make an impact on table's look and feel.
When creating responsive tables, we need to wrap any .table
in .table-responsive
to make them scroll horizontally on small devices (under 768 px). When we are viewing them on anything larger than 768 px wide, you will not see any difference in these tables.
Let's submit the ticket again and take a quick look at the table.
Go to the helpdesk drop-down in the navigation and click on view tickets.
You will get the appropriate message (You have not raised any ticket yet.) in the UI if you have not raised any ticket yet.
OK, so let's submit the fresh ticket and open this page again. Once the ticket is added, it will be displayed in your table:
We can see the ticket that we have submitted in the table now.