React/Bootstrap/Express/Cassandra/Oh My!


I’ve always greatly enjoyed batting practices as they provide “the problem” to learn a new platform. I recently received a batting practice as a pre-requisite for a job interview. They also provide great basics for a tutorial, so here we go!

The ask:

Write a very simple contact list web app that lists Contacts, save/add Contacts, and search Contacts 
Use react.js for the frontend. 
Use bootstrap for layout. 
Express for the backend (rest API). 
Use datastax Cassandra sandbox VM as the database. (Persist to Cassandra)

The problem:

I’m not a fan of bootstrap, I’ve never used react, and had never heard more than two sentences on Cassandra.  I do have a fond spot in my heart for Node/Express,  so 1/4 isn’t bad.

TL;DR – The Source

The solution:

Download DataStax Sandbox VM

I went down the route of following directions (silly me), and setup the datastax Cassandra sandbox VM.  Come to find out, it’s locked down pretty tight, and CentOS is not in my wheelhouse.  So after spending an hour against a brick wall, I did what anybody would do.

sudo apt-get install datastax-ddc

Imagine that… a working application that I can actually get to.

The Node

Starting with a simple express scaffold and adding a few favorite packages for react/express (browserify-middleware, browserify-css, lodash, nodemon, q, reactivy, serve-favicon) and the cassandra driver (cassandra-driver), I was on my way to victory.

The Architecture

Server Side

I needed to implement RESTful API, and thankfully express.Router() makes that simple easy.  But to get data, I needed to be able to pull it from somewhere.  Having never messed with cassandra or react, I decided to learn react first, and scaffold up a simple api.  Knowing I was going to need to be able to switch to cassandra, I chose to use a repository/service to do data management.  The first repo I started with:

var _ = require('lodash');
var q = require('q');

var contacts = [
        {id: '1', name: 'James Nelson', email: 'james@jamesknelson.com'},
        {id: '2', name: 'Joe Citizen', email: 'joe@example.com'},
        {id: '3', name: 'Jaime Torres', email: 'jatorres@vt.edu', address1: '1 Best Way Ln', city: 'Midlothian', state: 'VA', zip: '23112', phone: '555-123-1234',
            description: 'Senior technology expert with fifteen years of experience building scalable SaaS platforms and web applications seeking opportunities as senior architect that allows me to use technology to support and grow the company.' }
];

var repo = {
    contacts: contacts,
    getAll: function() {
        var self = this;
        return q.fcall(function() { return self.contacts; });
    },
    get: function(id) {
        var self = this;
        var contact = _.find(self.contacts, function (o) { return o.id === id; });
        if (contact) {
            return q.fcall(function() { return contact; });
        }

        return q.fcall(function() { return null; });
    },
    upsert: function(contact) {
        var self = this;

        var idx = _.findIndex(this.contacts, {id: contact.id});
        if (idx === -1) {
            this.contacts.push(contact);
        } else {
            this.contacts[idx] = contact;
        }

        return q.fcall(function() { return contact; });
    },
    remove: function(id) {
        this.contacts = this.contacts.filter(function(o) {
            return o.id !== id;
        });
        return q.fcall(function() { return true; });
    }
};

module.exports = repo;

This provided basic functionality to get my REST going:

var express = require('express');
var contactsRepo = require('../repo/contacts');
var router = express.Router();

/* CONTACTS */

/* GET contacts listing. */
router.route('/contacts')
    .get(function(req, res, next) {

        contactsRepo.getAll().then(function(result) {
           res.json(result);
        });
    })
    .post(function(req, res, next) {
        var contact = req.body;
        console.log("Insert: ", contact);

        contactsRepo.upsert(contact).then(function(result){
            if (result) res.status(200).json(result);
            else res.status(404).end();
        });
    });

router.route('/contacts/:id')
    .get(function(req, res, next) {
        contactsRepo.get(req.params.id).then(function(contact) {
            if (contact) {
                res.status(200).json(contact);
            } else {
                res.status(404).end();
            }
        });

    })
    .delete(function(req, res, next) {
        contactsRepo.remove(req.params.id).then(function() {
            res.status(200).end();
        });
    })
    .put(function(req, res, next) {
        var contact = req.body;
        console.log("Upsert: ", contact);
        contact.id = req.params.id;
        contactsRepo.upsert(contact).then(function(result) {
            if (result) {
                res.status(200).json(result);
            } else {
                return res.status(404).end();
            }
        });

    });

module.exports = router;

There’s nothing really special going on here, so I’ll add clarity if there are any questions. So here we have a router, an API, and a repo.  We need a few things to pull it together:

  1. An application
  2. Some display
  3. And a little bit of panache
The Application

As with any express application, we have to pull in “all the things”:

var express = require("express");
var path = require("path");
var favicon = require("serve-favicon");
var logger = require("morgan");
var cookieParser = require("cookie-parser");
var bodyParser = require("body-parser");

var api = require("./routes/api");
var bundler = require("./routes/bundles.js");

var app = express();

Setup the engine

app.engine('html', require('ejs').renderFile);
app.set('view engine', 'html');

Configure the application

app.use(favicon(path.join(__dirname, "public", "images", "favicon.ico")));
app.use(logger("dev"));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, "public")));

app.use("/api", api);
app.use("/bundles", bundler);

Set the entry point

app.get("/", function(req, res, next) {
    res.render("index", {});
});

And export

<span class="nx">module</span><span class="p">.</span><span class="nx">exports</span> <span class="o">=</span> <span class="nx">app</span><span class="p">;</span>

Now, you might be saying to yourself “LeftyFTW, I’m on to you.  What’s this bundles thing?”  And I’ll say it’s the only thing that makes something like react/angular possible.  And it’s too easy NOT to do:

var express = require("express");
var browserify = require("browserify-middleware");
var router = express.Router();

browserify.settings({
   transform: ['reactify'],
   extensions: ['.js', '.jsx'],
   grep: /\.jsx?$/
});

router.get("/app.js", browserify("./public/javascripts/contacts.js"));

module.exports = router;

This little bit of code will take all of our front end js/jsx, traverse the require tree, compile all jsx, and build a single app.js for the client to download.  I call this – organization with no cost!

The Display (Front End)

We spent a bit of time building a decent backend with some hints on how we’re going to do the front end.  First off, we know we need an index.html.  Since this will be a react application, that should be pretty tiny.  All we need is a node to attach our react app too, and a few CSS/JS references.

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>ArgoContacts</title>
    	<link rel="shortcut icon" href="/images/favicon.ico" type="image/x-icon">
    	<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/latest/css/bootstrap.min.css">
    	<link rel="stylesheet" href="/stylesheets/style.css">
  </head>
  <body>
<div class="container" id="react-app"></div>
<script src="https://cdn.jsdelivr.net/react/0.14.0-rc1/react.js"></script>
    <script src="https://cdn.jsdelivr.net/react/0.14.0-rc1/react-dom.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-bootstrap/0.28.4/react-bootstrap.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.8.2/lodash.min.js"></script>
    <script src="/bundles/app.js"></script>
  </body>
</html>

Here we pull in react, react-dom, react-bootstrap, and lodash  from CDNs, and the bundled app.js we created earlier.  We also created a node with id #react-app to attach to later.

React

A little bit of history: I came up from the land of jQuery, meandered through KnockoutJS, and found a warm home with AngularJS.  All of this, sadly, used Bootstrap.   There is nothing WRONG with bootstrap, but I was pretty butt hurt when it went from 2.x to 3.0.  And even though ui-bootstrap has done a good job of trying to make bootstrap more declarative, it’s still not.  Then Angular Material came around, and suddenly I had “components.”  I could make wonderfully declarative HTML structures with minimal scaffolding, and it was maintainable, sensible, readable, AND pretty!   And what I liked about React was that it took the “ugly” bootstrap and hit it away into a component nicely.  And it forces a “top-down” control structure, which I very much appreciate.  Our first component is our main display.

ContactView

At this point, I wish I wrote this blog as I developed the application, but I didn’t.  So let’s deconstruct this.  First, we reference the sub-components… we shouldn’t know what these are, but we do now, so that’s where we find ourselves.

var ContactCard = require("./contact-card.jsx");
var ContactInput = require("./contact-input.jsx");

Then, we create our reactClass:

module.exports = React.createClass({});

And now we fill it out.  We need to know EVERYTHING that this guy has to support.  So this was my thought:

  • A list of contacts (that is the point)
  • Some search text (a requirement)
  • A way to display a modal
  • The contact contained in said modal
  • onChange, onSave, onShow, onRemove, onEdit, and onSearch events

So now we know our propTypes:

propTypes: {
    contacts: React.PropTypes.array.isRequired,
    searchText: React.PropTypes.string,
    modalContact: React.PropTypes.object.isRequired,
    showModal: React.PropTypes.bool.isRequired,
    onContactChange: React.PropTypes.func.isRequired,
    onSaveContact: React.PropTypes.func.isRequired,
    onShowContactModal: React.PropTypes.func.isRequired,
    onRemoveContact: React.PropTypes.func.isRequired,
    onEditContact: React.PropTypes.func.isRequired,
    onUpdateSearchText: React.PropTypes.func.isRequired
  }

Now we have to define the component display, with some functions we’ll fill out later

 render: function() {
    var Button = ReactBootstrap.Button;
    return (
<div className='contact-view'>
<div className='container-fluid'>
<h1 className='ContactView-title col-xs-12 col-sm-6 col-lg-8'>Contacts</h1>
<div className='col-xs-12 col-sm-6 col-lg-4'>
<div className='header-search'>
<div id="txt-search-contact">
                            <input className="form-control" type="text" placeholder="Search..." value={this.props.searchText} onChange={this.searchChanged}></input></div>
<Button bsStyle="primary" id="btn-add-contact" title='Add Contact' onClick={showModal}> <i className='glyphicon glyphicon-plus' /> <i className='glyphicon glyphicon-user' /></Button></div>
</div>
</div>
{
                filterContacts()
                .map(function(c) {
                    return <ContactCard key={c.id} contact={c} onRemoveContact={self.props.onRemoveContact} onEditContact={self.props.onEditContact} />;
            })}</div>
);
  }

And, because bootstrap, we have to define the modal in a very special way (although I like how react-bootstrap simplifies things)

            <Modal bsSize='lg' show={this.props.showModal} onHide={hideModal}>
                <Modal.Header closeButton>
                    <Modal.Title>Add Contact</Modal.Title>
                </Modal.Header>
                <Modal.Body>
                    <ContactInput contact={this.props.modalContact} onChange={this.props.onContactChange} />
                </Modal.Body>
                <Modal.Footer>
                    <Button bsStyle='primary' onClick={this.props.onSaveContact}>Save</Button>
                    <Button onClick={hideModal}>Close</Button>
                </Modal.Footer>
            </Modal>

And now for the functionality

    var self = this;

    var showModal = function() {
        self.props.onShowContactModal(true);
    };

    var hideModal = function() {
        self.props.onShowContactModal(false);
    };

    var filterContacts = function() {
        return self.props.contacts
                .filter(function(c) {
                    if (!c.email) return false;

                    var search = self.props.searchText;

                    if (!search) return true;
                    return c.email.indexOf(search) !== -1
                            || (c.name &amp;amp;&amp;amp; c.name.indexOf(search) !== -1)
                            || (c.phone &amp;amp;&amp;amp; c.phone.indexOf(search) !== -1);
                })
    }

And to pull it all together (including a searchChanged function on the class)

var ContactCard = require("./contact-card.jsx");
var ContactInput = require("./contact-input.jsx");

module.exports = React.createClass({
  propTypes: {
    contacts: React.PropTypes.array.isRequired,
    searchText: React.PropTypes.string,
    modalContact: React.PropTypes.object.isRequired,
    showModal: React.PropTypes.bool.isRequired,
    onContactChange: React.PropTypes.func.isRequired,
    onSaveContact: React.PropTypes.func.isRequired,
    onShowContactModal: React.PropTypes.func.isRequired,
    onRemoveContact: React.PropTypes.func.isRequired,
    onEditContact: React.PropTypes.func.isRequired,
    onUpdateSearchText: React.PropTypes.func.isRequired
  },

  searchChanged: function(input) {
        this.props.onUpdateSearchText(input.target.value);
  },

  render: function() {
    var self = this;

    var self = this;
    var showModal = function() {
        self.props.onShowContactModal(true);
    };

    var hideModal = function() {
        self.props.onShowContactModal(false);
    };

    var filterContacts = function() {
        return self.props.contacts
                .filter(function(c) {
                    if (!c.email) return false;

                    var search = self.props.searchText;

                    if (!search) return true;
                    return c.email.indexOf(search) !== -1
                            || (c.name &amp;amp;&amp;amp; c.name.indexOf(search) !== -1)
                            || (c.phone &amp;amp;&amp;amp; c.phone.indexOf(search) !== -1);
                })
    }

    var Modal = ReactBootstrap.Modal;
    var Button = ReactBootstrap.Button;

    return (
<div className='contact-view'>
<div className='container-fluid'>
<h1 className='ContactView-title col-xs-12 col-sm-6 col-lg-8'>Contacts</h1>
<div className='col-xs-12 col-sm-6 col-lg-4'>
<div className='header-search'>
<div id="txt-search-contact">
                            <input className="form-control" type="text" placeholder="Search..." value={this.props.searchText} onChange={this.searchChanged}></input></div>
<Button bsStyle="primary" id="btn-add-contact" title='Add Contact' onClick={showModal}> <i className='glyphicon glyphicon-plus' /> <i className='glyphicon glyphicon-user' /></Button></div>
</div>
</div>
{
                filterContacts()
                .map(function(c) {
                    return <ContactCard key={c.id} contact={c} onRemoveContact={self.props.onRemoveContact} onEditContact={self.props.onEditContact} />;
            })}
            <Modal bsSize='lg' show={this.props.showModal} onHide={hideModal}>
                <Modal.Header closeButton>
                    <Modal.Title>Add Contact</Modal.Title>
                </Modal.Header>
                <Modal.Body>
                    <ContactInput contact={this.props.modalContact} onChange={this.props.onContactChange} />
                </Modal.Body>
                <Modal.Footer>
                    <Button bsStyle='primary' onClick={this.props.onSaveContact}>Save</Button>
                    <Button onClick={hideModal}>Close</Button>
                </Modal.Footer>
            </Modal></div>
);
  },
});

And we now have a display with a Modal popup that displays cards, and has an API for CRUDing contacts.  Let’s take a look at a ContactCard.

ContactCard

This is why I love react.  It’s a very formulary approach:

  • require
  • inputs
  • processing
  • display

Does this remind anyone of their first programming book? On we go:

Require (we’re doing this require for gravatar support… aka the panache!)

var md5 = require("../md5.js");

Inputs (propTypes)

    propTypes: {
        contact: React.PropTypes.object.isRequired,
        onRemoveContact: React.PropTypes.func.isRequired,
        onEditContact: React.PropTypes.func.isRequired
    }

Processing (functions)

        var Jumbotron = ReactBootstrap.Jumbotron;
        var Button = ReactBootstrap.Button;

        var address;
        if (this.props.contact.address1 || this.props.contact.city || this.props.contact.state || this.props.contact.zip ) {
            if (this.props.contact.address2) {
                address =
<address>{this.props.contact.address1} 
{this.props.contact.address2} 
 {this.props.contact.city}, {this.props.contact.state} {this.props.contact.zip}</address>

            }
            else {
                address =
<address>{this.props.contact.address1} 
 {this.props.contact.city}, {this.props.contact.state} {this.props.contact.zip}</address>

            }
        }

        var phone;
        if (this.props.contact.phone) {
            phone =
<div><abbr title="Phone">P:</abbr> <a href={'tel:' + this.props.contact.phone}>{this.props.contact.phone}</a></div>
;
        }

        var notes;
        if (this.props.contact.notes) {
            notes =
<div>
<h4>Notes:</h4>
<div><em>{this.props.contact.notes}</em></div>
</div>
;
        }

        var self = this;
        var deleteContact = function() {
            self.props.onRemoveContact(self.props.contact.id);
        };

        var editContact = function() {
            self.props.onEditContact(self.props.contact);
        };

Display (jsx)

<div className='contact-card'>
                <Jumbotron className='container-fluid'>
<div className='row'>
<div className='container-fluid'>
<div className='col-xs-5 col-md-4'>
                                <img className='contact-avatar img-responsive' src={'http://www.gravatar.com/avatar/' + md5(this.props.contact.email.trim().toLowerCase()) + '?s=200'}></img></div>
<div className='col-xs-7 col-md-4 col-lg-3'>
<h2> {this.props.contact.name}</h2>
{address}
                                {phone}
                                <a href={'mailto:' + this.props.contact.email}>{this.props.contact.email}</a></div>
<div className="col-xs-12 col-md-4 col-lg-5">
                                {notes}</div>
</div>
</div>
<div className='row'>
<div className='button-group pull-right'>
                            <Button title='Edit Contact' bsStyle='success' onClick={editContact}><i className='glyphicon glyphicon-edit'></i></Button>
                            <Button title='Delete Contact' bsStyle='danger' onClick={deleteContact}><i className='glyphicon glyphicon-trash'></i></Button></div>
</div>
</Jumbotron></div>

And all together:

var md5 = require("../md5.js");

module.exports = React.createClass({
    propTypes: {
        contact: React.PropTypes.object.isRequired,
        onRemoveContact: React.PropTypes.func.isRequired,
        onEditContact: React.PropTypes.func.isRequired
    },

    render: function() {

        var Jumbotron = ReactBootstrap.Jumbotron;
        var Button = ReactBootstrap.Button;

        var address;
        if (this.props.contact.address1 || this.props.contact.city || this.props.contact.state || this.props.contact.zip ) {
            if (this.props.contact.address2) {
                address =
<address>{this.props.contact.address1} 
{this.props.contact.address2} 
 {this.props.contact.city}, {this.props.contact.state} {this.props.contact.zip}</address>

            }
            else {
                address =
<address>{this.props.contact.address1} 
 {this.props.contact.city}, {this.props.contact.state} {this.props.contact.zip}</address>

            }
        }

        var phone;
        if (this.props.contact.phone) {
            phone =
<div><abbr title="Phone">P:</abbr> <a href={'tel:' + this.props.contact.phone}>{this.props.contact.phone}</a></div>
;
        }

        var notes;
        if (this.props.contact.notes) {
            notes =
<div>
<h4>Notes:</h4>
<div><em>{this.props.contact.notes}</em></div>
</div>
;
        }

        var self = this;
        var deleteContact = function() {
            self.props.onRemoveContact(self.props.contact.id);
        };

        var editContact = function() {
            self.props.onEditContact(self.props.contact);
        };

        return (
<div className='contact-card'>
                <Jumbotron className='container-fluid'>
<div className='row'>
<div className='container-fluid'>
<div className='col-xs-5 col-md-4'>
                                <img className='contact-avatar img-responsive' src={'http://www.gravatar.com/avatar/' + md5(this.props.contact.email.trim().toLowerCase()) + '?s=200'}></img></div>
<div className='col-xs-7 col-md-4 col-lg-3'>
<h2> {this.props.contact.name}</h2>
{address}
                                {phone}
                                <a href={'mailto:' + this.props.contact.email}>{this.props.contact.email}</a></div>
<div className="col-xs-12 col-md-4 col-lg-5">
                                {notes}</div>
</div>
</div>
<div className='row'>
<div className='button-group pull-right'>
                            <Button title='Edit Contact' bsStyle='success' onClick={editContact}><i className='glyphicon glyphicon-edit'></i></Button>
                            <Button title='Delete Contact' bsStyle='danger' onClick={deleteContact}><i className='glyphicon glyphicon-trash'></i></Button></div>
</div>
</Jumbotron></div>
);
    }
});
ContactInput

Finally, we need to pull data in from an end user, so we need that thing we put in the modal awhile back.  Same setup as before:

Require

var md5 = require("../md5.js");

Inputs

    propTypes: {
        contact: React.PropTypes.object.isRequired,
        onChange: React.PropTypes.func.isRequired
    }

Processing (Class-level)

    handleChange: function(field, e) {
        var update = {};
        update[field] = e.target.value;
        this.props.onChange(Object.assign({}, this.props.contact, update));
    }

Processing (Render-level)

        var photoSource;
        if (this.props.contact.email) {
            photoSource = "http://www.gravatar.com/avatar/" + md5(this.props.contact.email.trim().toLowerCase()) + "?s=200";
        }

Display

<div className='contact-input'>
                <Jumbotron>
<div className="row">
<form>
<div className="col-xs-4">
                            <img src={photoSource}></img></div>
<div className="col-xs-4">
<div className="form-group">
                                <label className="sr-only" htmlFor="new-contact-name">Name</label>
                                <input className="col-xs-12 form-control" type="text" placeholder="Name" id="new-contact-name" value={this.props.contact.name} onChange={this.handleChange.bind(this, "name")}></input></div>
<div className="form-group">
                                <label className="sr-only" htmlFor="new-contact-address1">Address 1</label>
                                <input className="col-xs-12 form-control" type="text" placeholder="Address 1" id="new-contact-address1" value={this.props.contact.address1} onChange={this.handleChange.bind(this, "address1")}></input></div>
<div className="form-group">
                                <label className="sr-only" htmlFor="new-contact-address2">Address 2</label>
                                <input className="col-xs-12 form-control" type="text" placeholder="Address 2" id="new-contact-address2" value={this.props.contact.address2} onChange={this.handleChange.bind(this, "address2")}></input></div>
<div className="form-group">
<div className="nopad col-xs-6">
                                    <label className="sr-only" htmlFor="new-contact-city">City</label>
                                    <input className="form-control" type="text" placeholder="City" id="new-contact-city" value={this.props.contact.city} onChange={this.handleChange.bind(this, "city")}></input></div>
<div className="nopad col-xs-3">
                                    <label className="sr-only" htmlFor="new-contact-state">State</label>
                                    <input className="form-control" type="text" placeholder="State" id="new-contact-state" value={this.props.contact.state} onChange={this.handleChange.bind(this, "state")}></input></div>
<div className="nopad col-xs-3">
                                    <label className="sr-only" htmlFor="new-contact-zip">Zip Code</label>
                                    <input className="form-control" type="text" placeholder="Zip" id="new-contact-zip" value={this.props.contact.zip} onChange={this.handleChange.bind(this, "zip")}></input></div>
</div>
<div className="form-group">
                                <label className="sr-only" htmlFor="new-contact-phone">Phone</label>
                                <input className="col-xs-12 form-control" type="text" placeholder="Phone" id="new-contact-phone" value={this.props.contact.phone} onChange={this.handleChange.bind(this, "phone")}></input></div>
<div className="form-group">
                                <label className="sr-only" htmlFor="new-contact-email">E-mail</label>
                                <input type="text" className="col-xs-12 form-control" placeholder="E-mail" id="new-contact-email" value={this.props.contact.email} onChange={this.handleChange.bind(this, "email")}></input></div>
</div>
<div className="col-xs-4">
<div className="form-group">
<div><label className="sr-only" htmlFor="new-contact-description">Notes</label></div>
<textarea className="col-xs-12 form-control" placeholder="Notes" id="new-contact-description" rows="5" value={this.props.contact.notes} onChange={this.handleChange.bind(this, "notes")}></textarea></div>
</div>
</form></div>
</Jumbotron></div>

And all together:

var md5 = require("../md5.js");

module.exports = React.createClass({
    propTypes: {
        contact: React.PropTypes.object.isRequired,
        onChange: React.PropTypes.func.isRequired
    },

    handleChange: function(field, e) {
        var update = {};
        update[field] = e.target.value;
        this.props.onChange(Object.assign({}, this.props.contact, update));
    },

    render: function() {

        var Jumbotron = ReactBootstrap.Jumbotron;

        var photoSource;
        if (this.props.contact.email) {
            photoSource = "http://www.gravatar.com/avatar/" + md5(this.props.contact.email.trim().toLowerCase()) + "?s=200";
        }

        return (
<div className='contact-input'>
                <Jumbotron>
<div className="row">
<form>
<div className="col-xs-4">
                            <img src={photoSource}></img></div>
<div className="col-xs-4">
<div className="form-group">
                                <label className="sr-only" htmlFor="new-contact-name">Name</label>
                                <input className="col-xs-12 form-control" type="text" placeholder="Name" id="new-contact-name" value={this.props.contact.name} onChange={this.handleChange.bind(this, "name")}></input></div>
<div className="form-group">
                                <label className="sr-only" htmlFor="new-contact-address1">Address 1</label>
                                <input className="col-xs-12 form-control" type="text" placeholder="Address 1" id="new-contact-address1" value={this.props.contact.address1} onChange={this.handleChange.bind(this, "address1")}></input></div>
<div className="form-group">
                                <label className="sr-only" htmlFor="new-contact-address2">Address 2</label>
                                <input className="col-xs-12 form-control" type="text" placeholder="Address 2" id="new-contact-address2" value={this.props.contact.address2} onChange={this.handleChange.bind(this, "address2")}></input></div>
<div className="form-group">
<div className="nopad col-xs-6">
                                    <label className="sr-only" htmlFor="new-contact-city">City</label>
                                    <input className="form-control" type="text" placeholder="City" id="new-contact-city" value={this.props.contact.city} onChange={this.handleChange.bind(this, "city")}></input></div>
<div className="nopad col-xs-3">
                                    <label className="sr-only" htmlFor="new-contact-state">State</label>
                                    <input className="form-control" type="text" placeholder="State" id="new-contact-state" value={this.props.contact.state} onChange={this.handleChange.bind(this, "state")}></input></div>
<div className="nopad col-xs-3">
                                    <label className="sr-only" htmlFor="new-contact-zip">Zip Code</label>
                                    <input className="form-control" type="text" placeholder="Zip" id="new-contact-zip" value={this.props.contact.zip} onChange={this.handleChange.bind(this, "zip")}></input></div>
</div>
<div className="form-group">
                                <label className="sr-only" htmlFor="new-contact-phone">Phone</label>
                                <input className="col-xs-12 form-control" type="text" placeholder="Phone" id="new-contact-phone" value={this.props.contact.phone} onChange={this.handleChange.bind(this, "phone")}></input></div>
<div className="form-group">
                                <label className="sr-only" htmlFor="new-contact-email">E-mail</label>
                                <input type="text" className="col-xs-12 form-control" placeholder="E-mail" id="new-contact-email" value={this.props.contact.email} onChange={this.handleChange.bind(this, "email")}></input></div>
</div>
<div className="col-xs-4">
<div className="form-group">
<div><label className="sr-only" htmlFor="new-contact-description">Notes</label></div>
<textarea className="col-xs-12 form-control" placeholder="Notes" id="new-contact-description" rows="5" value={this.props.contact.notes} onChange={this.handleChange.bind(this, "notes")}></textarea></div>
</div>
</form></div>
</Jumbotron></div>
);
    }
});

What about the data?

So far we have some react components that can display things, list things, and add things. But now we need the application.  A basic react application does two things: Maintains State, and shows a View.  The data isn’t part of react… which is why some people go with another framework.  But I think this gives us a lot of freedom.  Being old-school, lets setup a BLLC and something to hold state. You’ll notice API references… those will be shown later.

var contactApi = require('./api.js');
var util = require('./utilities.js');

var state = {};

var bllc = {
    saveContact: function() {
        var contacts = state.contacts;
        var contact = state.modalContact;

        var req = contactApi.updateContact(contact);

        req.then(function(contact) {
            if (contact) {
                contacts = _.unionBy([contact], contacts, 'id');
                setState({contacts: contacts});
            }
        });

        setState({modalContact: {}, showModal: false});
    },

    removeContact: function(id) {
        contactApi.deleteContact(id);
        var contacts = state.contacts.filter(function(o) { return o.id !== id; });
        setState({contacts: contacts});
    },

    editContact: function(contact) {
        setState({ modalContact: contact, showModal: true });
    },

    updateSearchText: function(searchText) {
        setState({ searchText: searchText});
    },
};

That “setState” thing

function setState(changes) {
    Object.assign(state, changes);
}

But wait, we need to re-render whenever state changes. And this is where react get’s awesome. I don’t need to check dirty to see what to change. I don’t need $scope.watches everywhere… I don’t need to make sure $scope.$apply gets called, I don’t need to check $$phase… I just say RENDER ALL THE THINGS. And react does it. Quickly. So setState now becomes:

var ContactView = require('./components/contact-view.jsx');

function setState(changes) {
    Object.assign(state, changes);
    ReactDOM.render(
        React.createElement(ContactView, Object.assign({}, state, {
            onContactChange: modalOperations.updateModalContact,
            onShowContactModal: modalOperations.showContactModal,
            onSaveContact: bllc.saveContact,
            onRemoveContact: bllc.removeContact,
            onEditContact: bllc.editContact,
            onUpdateSearchText: bllc.updateSearchText
        })),

        document.getElementById('react-app')
    );
}

So now let’s define those modal operations

var modalOperations = {
    updateModalContact: function (contact) {
        setState({ modalContact: contact });
    },

    showContactModal: function(display, contact) {
        if (display) {
            setState({ modalContact: contact || {id: util.generateUUID() }, showModal: true });
        } else {
            setState({showModal: false});
        }
    },
};

Bootstrap the application

// Set initial data
setState({
  contacts: [],
  modalContact: {},
  showModal: false,
  searchText: ''
});

And load up some data

// Get initial data from server
contactApi.getAllContacts().then(function(contacts) {
    setState({contacts: contacts});
});

And suddenly we have (almost) have a full-fledged react/bootstrap/express application. The full printout of the base application:

var ContactView = require('./components/contact-view.jsx');
var contactApi = require('./api.js');
var util = require('./utilities.js');

var state = {};

var bllc = {
    saveContact: function() {
        var contacts = state.contacts;
        var contact = state.modalContact;

        var req = contactApi.updateContact(contact);

        req.then(function(contact) {
            if (contact) {
                contacts = _.unionBy([contact], contacts, 'id');
                setState({contacts: contacts});
            }
        });

        setState({modalContact: {}, showModal: false});
    },

    removeContact: function(id) {
        contactApi.deleteContact(id);
        var contacts = state.contacts.filter(function(o) { return o.id !== id; });
        setState({contacts: contacts});
    },

    editContact: function(contact) {
        setState({ modalContact: contact, showModal: true });
    },

    updateSearchText: function(searchText) {
        setState({ searchText: searchText});
    },
};

var modalOperations = {
    updateModalContact: function (contact) {
        setState({ modalContact: contact });
    },

    showContactModal: function(display, contact) {
        if (display) {
            setState({ modalContact: contact || {id: util.generateUUID() }, showModal: true });
        } else {
            setState({showModal: false});
        }
    },
};

function setState(changes) {
    Object.assign(state, changes);
    ReactDOM.render(
        React.createElement(ContactView, Object.assign({}, state, {
            onContactChange: modalOperations.updateModalContact,
            onShowContactModal: modalOperations.showContactModal,
            onSaveContact: bllc.saveContact,
            onRemoveContact: bllc.removeContact,
            onEditContact: bllc.editContact,
            onUpdateSearchText: bllc.updateSearchText
        })),

        document.getElementById('react-app')
    );
}

// Set initial data
setState({
  contacts: [],
  modalContact: {},
  showModal: false,
  searchText: ''
});

// Get initial data from server
contactApi.getAllContacts().then(function(contacts) {
    setState({contacts: contacts});
});

Data: Moving between Server and Client

The magic behind the curtains is the API, and it’s tiny thanks to fetch:

module.exports = ((function() {
    return {
      getAllContacts: function() {
          return fetch('/api/contacts').then(function(response) {
              return response.json();
          });
      },
      updateContact: function(contact) {
          if (!contact.id) throw new Error('Contact in invalid state!');
          return fetch('/api/contacts/' + contact.id, {
              method: 'PUT',
              headers: {
                  'Accept': 'application/json',
                  'Content-Type': 'application/json'
              },
              body: JSON.stringify(contact)
          }).then(function(response) {
              if (response.status === 200) {
                  return response.json();
              }

              return null;
          });
      },
      deleteContact: function(id) {
          if (!id) throw new Error('Invalid id!');
          return fetch('/api/contacts/' + id, {
              method: 'DELETE'
          });
      }
    };
}))();

The 10,000 Foot View – And missing Requirement

We can now see that contacts.js (referenced from bundles.js) was the jumping off point of the application.  It pulled in api.js and utilities.js for data manipulation, and contact-view.js which pulled in the rest of the components for the view.  We have a full working application, but we still have that cassandra requirement.  But luckily, we initially wrote a repo called contacts.js, which just published an appropriate CRUD contract.  If we create a second repo (contacts-cassandra.js) and modify api.js to pull it in instead, we don’t have to change anything else!

<span class="kd">var</span> <span class="nx">contactsRepo</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s1">'../repo/contacts-cassandra'</span><span class="p">);</span>

The contacts-cassandra.js contents are as follows:

var cassandra = require('cassandra-driver');
var q = require('q');
var client = new cassandra.Client({contactPoints: ['127.0.0.1'], keyspace: 'argo'});

var repo = {
    getAll: function() {
        var deferred = q.defer();
        var query = 'SELECT id, contact FROM contacts';

        client.execute(query, function (err, result) {
           if (!err){
               if ( result.rows.length > 0 ) {
                   var contacts = [];
                   for(var i = 0; i < result.rows.length; i++) {                        var contact = result.rows[i].contact;                        contact.id = result.rows[i].id;                        contacts.push(contact);                    }                                        deferred.resolve(contacts);                } else {                    deferred.resolve([]);                }            } else {                deferred.resolve([]);            }         });                  return deferred.promise;     },          get: function(id) {         var deferred = q.defer();         var query = 'SELECT id, contact FROM contacts WHERE id = ?';                  client.execute(query, [id], { prepare: true }, function (err, result) {            if (!err){                if ( result.rows.length > 0 ) {
                   var row = result.first();
                   var contact = row.contact;
                   contact.id = row.id;

                   deferred.resolve(contact);
               } else {
                   deferred.resolve(null);
               }
           } else {
               deferred.resolve(null);
           }
        });

        return deferred.promise;
    },
    upsert: function(contact) {
        var record = Object.assign({}, contact);
        delete record.id;

        var deferred = q.defer();
        var query = "INSERT into contacts (id, contact) VALUES (?, ?)";
        client.execute(query, [contact.id, record], { prepare: true }, function(err, result) {
            if (err) {
                deferred.resolve(null);
                console.log(err);
            }
            else deferred.resolve(contact);
        });
        return deferred.promise;
    },
    remove: function(id) {
        var deferred = q.defer();
        var query = "DELETE FROM contacts WHERE id = ?";
        client.execute(query, [id], { prepare: true }, function(err, result) {
            if (err) deferred.resolve(false);
            else deferred.resolve(true);
        });

        return deferred.promise;
    }
};

module.exports = repo;

Writing those CQL statements directly in code definitely gave me the ADO.NET heebie jeebies, but everything is parameterized, so it should be safe.

Done

Now we have a full working React application that is performant, simple, and hopefully a decent example of the technology.  I had a lot of fun with this batting practice, and hopefully this writeup will be useful to someone in the future.  Happy Coding!

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s