Create New Components

Bowtie is designed to not make it terribly onerous to make new components. We need to write two new classes.

  1. Create new React class
  2. Create visual or control class in Python

To walk through this process I’ll use the dropdown component, since that touches on many interesting pieces.

React Class

The dropdown component leverages a popular react dropdown component to do the heavy lifting. First we start by importing react, msgpack and the component:

import React from 'react';
import Select from 'react-select';
import 'react-select/dist/react-select.css';
var msgpack = require('msgpack-lite');

We also imported the default styling so it looks reasonable. We can do this because we’re using webpack to compile the application.

Next we will define the properties that the React class will hold. This defines how the Python code can initialize the component. We always need uuid and socket properties since they make it possible for the Python backend to communicate with the React object. This component supports multiple selection and that will be a bool property. We’ll also make an initOptions property which will let us set an initial list of options to populate the dropdown. Now that we have that defined let’s write it in code:

Dropdown.propTypes = {
    uuid: React.PropTypes.string.isRequired,
    socket: React.PropTypes.object.isRequired,
    multi: React.PropTypes.bool.isRequired,
    initOptions: React.PropTypes.array
};

Now we will create the class:

export default class Dropdown extends React.Component {
    ...
}

Everything from now we’ll write as functions in the class body. First we’ll look at the render function:

render () {
    return (
        <Select
            multi={this.props.multi}
            value={this.state.value}
            options={this.state.options}
            onChange={this.handleChange}
        />
    );
}

This instantiates the component and allows us to set configuration options for the underlying component. Note that this.state is mutable and this.prop is fixed. For example, multiple selection cannot be changed but the drop down options can be changed.

Now we’ll tell it how to communicate. We do this after the component is created in the componentDidMount function:

componentDidMount() {
    var socket = this.props.socket;
    var uuid = this.props.uuid;
    socket.on(uuid + '#get', this.getValue);
    socket.on(uuid + '#options', (data) => {
        var arr = new Uint8Array(data['data']);
        this.setState({value: null, options: msgpack.decode(arr)});
    });
}

Note that we have defined a command to be used through Python with do_options and a get function so Python can get it’s current state. Next we define the constructor which initializes the state and binds this to those handlers:

constructor(props) {
    super(props);
    this.state = {value: null};
    this.state.options = this.props.initOptions;
    this.handleChange = this.handleChange.bind(this);
    this.getValue = this.getValue.bind(this);
}

Lastly we define the handlers referenced above:

handleChange(value) {
    this.setState({value});
    this.props.socket.emit(this.props.uuid + '#change', value);
}

getValue(data, fn) {
    fn(this.state.value);
}

Python Class

Now that we have the React component defined, let’s write the Python half. We don’t need to write much here, it’s a little glue code.

First we’ll define the class:

class Dropdown(_Controller):
    _TEMPLATE = 'dropdown.jsx'
    _COMPONENT = 'Dropdown'
    _PACKAGE = 'react-select'
    _TAG = ('<Dropdown initOptions={{{options}}} '
            'multi={{{multi}}}'
            'socket={{socket}} '
            'uuid={{{uuid}}} '
            '/>')

We have defined a few component specific constants:

  • _TEMPLATE: Name of the file where the React class is defined.
  • _COMPONENT: Name of the React class (used to import the class).
  • _PACKAGE: Name of the NPM package used by the component.
  • _TAG: String used to instantiate the component.

We write the constructor who’s main responsibility is creating the string to instantiate the component in Javascript. In Bowtie, this gets assigned to the _instantiate field:

def __init__(self, options, multi=False, caption=''):
    super(Dropdown, self).__init__()

    self._instantiate = self._TAG.format(
        options=json.dumps(options),
        multi='true' if multi else 'false',
        uuid="'{}'".format(self._uuid)
    )
    self.caption = caption

Lastly we have one event (named “change”), one command (named “options”), and one getter (named “get”). We can create those by defining functions with the appropriate name and arguments, metaclasses handle the rest:

def on_change(self):
    pass

For the commands, we have the ability to just pass the data through to the React component:

def do_options(self, data):
    return data

We can also preprocess the data to present an easier interface for the programmer:

def do_options(self, labels, values):
    return [dict(label=l, value=v) for l, v in zip(labels, values)]

The main caveat here is we must ensure the data is serializable by msgpack.

For the getter we can write:

def get(self, data):
    return data

We can use this getter to do post processing, but here I just return the data as given to me from the React component.

Metaclass Parsing

A note about how commands, events, and getters are transformed into messages.

Events

Anything function that begins with on_ is an event. The message that ends up getting sent is on_{name} is {uuid}#name.

Commands

Anything function that begins with do_ is a command. The message that ends up getting sent is do_{name} is {uuid}#name.

Getters

Anything function that begins with get_ or is get is a getter. The message that ends up getting sent is get_{name} is {uuid}#get or {uuid}#get_{name}.