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};