onejgordon/flow-dashboard

View on GitHub
src/js/components/common/FetchedList.js

Summary

Maintainability
B
6 hrs
Test Coverage
var React = require('react');
var api = require('utils/api');
import {IconButton, List, ListItem, FlatButton, TextField} from 'material-ui';
import {clone} from 'lodash';
var util = require('utils/util');
import {changeHandler} from 'utils/component-utils';


@changeHandler
export default class FetchedList extends React.Component {
  static defaultProps = {
    url: null,
    params: {},
    listProp: 'items',
    labelProp: 'label',
    subProp: null,
    listStyle: 'list', // or 'mui'
    autofetch: false,
    per_page: 30,
    fts_url: null,
    fts_prop: null,
    paging_enabled: false,
    renderItem: null // Function
  };

  constructor(props) {
    super(props);
    this.state = {
      form: {},
      items: [],
      loading: false,
      page: 0,
      more_data: true
    };
  }

  componentWillReceiveProps(nextProps) {
  }

  componentDidUpdate(prevProps, prevState) {
    var refetch = prevProps.autofetch != this.props.autofetch || prevProps.url != this.props.url;
    if (refetch) this.refresh();
  }

  componentDidMount() {
    if (this.props.autofetch) this.fetchData();
  }

  handle_more() {
    this.fetchData();
  }

  search() {
    let {form} = this.state;
    if (form.search_term && form.search_term.length > 0) {
      let term = form.search_term;
      this.setState({items: [], loading: true}, () => {
        api.get(this.props.fts_url, {term: term}, (res) => {
          this.setState({items: res[this.props.fts_prop], more_data: false, loading: false});
        });
      });
    }
  }

  fetchData() {
    if (this.props.url) {
      var params = clone(this.props.params);
      if (this.props.paging_enabled) {
        params.page = this.state.page;
        params.max = this.props.per_page;
      }
      this.setState({loading: true}, () => {
        api.get(this.props.url, params, (res) => {
          if (res.success) {
            var fetched_items = res[this.props.listProp];
            var st = {loading: false};
            if (this.props.paging_enabled) {
              st.items = this.state.items.concat(fetched_items);
              st.page = params.page + 1;
              st.more_data = fetched_items != null && fetched_items.length == this.props.per_page;
            } else {
              st.items = fetched_items;
            }
            this.setState(st)
          }
        });
      })
    }
  }

  handleItemClick(i) {
    if (this.props.onItemClick) this.props.onItemClick(i);
  }

  refresh() {
    this.setState({items: [], page: 0 }, this.fetchData);
  }

  remove_item_by_key(key, _keyProp) {
    var keyProp = _keyProp || "key";
    var items = this.state.items;
    for (var i=0; i<items.length; i++) {
      var _item = items[i];
      if (_item) {
        var keyval = _item[keyProp];
        if (keyval == key) {
          // Match
          items.splice(i, 1);
          break;
        }
      }
    }
    this.setState({items: items});
  }

  update_item_by_key(item, _keyProp, _delete, _add_to) {
    var add_to = _add_to || "top";
    var do_delete = _delete || false;
    var keyProp = _keyProp || "key";
    var items = this.state.items;
    let success = util.updateByKey(item, items, keyProp, do_delete);
    if (success) {
      this.setState({items: items})
    } else {
      if (!do_delete) {
        var new_items = this.state.items;
        if (add_to == "top") new_items.unshift(item);
        else if (add_to == "bottom") new_items.push(item);
        this.setState({items: new_items});
      }
    }
  }

  empty() {
    return this.state.items.length == 0;
  }

  render() {
    let {form} = this.state;
    let {fts_url} = this.props;
    let _search_box;
    var _items = this.state.items.map(function(item, i, arr) {
      if (this.props.renderItem != null) return this.props.renderItem(item);
      else {
        var name = item[this.props.labelProp] || "Unnamed";
        var sub = item[this.props.subProp] || null;
        if (this.props.listStyle == 'list') {
          return <li className="list-group-item" key={i}>
            <a href="javascript:void(0)" className="title" onClick={this.handleItemClick.bind(this, item)}>{ name }</a>
            </li>
        } else if (this.props.listStyle == 'mui') {
          return <ListItem key={i} primaryText={name} leftIcon={this.props.icon} secondaryText={sub} onClick={this.handleItemClick.bind(this, item)} />
        }
      }
    }, this);
    var ristatus = this.state.loading ? "loading" : "hide";
    var empty = this.empty();
    var _list;
    if (this.props.listStyle == 'list') {
      _list = (
        <ul className="list-group" hidden={empty}>
          { _items }
        </ul>
        )
    } else if (this.props.listStyle == 'mui') {
      _list = (
        <List hidden={empty}>
          { _items }
        </List>
      )
    }
    var n_fetched = this.state.items.length;
    var load_more_section = (
      <div className="clearfix vpad" hidden={n_fetched == 0}>
        <small>{"Showing " + n_fetched + "."}</small>
        <div hidden={!this.state.more_data || !this.props.paging_enabled}>
          <button onClick={this.handle_more.bind(this)} disabled={this.state.loading}
            className="btn btn-default btn-sm">
            { this.state.loading? <span><i className="fa fa-spin fa-cog"></i> Loading...</span> : 'Load More' }
          </button>
        </div>
      </div>
    );
    if (fts_url) _search_box = (
      <span>
        <TextField name="search"
          floatingLabelText="Enter search query..."
          value={form.search_term || ''}
          onChange={this.changeHandler.bind(this, 'form', 'search_term')} />
        <FlatButton label="Search" onClick={this.search.bind(this)} />
      </span>
      );
    return (
      <div>
        { _search_box }
        <IconButton iconClassName="material-icons" onClick={this.refresh.bind(this)}>refresh</IconButton>
        { _list }
        <div hidden={!empty}>
          <div className="empty">
            <i className="fa fa-warning"></i><br/>
            <span>Nothing to show</span>
          </div>
        </div>
        { load_more_section }
      </div>
    );
  }
}