Home Reference Source

application/hocs/atable.js

import React from 'react';

import UserFlux, {UserService, UsersStore} from './../services/user-service';

/**
 * Atable HOC
 *
 * Atable will inject itself into the 'atable' property, but will also
 * override the original onChange property (preserving it in the process)
 * to catch text input and look for valid '@' keywords. Those keywords are
 * then searched for and returned the the wrapped component via the 'atableSelectedUser'
 * callback.
 *
 * <code>
 * ...
 * componentWillMount() {
 *     this.props.atable.atableSelectedUserHook((textToReplace, userObject) => {
 *         ... then replace text with user input as necessary ...
 *     });
 * }
 * ...
 * </code>
 *
 * @param  {Object} WrappedComponent React component to wrap
 * @return {Object}                  Atable component
 */
const withAtable = (WrappedComponent) => class extends React.Component {
    constructor(props) {
        super(props);

        this.isRunning = false;

        this.service = new UserFlux;

        this.state = this.service.stores.users.getState();
        this.state.keyword = false;

        this.onChange = this.onChange.bind(this);
        this.onUpdate = this.onUpdate.bind(this);

        if (this.props.onChange) {
            this.state.wrappedOnChange = this.props.onChange;
        }

        this.atable = {
        }
    }

    componentWillMount() {
        this.service.stores.users.listen(this.onUpdate);
    }

    componentWillUnmount() {
        this.service.stores.users.unlisten(this.onUpdate);
    }

    onUpdate(state) {
        this.setState(state);
    }

    onChange(e) {
        this.state.wrappedOnChange(e);
        if (e.target) {
            const matches = this.matchSymbols(e.target.value);
            if (!matches || !matches.length) {
                return;
            }

            const match = matches[0];

            this.setState({keyword: match});
            this.service.actions.users.search({'keyword': match});
        }
    }

    matchSymbols(str) {
        return str.match(/(\@(\w([-|_])?)*)/g);
    }

    render() {
        let menu = '';
        if (this.state.keyword && this.state.users.size) {
            menu = <AtableMenu results={this.state.users} />;
        }

        return (
            <div>
                <WrappedComponent {...this.props} onChange={this.onChange} atable={this.atable}/>
                {menu}
            </div>
        );
    }
}

class AtableMenu extends React.Component {
    constructor(props) {
        super(props);
        this.displayName = 'AtableMenu';
    }
       
    render() {
        let children = [];
        if (this.props.results.size) {
            children = this.props.results.map((user, i) => {
                return <AtableMenuItem user={user} key={i} />;  
            });
        }
        
        return (
            <div>{children}</div>
        );
    }
}

class AtableMenuItem extends React.Component {
    constructor(props) {
        super(props);
        this.displayName = 'AtableMenuItem';
    }
    
    render() {
        return <div>{this.props.user.get('name')}</div>;
    }
}

export {withAtable, AtableMenu, AtableMenuItem};