Chapter 03 (Invoice App Static Data) - Intuit-London/imperial-react-workshop GitHub Wiki

Initial SetUp.

  1. Create a new folder say chapter03
  2. npm init
  3. Add the below dependencies inside the package.json file
  "dependencies": {
    "react": "^15.3.2",
    "react-dom": "^15.3.2"
  },
  "devDependencies": {
    "babel-core": "^6.18.2",
    "babel-loader": "^6.2.7",
    "babel-preset-es2015": "^6.18.0",
    "babel-preset-react": "^6.16.0"
    "html-loader": "^0.4.4",
    "html-webpack-plugin": "^2.24.1",
    "webpack": "^1.13.3",
    "webpack-dev-server": "^1.16.2"
  }
  1. Create webpack.config.js under the folder chapter03 with the below contents
var webpack = require('webpack');
var path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

var BUILD_DIR = path.resolve(__dirname, 'build');
var APP_DIR = path.resolve(__dirname, 'src');

module.exports = {
     entry: APP_DIR + '/index.js',
     output: {
         path: BUILD_DIR,
         filename: 'app.bundle.js',
     },
     debug: true,
     devtool: 'source-map',
     module: {
         loaders: [{
             test: /\.js$/,
             exclude: /node_modules/,
             loader: 'babel-loader'
           },{
            test: /\.html$/,
            loader: 'html'
          }
         ]
     },
     plugins: [
        new HtmlWebpackPlugin({
          title: 'Chapter03',
          template: 'src/index.html'
        })
    ]
 };
  1. Create a file .babelrc under the folder chapter03 with the below contents
{
  "presets": [
    "react",
    "es2015"
  ]
}

Configure bootstrap css

  1. npm install --save bootstrap@3
  2. npm install --save-dev css-loader
  3. npm install --save-dev style-loader
  4. npm install --save-dev file-loader
  5. npm install --save-dev url-loader
  6. Add loaders in the webpack.config.js as follows
         ....... 
         {
            test:  /\.css$/,
            include: /node_modules/,
            loader: 'style-loader!css-loader'
          },{test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: 'file'
          },{test: /\.(woff|woff2)$/, loader: 'url?prefix=font/&limit=5000'
          },{test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: 'url?limit=10000&mimetype=application/octet-stream'
          },{test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: 'url?limit=10000&mimetype=image/svg+xml'
          }
          ......

Creating sub-components

  1. Add index.html file under src folder
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Chapter 04</title>
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>
  1. Create React Component AppHeader.js under src/components folder.
import React, { Component } from 'react';

class AppHeader extends Component {

  render() {

      return (
        <nav className="navbar navbar-inverse navbar-static-top">
          <div className="container-fluid">
            <a className="navbar-brand" href="#">Invoicing App</a>
          </div>
        </nav>
      );
  }
}

export default AppHeader;
  1. Create React Component InvoiceRow.js under src/components folder
import React, { Component } from 'react';

class InvoiceRow extends Component {

  render() {

      return (
        <tr>
          <td>{this.props.invoice.number}</td>
          <td>{this.props.invoice.customer.businessName}</td>
          <td>{this.props.invoice.creationDate}</td>
          <td>{this.props.invoice.totalAmount}</td>
          <td>{this.props.invoice.paid ? "Yes" : "No"}</td>
        </tr>
      );
  }
}

export default InvoiceRow;
  1. Create React Component InvoiceList.js under src/components folder
import React, { Component } from 'react';
import InvoiceRow from './InvoiceRow';

class InvoiceList extends Component {

  render() {

      var invoices = [
            {"number": "1", "customer": {"businessName": "Manish"}, "creationDate": "20/06/2016", "totalAmount": "£345", "paid": true},
    {"number": "2", "customer": {"businessName": "Manish"}, "creationDate": "10/12/2016", "totalAmount": "£35", "paid": true},
    {"number": "3", "customer": {"businessName": "Manish"}, "creationDate": "23/05/2016", "totalAmount": "£34", "paid": false},
    {"number": "4", "customer": {"businessName": "Manish"}, "creationDate": "10/06/2016", "totalAmount": "£90", "paid": false},
    {"number": "5", "customer": {"businessName": "Manish"}, "creationDate": "09/08/2016", "totalAmount": "£12", "paid": true},
    {"number": "6", "customer": {"businessName": "Manish"}, "creationDate": "01/06/2016", "totalAmount": "£34", "paid": true},
    {"number": "7", "customer": {"businessName": "Manish"}, "creationDate": "24/11/2015", "totalAmount": "£98", "paid": false}

      ], rows = [];

      for (var i = 0; i < invoices.length; i++) {
        rows.push(<InvoiceRow key={i} invoice={invoices[i]}/>);
      }

      return (
        <table className="table table-hover">
          <thead>
            <tr>
              <th>#</th>
              <th>Name</th>
              <th>Date</th>
              <th>Amount</th>
              <th>Paid</th>
            </tr>
          </thead>
          <tbody>{rows}</tbody>
        </table>
      );
  }
}

export default InvoiceList;
  1. Create React Component InvoiceSummary.js under src/components folder
import React, { Component } from 'react';

class InvoiceSummary extends Component {

  render() {

      return (
        <div>Invoice Summary</div>
      );
  }
}

export default InvoiceSummary;
  1. Create React Component InvoiceApp.js under src/app folder
import React, { Component } from 'react';
import AppHeader from "../components/AppHeader"
import InvoiceList from '../components/InvoiceList';
import InvoiceSummary from '../components/InvoiceSummary';
import 'bootstrap/dist/css/bootstrap.css';

class InvoiceApp extends Component {

  render() {

      return (
        <div>
            <AppHeader/>
            <InvoiceSummary/>
            <InvoiceList/>
        </div>
      );
  }
}

export default InvoiceApp;
  1. Create index.js under src folder to render the InvoiceApp on document
import React from 'react';
import ReactDOM from 'react-dom';
import InvoiceApp from './app/InvoiceApp';
var InvoiceAppFactory = React.createFactory(InvoiceApp);

ReactDOM.render(
  InvoiceAppFactory({}),document.getElementById('root')
);

Run the project

  1. Open package.json and replace the existing script section with the below content.
"scripts": {
    "dev": "node_modules/.bin/webpack-dev-server --content-base build/ --port 8000",
    "build": "node_modules/.bin/webpack",
    "test": "echo \"Error: no test specified\" && exit 1"
  }
  1. Run npm install
  2. Run npm run dev
  3. Open url http://localhost:8000/index.html to see it working.

Enhance InvoiceSummary

  1. Enhance Invoice Summary Component to show a Graph.
return (
        <div className="progress" id="summary" style={{height: 40 +'px'}}>
          <div className="progress-bar progress-bar-success summaryLabel" style={{width: 15 + '%', paddingTop: 10 + 'px'}}>
            15 Invoices Paid
          </div>
          <div className="progress-bar progress-bar-warning progress-bar summaryLabel" style={{width: 65 + '%', paddingTop: 10 + 'px'}}>
            65 Open Invoices
          </div>
          <div className="progress-bar progress-bar-danger summaryLabel" style={{width: 20 + '%', paddingTop: 10 + 'px'}}>
            20 OverDue invoices
          </div>
        </div>
      );

Using State

  1. Create InvoiceCreate component
import React, { Component } from 'react';

class InvoiceCreate extends Component {

  render() {

      return (
        <div>
          <form className="form-horizontal" ref={(ref) => this.createInvoiceForm = ref}>
            <div className="form-group">
                <label htmlFor="refNum" className="col-sm-offset-1 col-sm-2">Reference Number</label>
                <div className="col-sm-8">
                  <input type="number" className="form-control" id="refNum" placeholder="Number" ref={(ref) => this.invoiceNumber = ref}/>
                </div>
            </div>
            <div className="form-group">
                <label htmlFor="name" className="col-sm-offset-1 col-sm-2">Customer Name</label>
                <div className="col-sm-4">
                  <input type="text" className="form-control" id="name" placeholder="Customer Name" ref={(ref) => this.customername = ref}/>
                </div>
                <div className="col-sm-4">
                  <input type="email" className="form-control" id="email" placeholder="Customer Email"/>
                </div>
            </div>
            <div className="form-group">
                <label htmlFor="email" className="col-sm-offset-1 col-sm-2">Billing Address</label>
                <div className="col-sm-8 paddingBottom10">
                  <input type="text" className="form-control" id="email" placeholder="Line 1"/>
                </div>
                <div className="col-sm-offset-3 col-sm-8 paddingBottom10">
                  <input type="text" className="form-control" id="email" placeholder="Line 2"/>
                </div>
                <div className="col-sm-offset-3 col-sm-4 paddingBottom10">
                  <input type="text" className="form-control" id="email" placeholder="City"/>
                </div>
                <div className="col-sm-4 paddingBottom10">
                  <input type="text" className="form-control" id="email" placeholder="ZipCode"/>
                </div>
            </div>
            <div className="form-group">
                <label htmlFor="date" className="col-sm-offset-1 col-sm-2">Date</label>
                <div className="col-sm-8">
                  <input type="date" className="form-control" id="date" placeholder="Date" ref={(ref) => this.invoiceDate = ref}/>
                </div>
            </div>
            <div className="form-group">
                <label htmlFor="amount" className="col-sm-offset-1 col-sm-2">Total Amount</label>
                <div className="col-sm-8">
                  <input type="number" className="form-control" id="date" placeholder="Amount" ref={(ref) => this.invoiceAmount = ref}/>
                </div>
            </div>
            <div className="form-group">
                <label htmlFor="amountPaid" className="col-sm-offset-1 col-sm-2">Amount Paid</label>
                <div className="col-sm-8">
                  <input type="checkbox" className="form-control" placeholder="Amount Paid" ref={(ref) => this.invoicePaid = ref}/>
                </div>
            </div>
            <div className="form-group">
                <div className="col-sm-offset-3 col-sm-8">
                  <button type="button" className="btn btn-primary">Save</button>
                </div>
            </div>
          </form>
        </div>
      );
  }
}

export default InvoiceCreate;
  1. Enhance InvoiceApp to have a state by modifying constructor and render function.
import InvoiceCreate from '../components/InvoiceCreate';
 constructor(props) {
    super(props);
    this.state = {
        isCreateInvoice: false
    }
  };
setCreateInvoice() {
    this.setState({
      isCreateInvoice: true
    });
  };

  setListInvoice() {
    this.setState({
      isCreateInvoice: false
    })
  };
return (
        <div>
            <AppHeader createInvoice = {this.setCreateInvoice.bind(this)} listInvoice = {this.setListInvoice.bind(this)}/>
            <InvoiceSummary user={this.props.user}/>
            {this.state.isCreateInvoice ? <InvoiceCreate/> : <InvoiceList/> }
        </div>
      );
  1. Enhance AppHeader component to have button to change the state by modifying the render function
<nav className="navbar navbar-inverse navbar-static-top">
          <div className="container-fluid">
            <a className="navbar-brand" href="#">Invoicing App</a>
            <button type="button" className="btn btn-default navbar-btn btn-primary pull-right" onClick={this.props.createInvoice}>New Invoice</button>
            <button type="button" className="btn btn-default navbar-btn btn-link pull-right" onClick={this.props.listInvoice}>Invoice Lists</button>
          </div>
        </nav>

Using JsonLoader

  1. Create InvoiceList.json under /src/data as follows
    {
  "invoices" : [
    {"number": "1", "customer": {"businessName": "Manish"}, "creationDate": "20/06/2016", "totalAmount": "£345", "paid": true},
    {"number": "2", "customer": {"businessName": "Manish"}, "creationDate": "10/12/2016", "totalAmount": "£35", "paid": true},
    {"number": "3", "customer": {"businessName": "Manish"}, "creationDate": "23/05/2016", "totalAmount": "£34", "paid": false},
    {"number": "4", "customer": {"businessName": "Manish"}, "creationDate": "10/06/2016", "totalAmount": "£90", "paid": false},
    {"number": "5", "customer": {"businessName": "Manish"}, "creationDate": "09/08/2016", "totalAmount": "£12", "paid": true},
    {"number": "6", "customer": {"businessName": "Manish"}, "creationDate": "01/06/2016", "totalAmount": "£34", "paid": true},
    {"number": "7", "customer": {"businessName": "Manish"}, "creationDate": "24/11/2015", "totalAmount": "£98", "paid": false}
  ]
}
  1. Lets install json-loader using the command npm install --save-dev json-loader
  2. Lets configure webpack to bundle the json into app.bundle.js
          {
             test: /\.json$/,
             exclude: /node_modules/,
             loader: 'json-loader'
          }
  1. Lets enhance index.js to consume the data from data file and pass it down to components
import invoiceList from './data/InvoiceList';
var invoices = invoiceList;

ReactDOM.render(
  InvoiceAppFactory(invoices),document.getElementById('root')
);

"Enhance InvoiceApp.js to pass the data into sub-components"

<div>
            <AppHeader createInvoice = {this.setCreateInvoice.bind(this)} listInvoice = {this.setListInvoice.bind(this)}/>
            <InvoiceSummary user={this.props.user}/>
            {this.state.isCreateInvoice ? <InvoiceCreate/> : <InvoiceList invoices={this.props.invoices}/> }
        </div>
  1. Change InvoiceList.js to use the props.
 for (var i = 0; i < this.props.invoices.length; i++) {
        rows.push(<InvoiceRow key={i} invoice={this.props.invoices[i]}/>);
      }

Run the project

  1. npm install
  2. npm run dev
  3. Open url http://localhost:8000/index.html
⚠️ **GitHub.com Fallback** ⚠️