Click here to Skip to main content
15,881,898 members
Articles / Web Development / React

Madcap Idea Part 6: Static Screen Design

Rate me:
Please Sign up or sign in to vote.
5.00/5 (5 votes)
27 Jun 2017CPOL8 min read 4.9K  
In this post, we will be implementing the static screen designs.

Last Time

Last time, we looked at setting up the react router, which used some dummy screens. This time, we will actually be implementing the static screen designs. The screens will be pretty much what the end application will look like, with the exception that they are not wired up to the Play framework backend, and for now might use dummy data to show what they would look like for real, when the entire app is completed and working.

PreAmble

Just as a reminder, this is part of my ongoing set of posts which I talk about here:

https://sachabarbs.wordpress.com/2017/05/01/madcap-idea/, where we will be building up to a point where we have a full app using lots of different stuff, such as these:

  • WebPack
  • React.js
  • React Router
  • TypeScript
  • Babel.js
  • Akka
  • Scala
  • Play (Scala Http Stack)
  • MySql
  • SBT
  • Kafka
  • Kafka Streams

So I think the best way to cover this post is to do it screen by screen, so let's start at the beginning and end at the end.

The Screens

In this section, we will simply go through each of the screens and examine the code and the final rendering and discuss any important code that might be contained within the screen being discussed.

Login

This is the entire code for the login screen:

JavaScript
import * as React from "react";
import * as ReactDOM from "react-dom";

import 'bootstrap/dist/css/bootstrap.css';
import
{
    Well,
    Grid,
    Row,
    Col,
    ButtonInput
} from "react-bootstrap";

import { Form, ValidatedInput } from 'react-bootstrap-validation';

import revalidator from 'revalidator';

let schema = {
    properties: {
        email: {
            type: 'string',
            maxLength: 255,
            format: 'email',
            required: true,
            allowEmpty: false
        },
        password: {
            type: 'string',
            minLength: 8,
            maxLength: 60,
            required: true,
            allowEmpty: false
        }
    }
};

export class Login extends React.Component<undefined, undefined> {
    render() {
        return (
            <Well className="outer-well">
                <Form
                    // Supply callbacks to both valid and invalid
                    // submit attempts
                    validateAll={this._validateForm}
                    onInvalidSubmit={this._handleInvalidSubmit}
                    onValidSubmit={this._handleValidSubmit}>
                    <Grid>
                        <Row className="show-grid">
                            <Col xs={10} md={6}>
                                <h4>ENTER YOUR LOGIN DETAILS</h4>
                            </Col>
                        </Row>
                        <Row className="show-grid">
                            <Col xs={10} md={6}>
                                <ValidatedInput type='text'
                                    label='Email'
                                    name='email'
                                    errorHelp='Email address is invalid'/>

                            </Col>
                        </Row>
                        <Row className="show-grid">
                            <Col xs={10} md={6}>
                                <ValidatedInput type='password'
                                    name='password'
                                    label='Password'
                                    errorHelp='Password is invalid'/>

                            </Col>
                        </Row>
                        <Row className="show-grid">
                            <Col xs={10} md={6}>
                                <ButtonInput
                                    id="loginBtn"
                                    type='submit'
                                    bsSize='small'
                                    bsStyle='primary'
                                    value='Register'>Login</ButtonInput>
                            </Col>
                        </Row>
                    </Grid>
                </Form>
            </Well>
        )
    }

    _validateForm = (values) => {
        let res = revalidator.validate(values, schema);

        // If the values passed validation, we return true
        if (res.valid) {
            return true;
        }

        // Otherwise we should return an object containing errors
        // e.g. { email: true, password: true }
        return res.errors.reduce((errors, error) => {
            // Set each property to either true or
            // a string error description
            errors[error.property] = true;

            return errors;
        }, {});
    }

    _handleInvalidSubmit = (errors, values) => {
        // Errors is an array containing input names
        // that failed to validate
        alert("Form has errors and may not be submitted");
    }

    _handleValidSubmit = (values) => {
        // Values is an object containing all values
        // from the inputs
        console.log("Form may be submitted");
        console.log(values);
    }
}

Where this will look like this in a browser:

image

There are a couple of things to talk about on this screen.

Firstly, there is the use of React-bootstrap components, which are all imported at the top of the file like this:

JavaScript
import 'bootstrap/dist/css/bootstrap.css';
import
{
    Well,
    Grid,
    Row,
    Col,
    ButtonInput
} from "react-bootstrap";

This allows us to import the actual React-bootstrap components and the CSS for bootstrap. React-bootstrap doesn’t really mind which CSS it uses, as long as it is a version of regular bootstrap CSS. I am using bootstrap v3.0 CSS here. React-bootstrap is more about the actual markup, and doing things in a more React like manner, to reduce some of the regular Bootstrap markup, which is possible thanks to using React.

The other important part about this screen is that it does client side validation. This is done using this library: react-bootstrap-validation. There are a few React validation libraries out there, but I like the simplicity and elegance of this one a lot. You really only have to do a couple of thing (as can be seen in full in the code above).

You need to import the library:

JavaScript
import { Form, ValidatedInput } from 'react-bootstrap-validation';

import revalidator from 'revalidator';

Then you need a schema describing your form that you are trying to validate:

JavaScript
let schema = {
    properties: {
        email: {
            type: 'string',
            maxLength: 255,
            format: 'email',
            required: true,
            allowEmpty: false
        },
        password: {
            type: 'string',
            minLength: 8,
            maxLength: 60,
            required: true,
            allowEmpty: false
        }
    }
};

And then, you need to create the actual form (here is a stripped down one that matches the schema above):

JavaScript
<Form>
	<ValidatedInput type='text'
		label='Email'
		name='email'
		errorHelp='Email address is invalid'/>
	<ValidatedInput type='password'
		name='password'
		label='Password'
		errorHelp='Password is invalid'/>
</Form>

And we must also supply some validation callback hooks for the form, which are done like this:

JavaScript
<Form
    // Supply callbacks to both valid and invalid
    // submit attempts
    validateAll={this._validateForm}
    onInvalidSubmit={this._handleInvalidSubmit}
    onValidSubmit={this._handleValidSubmit}>

</Form>

Where the code for these callbacks looks like this:

JavaScript
export class Login extends React.Component<undefined, undefined> {
    render() {
        return (
          .....
        )
    }


    _validateForm = (values) => {
        let res = revalidator.validate(values, schema);

        // If the values passed validation, we return true
        if (res.valid) {
            return true;
        }

        // Otherwise we should return an object containing errors
        // e.g. { email: true, password: true }
        return res.errors.reduce((errors, error) => {
            // Set each property to either true or
            // a string error description
            errors[error.property] = true;

            return errors;
        }, {});
    }

    _handleInvalidSubmit = (errors, values) => {
        // Errors is an array containing input names
        // that failed to validate
        alert("Form has errors and may not be submitted");
    }

    _handleValidSubmit = (values) => {
        // Values is an object containing all values
        // from the inputs
        console.log("Form may be submitted");
        console.log(values);
    }
}

So that pretty much explains how the validation works, what does it look like when run? Well, it looks like this when it's not valid:

image

And that is pretty much the static design of the Login screen, let's move on.

Register

The Register screen uses the same validation stuff as above, so I will not cover it again. The only thing of note in the Register screen is that it will either need to render a Passenger registration or a Driver registration depending on what option was chosen by the user.

To do this conditional rendering, I opted for a set of 2 simple buttons that modify the internal state of the main Register React component, and will then either display a PassengerRegistration component of a DriverRegistration component.

Here is the full code for the main Register React component:

JavaScript
import * as React from "react";
import * as ReactDOM from "react-dom";

import 'bootstrap/dist/css/bootstrap.css';
import
{
    Well,
    Grid,
    Row,
    Col,
    ButtonInput,
    ButtonGroup,
    Button
} from "react-bootstrap";

import { PassengerRegistration } from "./PassengerRegistration";
import { DriverRegistration } from "./DriverRegistration";


export interface RegisterState {
    option: string;
}

export class Register extends React.Component<any, RegisterState> {

    constructor(props: any) {
        super(props);
        this.state = { option: "passenger" };
    }

    render() {
        return (
            <Well className="outer-well">
                <Grid>
                    <Row className="show-grid">
                        <Col xs={10} md={6}>
                            <h4>PLEASE ENTER YOUR REGISTRATION DETAILS</h4>
                        </Col>
                    </Row>
                    <Row className="show-grid">
                        <Col xs={10} md={6}>
                            <h6>Choose your registration type </h6>
                        </Col>
                    </Row>
                    <Row className="show-grid">
                        <Col xs={10} md={6}>
                            <ButtonGroup>
                                <Button bsSize='small' 
                                onClick={this._onOptionChange.bind
                                (this, 'passenger') } 
                                active={this.state.option === 'passenger'}>
                                Passenger</Button>
                                <Button bsSize='small' 
                                onClick={this._onOptionChange.bind
                                (this, 'driver') } 
                                active={this.state.option === 'driver'}>
                                Driver</Button>
                            </ButtonGroup>
                        </Col>
                    </Row>
                    <Row className="show-grid">
                        <Col xs={10} md={6}>
                            {this.state.option === 'passenger' ?
                                <div><PassengerRegistration/></div> :
                                <div><DriverRegistration/></div>
                            }
                        </Col>
                    </Row>
                </Grid>
            </Well>
        )
    }

    _onOptionChange(option) {
        this.setState({
            option: option
        });
    }
}

As you can see, this code contains the state that is set to determine which type of sub registration component to actually render (see the Render() method above).

The sub components are as shown below (as stated above, we already discussed validation, so I will not cover that again).

PassengerRegistration

Here is the full code for this sub component:

JavaScript
import * as React from "react";
import * as ReactDOM from "react-dom";

import 'bootstrap/dist/css/bootstrap.css';
import
{
    Well,
    Grid,
    Row,
    Col,
    ButtonInput
} from "react-bootstrap";

import { Form, ValidatedInput } from 'react-bootstrap-validation';

import revalidator from 'revalidator';

let schema = {
    properties: {
        fullname: {
            type: 'string',
            minLength: 8,
            maxLength: 12,
            required: true,
            allowEmpty: false
        },
        email: {
            type: 'string',
            maxLength: 255,
            format: 'email',
            required: true,
            allowEmpty: false
        },
        password: {
            type: 'string',
            minLength: 8,
            maxLength: 60,
            required: true,
            allowEmpty: false
        }
    }
};

export class PassengerRegistration extends React.Component<undefined, undefined> {
    render() {
        return (
            <Form className="submittable-form-inner"
                // Supply callbacks to both valid and invalid
                // submit attempts
                validateAll={this._validateForm}
                onInvalidSubmit={this._handleInvalidSubmit}
                onValidSubmit={this._handleValidSubmit}>
                <Grid>
                    <Row className="show-grid">
                        <Col xs={10} md={6}>
                            <h4>Passenger details</h4>
                        </Col>
                    </Row>
                    <Row className="show-grid">
                        <Col xs={10} md={6}>
                            <ValidatedInput type='text'
                                label='FullName'
                                name='fullname'
                                errorHelp='FullName is invalid'/>

                        </Col>
                    </Row>
                    <Row className="show-grid">
                        <Col xs={10} md={6}>
                            <ValidatedInput type='text'
                                label='Email'
                                name='email'
                                errorHelp='Email address is invalid'/>
                        </Col>
                    </Row>
                    <Row className="show-grid">
                        <Col xs={10} md={6}>
                            <ValidatedInput type='password'
                                label='Password'
                                name='password'
                                errorHelp='Password is invalid'/>
                        </Col>
                    </Row>
                    <Row className="show-grid">
                        <Col xs={10} md={6}>
                            <ButtonInput
                                id="registerBtn"
                                type='submit'
                                bsSize='small'
                                bsStyle='primary'
                                value='Register'>Register</ButtonInput>
                        </Col>
                    </Row>
                </Grid>
            </Form>
        )
    }

    _validateForm = (values) => {
        let res = revalidator.validate(values, schema);

        // If the values passed validation, we return true
        if (res.valid) {
            return true;
        }

        // Otherwise we should return an object containing errors
        // e.g. { email: true, password: true }
        return res.errors.reduce((errors, error) => {
            // Set each property to either true or
            // a string error description
            errors[error.property] = true;

            return errors;
        }, {});
    }

    _handleInvalidSubmit = (errors, values) => {
        // Errors is an array containing input names
        // that failed to validate
        alert("Form has errors and may not be submitted");
    }

    _handleValidSubmit = (values) => {
        // Values is an object containing all values
        // from the inputs
        console.log("Form may be submitted");
        console.log(values);
    }
}

DriverRegistration

Here is the full code for this sub component:

JavaScript
import * as React from "react";
import * as ReactDOM from "react-dom";

import 'bootstrap/dist/css/bootstrap.css';
import
{
    Well,
    Grid,
    Row,
    Col,
    ButtonInput
} from "react-bootstrap";

import { Form, ValidatedInput } from 'react-bootstrap-validation';

import revalidator from 'revalidator';

let schema = {
    properties: {
        fullname: {
            type: 'string',
            minLength: 8,
            maxLength: 12,
            required: true,
            allowEmpty: false
        },
        email: {
            type: 'string',
            maxLength: 255,
            format: 'email',
            required: true,
            allowEmpty: false
        },
        password: {
            type: 'string',
            minLength: 8,
            maxLength: 60,
            required: true,
            allowEmpty: false
        },
        vehicleDescription: {
            type: 'string',
            minLength: 6,
            maxLength: 60,
            required: true,
            allowEmpty: false
        },
        vehicleRegistrationNumber: {
            type: 'string',
            minLength: 6,
            maxLength: 30,
            required: true,
            allowEmpty: false
        }
    }
};

export class DriverRegistration extends React.Component<undefined, undefined> {
    render() {
        return (
            <Form className="submittable-form-inner"
                // Supply callbacks to both valid and invalid
                // submit attempts
                validateAll={this._validateForm}
                onInvalidSubmit={this._handleInvalidSubmit}
                onValidSubmit={this._handleValidSubmit}>
                <Grid>
                    <Row className="show-grid">
                        <Col xs={10} md={6}>
                            <h4>Driver details</h4>
                        </Col>
                    </Row>
                    <Row className="show-grid">
                        <Col xs={10} md={6}>
                            <ValidatedInput type='text'
                                label='FullName'
                                name='fullname'
                                errorHelp='FullName is invalid'/>

                        </Col>
                    </Row>
                    <Row className="show-grid">
                        <Col xs={10} md={6}>
                            <ValidatedInput type='text'
                                label='Email'
                                name='email'
                                errorHelp='Email address is invalid'/>

                        </Col>
                    </Row>
                    <Row className="show-grid">
                        <Col xs={10} md={6}>
                            <ValidatedInput type='password'
                                name='password'
                                label='Password'
                                errorHelp='Password is invalid'/>
                        </Col>
                    </Row>
                    <Row className="show-grid">
                        <Col xs={10} md={6}>
                            <h4>Vehicle details</h4>
                        </Col>
                    </Row>
                    <Row className="show-grid">
                        <Col xs={10} md={6}>
                            <ValidatedInput type='text'
                                label='Vehicle Description'
                                name='vehicleDescription'
                                errorHelp='Vehicle description is invalid'/>
                        </Col>
                    </Row>
                    <Row className="show-grid">
                        <Col xs={10} md={6}>
                            <ValidatedInput type='text'
                                label='Vehicle Registration Number'
                                name='vehicleRegistrationNumber'
                                errorHelp='Vehicle registration number is invalid'/>
                        </Col>
                    </Row>

                    <Row className="show-grid">
                        <Col xs={10} md={6}>
                            <ButtonInput
                                id="registerBtn"
                                type='submit'
                                bsSize='small'
                                bsStyle='primary'
                                value='Register'>Register</ButtonInput>
                        </Col>
                    </Row>
                </Grid>
            </Form>
        )
    }

    _validateForm = (values) => {
        let res = revalidator.validate(values, schema);

        // If the values passed validation, we return true
        if (res.valid) {
            return true;
        }

        // Otherwise we should return an object containing errors
        // e.g. { email: true, password: true }
        return res.errors.reduce((errors, error) => {
            // Set each property to either true or
            // a string error description
            errors[error.property] = true;

            return errors;
        }, {});
    }

    _handleInvalidSubmit = (errors, values) => {
        // Errors is an array containing input names
        // that failed to validate
        alert("Form has errors and may not be submitted");
    }

    _handleValidSubmit = (values) => {
        // Values is an object containing all values
        // from the inputs
        console.log("Form may be submitted");
        console.log(values);
    }
}

And this is the end result, with the Passenger registration option selected.

image

And this is the driver registration:

image

CreateJob

Ok, so now things are getting slightly more exotic. We need to use a Google map along with React. There are many good tutorials that walk you through this, which are great to read so you can see how the Google maps API gets wrapped (typically, this is what is done) and you can also see how to use the React Ref attribute to grab the ACTUAL element from the real DOM to host the map. There are also many available React Google maps components. I had a hunt around and tried out a few and settled on this one: https://github.com/tomchentw/react-google-maps where there are some great demos here: https://tomchentw.github.io/react-google-maps/.

The job of the “CreateJob” screen for me was to allow the user to set their current position on a map, and issue the job out to drivers that may be logged in at that time.

Remember I will be doing all this work on my laptop. So that is my laptop pretending to be many drivers/users, etc. etc., so I can't really use the inbuilt GPS position, everyone would always be at the same place, i.e., wherever my laptop is at that moment.

Ok, so now we know what the job of the “CreateJob” screen is, let's have a look at some of the code.

For the React-Google-Maps, we need this import, otherwise, it's just stuff you have seen before:

JavaScript
import { withGoogleMap, GoogleMap, Marker, InfoBox } from "react-google-maps";

The next part of interest is how we create an actual map which might have a marker on the map with a custom icon. Thanks to the great work of the React-Google-Maps code, this is as simple as this:

JavaScript
const CreateJobGoogleMap = withGoogleMap(props => (
    <GoogleMap
        ref={props.onMapLoad}
        defaultZoom={16}
        defaultCenter={{ lat: 50.8202949, lng: -0.1406958 }}
        onClick={props.onMapClick}>
        <Marker
            position={props.currentPosition}
            icon='/assets/images/passenger.png'>
        </Marker>
    </GoogleMap>
));

It can be seen there that the Marker is using the position that is passed down as props to it from the parent React component. Let's now have a look at the parent React component.

JavaScript
export interface CreateJobState {
    currentPosition: any;
}

export class CreateJob extends React.Component<undefined, CreateJobState> {

    constructor(props: any) {
        super(props);
        this.state = {
            currentPosition: { lat: 50.8202949, lng: -0.1406958 }
          };
    }

    render() {
        return (
            <Well className="outer-well">
                <Grid>
                    <Row className="show-grid">
                        <Col xs={10} md={6}>
                            <h4>SET YOUR CURRENT LOCATION</h4>
                            <h6>Click the map to set your current location</h6>
                        </Col>
                    </Row>
                    <Row className="show-grid">
                        <Col xs={10} md={6}>
                            <CreateJobGoogleMap
                                containerElement={
                                    <div style={{
                                        position: 'relative',
                                        top: 0,
                                        left: 0,
                                        right: 0,
                                        bottom: 0,
                                        width: 600,
                                        height: 600,
                                        justifyContent: 'flex-end',
                                        alignItems: 'center',
                                        marginTop: 20,
                                        marginLeft: 0,
                                        marginRight: 0,
                                        marginBottom: 20
                                    }} />
                                }
                                mapElement={
                                    <div style={{
                                        position: 'relative',
                                        top: 0,
                                        left: 0,
                                        right: 0,
                                        bottom: 0,
                                        width: 600,
                                        height: 600,
                                        marginTop: 20,
                                        marginLeft: 0,
                                        marginRight: 0,
                                        marginBottom: 20
                                    }} />
                                }
                                onMapLoad={this._handleMapLoad}
                                onMapClick={this._handleMapClick}
                                currentPosition={this.state.currentPosition}
                                />
                        </Col>
                    </Row>
                    <Row className="show-grid">
                        <ButtonInput
                            id="createJobBtn"
                            type='submit'
                            bsSize='small'
                            bsStyle='primary'
                            value='Register'>Create Job</ButtonInput>
                    </Row>
                </Grid>
            </Well>
        );
    }

    _handleMapLoad = (map) => {
        if (map) {
            console.log(map.getZoom());
        }
    }

    _handleMapClick = (event) => {
        this.setState({
            currentPosition: event.latLng
        });
    }
}

If you cut through all the bootstrap code to render things nicely, it really boils down to what happens when the map gets clicked?

Well, what happens is that the map getting clicked will fire the _handleMapClick function, which will set the internal currentPosition state value, which was handed down to the map sub component as props, so the Marker will be shown at the clicked location.

This is what the final rendering looks like:

image

ViewJob

This is by far the most complex of all the screens, and here is why:

  • It will have to show the incoming movements (geo-coords wise) of both driver(s) and passenger (icon distinguishes them from each other, as well as metadata).
  • A passenger should be able to accept a driver for a job.
  • It will need to allow a job to be cancelled, with confirmation.
  • It will need to allow a job to be completed.
  • On completion of a job, the driver should be able to rate a passenger.
  • On completion of a job, the passenger should be able to rate a driver.

So as you can see, it WILL do quite a lot, obviously a lot of this will come later once the streaming aspects of the code are completed. It is however quite a lot to do statically.

In fact, it is so much that I have had to create 3 utility components to help me out.

YesNoDialog

This represents a generic re-usable yes/no dialog that can be triggered, here is the code for this one. The important part is that the various prop values can be controlled via the parent component state values.

JavaScript
import * as React from "react";
import * as ReactDOM from "react-dom";
import * as _ from "lodash";

import 'bootstrap/dist/css/bootstrap.css';
import
{
    Button, 
    Modal
} from "react-bootstrap";


//TODO : Fix this
export interface YesNoDialogProps {
    headerText: string;
    theId: string;
    launchButtonText: string;
    yesCallBack: any;
    noCallBack: any;
}

export interface YesNoDialogState {
    showModal: boolean;
}

export class YesNoDialog extends React.Component<YesNoDialogProps, YesNoDialogState> {

    constructor(props) {
        super(props);
        console.log(this.props);
        //set initial state
        this.state = {
            showModal: false
        };
    }

    _yesClicked = () => {
        this.setState({ showModal: false });
        this.props.yesCallBack();
    }

    _noClicked = () => {
        this.setState({ showModal: false });
        this.props.noCallBack();
    }

    _close = () => {
        this.setState({ showModal: false });
    }

    _open = () => {
        this.setState({ showModal: true });
    }

    render() {
        return (
            <div className="leftFloat">

                <Button
                    id={this.props.theId}
                    type='button'
                    bsSize='small'
                    bsStyle='primary'
                    onClick={this._open}>{this.props.launchButtonText}</Button>

                <Modal show={this.state.showModal} onHide={this._close}>
                    <Modal.Header closeButton>
                        <Modal.Title>{ this.props.headerText }</Modal.Title>
                    </Modal.Header>
                    <Modal.Body>
                        <h4>Are you sure?</h4>
                    </Modal.Body>
                    <Modal.Footer>
                        <Button
                            type='button'
                            bsSize='small'
                            bsStyle='primary'
                            onClick={this._yesClicked}>Yes</Button>
                        <Button
                            type='button'
                            bsSize='small'
                            bsStyle='danger'
                            onClick={this._noClicked}>Cancel</Button>
                    </Modal.Footer>
                </Modal>
            </div>
        );
    }
}

This looks like this when rendered:

image

OkDialog

This represents a generic re-usable ok dialog that can be triggered, here is the code for this one. The important part is that the various prop values can be controlled via the parent component state values.

JavaScript
import * as React from "react";
import * as ReactDOM from "react-dom";
import * as _ from "lodash";

import 'bootstrap/dist/css/bootstrap.css';
import
{
    Button, 
    Modal
} from "react-bootstrap";


//TODO : Fix this
export interface OkDialogProps {
    headerText: string;
    bodyText: string;
    open: boolean;
    okCallBack: any;
}

export interface OkDialogState {
    showModal: boolean;
}

export class OkDialog extends React.Component<OkDialogProps, OkDialogState> {

    constructor(props) {
        super(props);
        console.log(this.props);
        //set initial state
        this.state = {
            showModal: false
        };
    }

    componentDidMount() {
        if (this.props.open === true) {
            this.setState({ showModal: true });
        }
    }

    _okClicked = () => {
        this.setState({ showModal: false });
        this.props.okCallBack();
    }

    _close = () => {
        this.setState({ showModal: false });
        this.props.okCallBack();
    }

    _open = () => {
        this.setState({ showModal: true });
    }

    render() {
        return (
            <div className="leftFloat">

                <Modal show={this.state.showModal} onHide={this._close}>
                    <Modal.Header closeButton>
                        <Modal.Title>{ this.props.headerText }</Modal.Title>
                    </Modal.Header>
                    <Modal.Body>
                        <h4>{this.props.bodyText}</h4>
                    </Modal.Body>
                    <Modal.Footer>
                        <Button
                            type='button'
                            bsSize='small'
                            bsStyle='primary'
                            onClick={this._okClicked}>Ok</Button>
                    </Modal.Footer>
                </Modal>
            </div>
        );
    }
}

This looks like this when rendered:

image

RatingDialog

This represents a generic re-usable rating control where rating can be from 1-5, here is the code for this one. The important part is that the various prop values can be controlled via the parent component state values.

JavaScript
import * as React from "react";
import * as ReactDOM from "react-dom";
import * as _ from "lodash";

import 'bootstrap/dist/css/bootstrap.css';
import
{
    Button, 
    Modal
} from "react-bootstrap";


import ReactStars from 'react-stars';


export interface RatingDialogProps {
    headerText: string;
    theId: string;
    okCallBack: any;
}

export interface RatingDialogState {
    showModal: boolean;
    rating: number;
}

export class RatingDialog extends React.Component<RatingDialogProps, RatingDialogState> {

    constructor(props) {
        super(props);
        console.log(this.props); 
        //set initial state
        this.state = {
            showModal: false,
            rating:0
        };
    }

    _close = () => {
        this.setState(
            {
                showModal: false,
                rating:0
            }
        );
    }

    _open = () => {
        this.setState(
            {
                showModal: true,
                rating: 0
            }
        );
    }

    _ratingChanged = (newRating) => {
        console.log(newRating)
        this.setState(
            {
                rating: newRating
            }
        );
    }

    _okClicked = () => {
        this._close();
        this.props.okCallBack();
    }

    render() {
        return (
            <div className="leftFloat">

                <Button
                    id={this.props.theId}
                    type='button'
                    bsSize='small'
                    bsStyle='primary'
                    onClick={this._open}>Complete</Button>

                <Modal show={this.state.showModal} onHide={this._close}>
                    <Modal.Header closeButton>
                        <Modal.Title>{ this.props.headerText }</Modal.Title>
                    </Modal.Header>
                    <Modal.Body>
                        <h4>Give your rating between 1-5</h4>
                        <ReactStars count={5}
                                    onChange={this._ratingChanged}
                                    size={24}
                                    color2={'#ffd700'} />
                    </Modal.Body>
                    <Modal.Footer>
                        <Button
                            type='submit'
                            bsSize='small'
                            bsStyle='primary'
                            onClick={this._okClicked}>Ok</Button>
                    </Modal.Footer>
                </Modal>
            </div>
        );
    }
}

This looks like this when rendered:

image

For the rating component, I make use of this React library: https://www.npmjs.com/package/react-stars

So this just leaves the main component that represents the page, the full code is as follows:

JavaScript
import * as React from "react";
import * as ReactDOM from "react-dom";
import * as _ from "lodash";

import { RatingDialog } from "./components/RatingDialog";
import { YesNoDialog } from "./components/YesNoDialog";
import { OkDialog } from "./components/OkDialog";


import 'bootstrap/dist/css/bootstrap.css';
import
{
    Well,
    Grid,
    Row,
    Col,
    ButtonInput,
    ButtonGroup,
    Button, 
    Modal,
    Popover,
    Tooltip,
    OverlayTrigger
} from "react-bootstrap";

import { withGoogleMap, GoogleMap, Marker, OverlayView } from "react-google-maps";

const STYLES = {
    overlayView: {
        background: `white`,
        border: `1px solid #ccc`,
        padding: 15,
    }
}

const GetPixelPositionOffset = (width, height) => {
    return { x: -(width / 2), y: -(height / 2) };
}

const ViewJobGoogleMap = withGoogleMap(props => (
    <GoogleMap
        ref={props.onMapLoad}
        defaultZoom={14}
        defaultCenter={{ lat: 50.8202949, lng: -0.1406958 }}>


        {props.markers.map((marker, index) => (
            <OverlayView
                key={marker.key}
                mapPaneName={OverlayView.OVERLAY_MOUSE_TARGET}
                position={marker.position}
                getPixelPositionOffset={GetPixelPositionOffset}>
                <div style={STYLES.overlayView}>
                    <img src={marker.icon} />
                    <strong>{marker.key}</strong>
                    <br/>
                    <Button
                        type='button'
                        bsSize='xsmall'
                        bsStyle='primary'
                        onClick={() => props.onMarkerClick(marker) }
                        value='Accept'>Accept</Button>
                </div>
            </OverlayView>
        )) }
    </GoogleMap>
));

export interface ViewJobState {
    markers: any;
    okDialogOpen: boolean;
    okDialogKey: any;
    okDialogHeaderText: string;
    okDialogBodyText: string;
}

export class ViewJob extends React.Component<undefined, ViewJobState> {

    constructor(props: any) {
        super(props);
        this.state = {
            markers: [{
                    position: {
                        lat: 50.8202949,
                        lng: -0.1406958
                    },
                    key: 'driver_1',
                    icon: '/assets/images/driver.png'
                },
                {
                    position: {
                        lat: 50.8128187,
                        lng: -0.1361418
                    },
                    key: 'driver_2',
                    icon: '/assets/images/driver.png'
                }
            ],
            okDialogHeaderText: '',
            okDialogBodyText: '',
            okDialogOpen: false,
            okDialogKey:0
        };
    }

    render() {
        return (
            <Well className="outer-well">
                <Grid>
                    <Row className="show-grid">
                        <Col xs={10} md={6}>
                            <h4>CURRENT JOB</h4>
                        </Col>
                    </Row>
                    <Row className="show-grid">
                        <Col xs={10} md={6}>
                            <ViewJobGoogleMap
                                containerElement={
                                    <div style={{
                                        position: 'relative',
                                        top: 0,
                                        left: 0,
                                        right: 0,
                                        bottom: 0,
                                        width: 600,
                                        height: 600,
                                        justifyContent: 'flex-end',
                                        alignItems: 'center',
                                        marginTop: 20,
                                        marginLeft: 0,
                                        marginRight: 0,
                                        marginBottom: 20
                                    }} />
                                }
                                mapElement={
                                    <div style={{
                                        position: 'relative',
                                        top: 0,
                                        left: 0,
                                        right: 0,
                                        bottom: 0,
                                        width: 600,
                                        height: 600,
                                        marginTop: 20,
                                        marginLeft: 0,
                                        marginRight: 0,
                                        marginBottom: 20
                                    }} />
                                }
                                markers={this.state.markers}
                                onMarkerClick={this._handleClick}/>
                        </Col>
                    </Row>
                    <Row className="show-grid">
                        <span>
                            <RatingDialog
                                theId="viewJobCompleteBtn"
                                headerText="Rate your driver/passenger"
                                okCallBack= {this._ratingsDialogOkCallBack}/>

                            <YesNoDialog
                                theId="viewJobCancelBtn"
                                launchButtonText="Cancel"
                                yesCallBack={this._jobCancelledCallBack}
                                noCallBack={this._jobNotCancelledCallBack}
                                headerText="Cancel the job"/>

                            <OkDialog
                                open= {this.state.okDialogOpen}
                                okCallBack= {this._okDialogCallBack}
                                headerText={this.state.okDialogHeaderText}
                                bodyText={this.state.okDialogBodyText}
                                key={this.state.okDialogKey}/>
                        </span>
                    </Row>
                </Grid>
            </Well>
        );
    }

    _handleClick = (targetMarker) => {
        console.log('button on overlay clicked:' + targetMarker.key);
    }

    _ratingsDialogOkCallBack = () => {
        console.log('RATINGS OK CLICKED');
        this.setState(
            {
                okDialogHeaderText: 'Ratings',
                okDialogBodyText: 'Rating successfully recorded',
                okDialogOpen: true,
                okDialogKey: Math.random()
            });
    }

    _jobCancelledCallBack = () => {
        console.log('YES CLICKED');
        this.setState(
            {
                okDialogHeaderText: 'Job Cancellaton',
                okDialogBodyText: 'Job successfully cancelled',
                okDialogOpen: true,
                okDialogKey: Math.random() 
            });
    }

    _jobNotCancelledCallBack = () => {
        console.log('NO CLICKED');
        this.setState(
            {
                okDialogHeaderText: 'Job Cancellaton',
                okDialogBodyText: 'Job remains open',
                okDialogOpen: true,
                okDialogKey: Math.random() 
            });
    }

    _okDialogCallBack = () => {
        console.log('OK on OkDialog CLICKED');
        this.setState(
            {
                okDialogOpen: false
            });
    }
}

Which makes use of some of the React-Google-Maps goodness we saw before, but instead of Marker objects, we are now using Overlay objects to represent the driver/passenger information, obviously we make use of an array of items this time, as many driver(s) might bid for a job. Also shown here is how you can create the dialogs on demand and have their content tailored to the specific scenario in hand.

Right now, when this component renders, it looks like this:

image

ViewRating

The Rating screen is perhaps the easiest of all the screens. It simply allows you to view your overall rating and who made the rating for you so far. This would like be an active query over a Kafka streams KTable.

Here is the full code for this screen, there is not much to say on this one:

JavaScript
import * as React from "react";
import * as ReactDOM from "react-dom";

import 'bootstrap/dist/css/bootstrap.css';
import
{
    Well,
    Grid,
    Row,
    Col,
    Label
} from "react-bootstrap";


export class ViewRating extends React.Component<undefined, undefined> {
    render() {
        return (
            <Well className="outer-well">
                    <Grid>
                        <Row className="show-grid">
                            <Col xs={6} md={6}>
                                <div>
                                    <h4>YOUR RANKING 
                                    <Label>4.2</Label></h4>
                                </div>
                            </Col>
                        </Row>
                        <Row className="show-grid">
                            <Col xs={10} md={6}>
                                <h6>The finer details of 
                                your ranking are shown below</h6>
                            </Col>
                        </Row>
                        <Row className="show-grid">
                            <Col xs={10} md={6}>
                                <div className="table-responsive">
                                    <table className="table table-striped 
                                    table-bordered table-condensed factTable">
                                        <thead>
                                            <tr>
                                                <th>Ranked By</th>
                                                <th>Rank Given</th>
                                            </tr>
                                        </thead>
                                        <tbody>
                                            <tr>
                                                <td>John Doe</td>
                                                <td>4.2</td>
                                            </tr>
                                            <tr>
                                                <td>Mary Moe</td>
                                                <td>4.7</td>
                                            </tr>
                                            <tr>
                                                <td>July Dooley</td>
                                                <td>4.5</td>
                                            </tr>
                                        </tbody>
                                    </table>
                                </div>
                            </Col>
                        </Row>
                    </Grid>
            </Well>
        )
    }
}

When rendered, it looks like this:

image

Conclusion

I am no web designer, but I am fairly pleased with the outcome of the static screen designs, and have found using React-bootstrap/react/the map components and the React-validation to be quite simple and they all seemed to play together quite nicely.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United Kingdom United Kingdom
I currently hold the following qualifications (amongst others, I also studied Music Technology and Electronics, for my sins)

- MSc (Passed with distinctions), in Information Technology for E-Commerce
- BSc Hons (1st class) in Computer Science & Artificial Intelligence

Both of these at Sussex University UK.

Award(s)

I am lucky enough to have won a few awards for Zany Crazy code articles over the years

  • Microsoft C# MVP 2016
  • Codeproject MVP 2016
  • Microsoft C# MVP 2015
  • Codeproject MVP 2015
  • Microsoft C# MVP 2014
  • Codeproject MVP 2014
  • Microsoft C# MVP 2013
  • Codeproject MVP 2013
  • Microsoft C# MVP 2012
  • Codeproject MVP 2012
  • Microsoft C# MVP 2011
  • Codeproject MVP 2011
  • Microsoft C# MVP 2010
  • Codeproject MVP 2010
  • Microsoft C# MVP 2009
  • Codeproject MVP 2009
  • Microsoft C# MVP 2008
  • Codeproject MVP 2008
  • And numerous codeproject awards which you can see over at my blog

Comments and Discussions

 
-- There are no messages in this forum --