React Ace Code Editor

Part 6, Lesson 8



In this lesson, we'll add some exercises to the client-side using the Ace code editor plugin...


Seed Data

Within flask-microservices-eval, start by adding a seed command to the manage.py file to populate the database with some initial data:

@manager.command
def seed_db():
    """Seeds the database."""
    db.session.add(Exercise(
        exercise_body='Define a function called sum that takes two integers as arguments and returns their sum.',
        test_code='print(sum(2, 3))',
        test_code_solution='5'
    ))
    db.session.add(Exercise(
        exercise_body='Define a function called reverse that takes a string as an argument and returns the string in reversed order.',
        test_code='print(reverse(racecar))',
        test_code_solution='racecar'
    ))
    db.session.add(Exercise(
        exercise_body='Define a function called factorial that takes a random number as an argument and then returns the factorial of that given number.',
        test_code='print(factorial(5))',
        test_code_solution='120'
    ))
    db.session.commit()

Set dev as the active machine:

$ docker-machine env dev
$ eval $(docker-machine env dev)

Set the environment variables:

$ export REACT_APP_USERS_SERVICE_URL=DOCKER_MACHINE_DEV_IP:5001
$ export TEST_URL=DOCKER_MACHINE_DEV_IP

Fire up the containers:

$ docker-compose up -d --build

Create the database:

$ docker-compose run eval-service python manage.py recreate_db

Apply the seed:

$ docker-compose run eval-service python manage.py seed_db

Exercise Component

Next, let's add a new Component to the React app. Within flask-microservices-client, add a new class-based component called Exercises.jsx to "src/components":

import React, { Component } from 'react';

class Exercises extends Component {
  constructor (props) {
    super(props)
    this.state = {}
  }
  render() {
    return (
      <div>
        <p>Hello, world!</p>
      </div>
    )
  }
}

export default Exercises

Update the main route in src/App.jsx:

<Route exact path='/' render={() => (
  <Exercises />
)} />

Add the import at the top:

import Exercises from './components/Exercises';

You should see Hello, world! in your browser.

Next, let's wire up an AJAX call to grab the exercises from the Flask eval service and render them in the component.

Add a new method to the component:

getExercises() {
  axios.get(`${process.env.REACT_APP_EVAL_SERVICE_URL}/exercises`)
  .then((res) => { this.setState({ exercises: res.data.data.exercises }); })
  .catch((err) => { console.log(err); })
}

Import axios:

import axios from 'axios';

And set the environment variable:

$ export REACT_APP_EVAL_SERVICE_URL=DOCKER_MACHINE_DEV_IP:5002

Update the environment under the web-service in the docker-compose.yml file in flask-microservices-main:

environment:
  - NODE_ENV=development
  - REACT_APP_USERS_SERVICE_URL=${REACT_APP_USERS_SERVICE_URL}
  - REACT_APP_EVAL_SERVICE_URL=${REACT_APP_EVAL_SERVICE_URL}

Add exercises to the state:

this.state = {
  exercises: []
}

Call getExercises() in the componentDidMount Lifecycle method:

componentDidMount() {
  this.getExercises();
}

Finally, update the render():

render() {
  return (
    <div>
      <h1>Exercises</h1>
      <hr/><br/>
      {this.state.exercises.length &&
        <div key={this.state.exercises[0].id}>
          <p>{this.state.exercises[0].exercise_body}</p>
        </div>
      }
    </div>
  )
}

Ace Code Editor

Ace is an embeddable code editor, which we'll use to allow end users to submit their exercise solutions directly in the browser. We'll use a pre-configured Component for Ace called React-Ace.

Install:

$ npm install --save [email protected]

Add the imports:

import AceEditor from 'react-ace';
import 'brace/mode/python';
import 'brace/theme/solarized_dark';

Update the render():

render() {
  return (
    <div>
      <h1>Exercises</h1>
      <hr/><br/>
        {this.state.exercises.length &&
          <div key={this.state.exercises[0].id}>
            <h4>{this.state.exercises[0].exercise_body}</h4>
              <AceEditor
                mode="python"
                theme="solarized_dark"
                name={(this.state.exercises[0].id).toString()}
                onLoad={this.onLoad}
                fontSize={14}
                height={'175px'}
                showPrintMargin={true}
                showGutter={true}
                highlightActiveLine={true}
                value={'# Enter your code here.'}
                style={{
                  marginBottom: '10px'
                }}
              />
            <Button bsStyle="primary" bsSize="small">Run Code</Button>
            <br/><br/>
          </div>
        }
    </div>
  )
}

Take note of how we created a new instance of the Ace Editor. Experiment with the available props if you'd like. We also added a Bootstrap-styled button with React Bootstrap. Make sure you add the import:

import { Button } from 'react-bootstrap';

Jump back to the browser. You should see something similar to:

react ace code editor

Ensure Authenticated

Next, let's only display the button if a user is logged in:

{this.props.isAuthenticated &&
  <Button bsStyle="primary" bsSize="small">Run Code</Button>
}

Pass the appropriate prop in by updating the route in src/App.jsx:

<Route exact path='/' render={() => (
  <Exercises
    isAuthenticated={this.state.isAuthenticated}
  />
)} />

Let's also add a message for those not logged in. Update the render() method in src/components/Exercises.jsx

render() {
  return (
    <div>
      <h1>Exercises</h1>
      <hr/><br/>
        {!this.props.isAuthenticated &&
          <div>
            <div className="alert alert-warning">
              <span
                className="glyphicon glyphicon-exclamation-sign"
                aria-hidden="true">
              </span>
              <span>&nbsp;Please log in to submit an exercise.</span>
            </div>
            <br/>
          </div>
        }
        {this.state.exercises.length &&
          <div key={this.state.exercises[0].id}>
            <h4>{this.state.exercises[0].exercise_body}</h4>
              <AceEditor
                mode="python"
                theme="solarized_dark"
                name={(this.state.exercises[0].id).toString()}
                onLoad={this.onLoad}
                fontSize={14}
                height={'175px'}
                showPrintMargin={true}
                showGutter={true}
                highlightActiveLine={true}
                value={'# Enter your code here.'}
                style={{
                  marginBottom: '10px'
                }}
              />
              {this.props.isAuthenticated &&
                <Button bsStyle="primary" bsSize="small">Run Code</Button>
              }
            <br/><br/>
          </div>
        }
    </div>
  )
}

Event Handler

Start by adding the value to the state:

this.state = {
  exercises: [],
  aceEditorValue: '# Enter your code here.'
}

Then, update the value property of the AceEditor:

value={this.state.aceEditorValue}

Next, add an onChange prop to the AceEditor as well:

onChange={this.onChange.bind(this)}

onChange, which is an event used to retrieve the current content of the editor, can be used to fire the following function to update the state:

onChange(value) {
  this.setState({ aceEditorValue: value });
}

Finally, add an onClick handler to the button:

{this.props.isAuthenticated &&
  <Button
    bsStyle="primary"
    bsSize="small"
    onClick={this.submitExercise.bind(this)}
  >Run Code</Button>
}

Add the submitExercise method to the component:

submitExercise(event) {
  event.preventDefault();
  const data = {
    answer: this.state.aceEditorValue
  }
  const url = 'tbd'
  axios.post(url, data)
  .then((res) => {
    console.log(res);
  })
  .catch((err) => {
    console.log(err);
  })
}

Re-build the containers to manually test. Commit and push your code to GitHub once done.


With that, we need to wire up the actual code evaluation process...


React Ace Code Editor

In this lesson, we'll add some exercises to the client-side using the Ace code editor plugin...


Seed Data

Within flask-microservices-eval, start by adding a seed command to the manage.py file to populate the database with some initial data:

@manager.command
def seed_db():
    """Seeds the database."""
    db.session.add(Exercise(
        exercise_body='Define a function called sum that takes two integers as arguments and returns their sum.',
        test_code='print(sum(2, 3))',
        test_code_solution='5'
    ))
    db.session.add(Exercise(
        exercise_body='Define a function called reverse that takes a string as an argument and returns the string in reversed order.',
        test_code='print(reverse(racecar))',
        test_code_solution='racecar'
    ))
    db.session.add(Exercise(
        exercise_body='Define a function called factorial that takes a random number as an argument and then returns the factorial of that given number.',
        test_code='print(factorial(5))',
        test_code_solution='120'
    ))
    db.session.commit()

Set dev as the active machine:

$ docker-machine env dev
$ eval $(docker-machine env dev)

Set the environment variables:

$ export REACT_APP_USERS_SERVICE_URL=DOCKER_MACHINE_DEV_IP:5001
$ export TEST_URL=DOCKER_MACHINE_DEV_IP

Fire up the containers:

$ docker-compose up -d --build

Create the database:

$ docker-compose run eval-service python manage.py recreate_db

Apply the seed:

$ docker-compose run eval-service python manage.py seed_db

Exercise Component

Next, let's add a new Component to the React app. Within flask-microservices-client, add a new class-based component called Exercises.jsx to "src/components":

import React, { Component } from 'react';

class Exercises extends Component {
  constructor (props) {
    super(props)
    this.state = {}
  }
  render() {
    return (
      <div>
        <p>Hello, world!</p>
      </div>
    )
  }
}

export default Exercises

Update the main route in src/App.jsx:

<Route exact path='/' render={() => (
  <Exercises />
)} />

Add the import at the top:

import Exercises from './components/Exercises';

You should see Hello, world! in your browser.

Next, let's wire up an AJAX call to grab the exercises from the Flask eval service and render them in the component.

Add a new method to the component:

getExercises() {
  axios.get(`${process.env.REACT_APP_EVAL_SERVICE_URL}/exercises`)
  .then((res) => { this.setState({ exercises: res.data.data.exercises }); })
  .catch((err) => { console.log(err); })
}

Import axios:

import axios from 'axios';

And set the environment variable:

$ export REACT_APP_EVAL_SERVICE_URL=DOCKER_MACHINE_DEV_IP:5002

Update the environment under the web-service in the docker-compose.yml file in flask-microservices-main:

environment:
  - NODE_ENV=development
  - REACT_APP_USERS_SERVICE_URL=${REACT_APP_USERS_SERVICE_URL}
  - REACT_APP_EVAL_SERVICE_URL=${REACT_APP_EVAL_SERVICE_URL}

Add exercises to the state:

this.state = {
  exercises: []
}

Call getExercises() in the componentDidMount Lifecycle method:

componentDidMount() {
  this.getExercises();
}

Finally, update the render():

render() {
  return (
    <div>
      <h1>Exercises</h1>
      <hr/><br/>
      {this.state.exercises.length &&
        <div key={this.state.exercises[0].id}>
          <p>{this.state.exercises[0].exercise_body}</p>
        </div>
      }
    </div>
  )
}

Ace Code Editor

Ace is an embeddable code editor, which we'll use to allow end users to submit their exercise solutions directly in the browser. We'll use a pre-configured Component for Ace called React-Ace.

Install:

$ npm install --save [email protected]

Add the imports:

import AceEditor from 'react-ace';
import 'brace/mode/python';
import 'brace/theme/solarized_dark';

Update the render():

render() {
  return (
    <div>
      <h1>Exercises</h1>
      <hr/><br/>
        {this.state.exercises.length &&
          <div key={this.state.exercises[0].id}>
            <h4>{this.state.exercises[0].exercise_body}</h4>
              <AceEditor
                mode="python"
                theme="solarized_dark"
                name={(this.state.exercises[0].id).toString()}
                onLoad={this.onLoad}
                fontSize={14}
                height={'175px'}
                showPrintMargin={true}
                showGutter={true}
                highlightActiveLine={true}
                value={'# Enter your code here.'}
                style={{
                  marginBottom: '10px'
                }}
              />
            <Button bsStyle="primary" bsSize="small">Run Code</Button>
            <br/><br/>
          </div>
        }
    </div>
  )
}

Take note of how we created a new instance of the Ace Editor. Experiment with the available props if you'd like. We also added a Bootstrap-styled button with React Bootstrap. Make sure you add the import:

import { Button } from 'react-bootstrap';

Jump back to the browser. You should see something similar to:

react ace code editor

Ensure Authenticated

Next, let's only display the button if a user is logged in:

{this.props.isAuthenticated &&
  <Button bsStyle="primary" bsSize="small">Run Code</Button>
}

Pass the appropriate prop in by updating the route in src/App.jsx:

<Route exact path='/' render={() => (
  <Exercises
    isAuthenticated={this.state.isAuthenticated}
  />
)} />

Let's also add a message for those not logged in. Update the render() method in src/components/Exercises.jsx

render() {
  return (
    <div>
      <h1>Exercises</h1>
      <hr/><br/>
        {!this.props.isAuthenticated &&
          <div>
            <div className="alert alert-warning">
              <span
                className="glyphicon glyphicon-exclamation-sign"
                aria-hidden="true">
              </span>
              <span>&nbsp;Please log in to submit an exercise.</span>
            </div>
            <br/>
          </div>
        }
        {this.state.exercises.length &&
          <div key={this.state.exercises[0].id}>
            <h4>{this.state.exercises[0].exercise_body}</h4>
              <AceEditor
                mode="python"
                theme="solarized_dark"
                name={(this.state.exercises[0].id).toString()}
                onLoad={this.onLoad}
                fontSize={14}
                height={'175px'}
                showPrintMargin={true}
                showGutter={true}
                highlightActiveLine={true}
                value={'# Enter your code here.'}
                style={{
                  marginBottom: '10px'
                }}
              />
              {this.props.isAuthenticated &&
                <Button bsStyle="primary" bsSize="small">Run Code</Button>
              }
            <br/><br/>
          </div>
        }
    </div>
  )
}

Event Handler

Start by adding the value to the state:

this.state = {
  exercises: [],
  aceEditorValue: '# Enter your code here.'
}

Then, update the value property of the AceEditor:

value={this.state.aceEditorValue}

Next, add an onChange prop to the AceEditor as well:

onChange={this.onChange.bind(this)}

onChange, which is an event used to retrieve the current content of the editor, can be used to fire the following function to update the state:

onChange(value) {
  this.setState({ aceEditorValue: value });
}

Finally, add an onClick handler to the button:

{this.props.isAuthenticated &&
  <Button
    bsStyle="primary"
    bsSize="small"
    onClick={this.submitExercise.bind(this)}
  >Run Code</Button>
}

Add the submitExercise method to the component:

submitExercise(event) {
  event.preventDefault();
  const data = {
    answer: this.state.aceEditorValue
  }
  const url = 'tbd'
  axios.post(url, data)
  .then((res) => {
    console.log(res);
  })
  .catch((err) => {
    console.log(err);
  })
}

Re-build the containers to manually test. Commit and push your code to GitHub once done.


With that, we need to wire up the actual code evaluation process...