Application Entry Point - app.js

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { settings } from './settings';
import { Router, Route, browserHistory } from 'react-router';
import { Dashboard, Dataset } from '../src/ReactDashboard';
let _settings;
import { omit } from 'lodash';

// get settings object from global, if available
if (typeof expressDashSettings != "undefined") {
   _settings = expressDashSettings;
} else {
  _settings = settings;
}

// Extend Dashboard with our data fetch logic
// @@TODO - this is a good case for a higher order function as mariano discussed 
class Dash extends Dashboard {
  getDashboardData(_appliedFilters) {
    let dashData = Object.assign({}, this.state.data);
    let dataKeys = Object.keys(this.props.dataResources);
    let appliedFilters = _appliedFilters || Object.assign({}, this.state.appliedFilters);
    
    dataKeys.forEach(dataKey => {
      let filters = this.getFilters(dataKey, appliedFilters);
      
      this.fetchBackend(this.props.dataResources[dataKey]).then(data => {
      
      // Note that, because of the shape of our data and the need for custom processing, that we do our filtering AFTER we fetch. In other cases (The DKAN Dash implementation for example),  we use the appropriate filters to update our API calls to return filtered data. The details of implementation are up to you, but we suggest you stick with the appliedFilters pattern, which maintains the proper top down data flow
        dashData[dataKey] = this.applyFilters(data.hits, filters);  
        if (Object.keys(dashData).length === dataKeys.length) {
          this.setState({data: dashData, isFetching: false});
        }
      }).catch(e => {
        console.log('Error fetching dashboard data', e);
      });  
    });
  }

  /**
   * Use backends to fetch data and query the result
   */
  fetchBackend(fetcher) {
    return new Promise((resolve, reject) => {
      let dataset = new Dataset(omit(fetcher.fetchData, 'type'));
      let queryObj = this.state.queryObj;
      this.setState({isFetching: true, dataset: dataset});
      dataset.fetch().then((data) => {
        this.state.dataset.query(queryObj).then(queryRes => {
          resolve(queryRes);
        })
      }).catch(e => {
          reject(e);
      });
    });
  }

  // A bit of a trivial example of how to use filters to return filtered data
  applyFilters(data, filters) {
    let _data = data.slice(0), filterVal;
    
    // perform required filtering based on filters obj 
    // In this case we need to compare the year value with the YearMonth value as strings
    if (filters && filters.length > 0) {
      filters.forEach(f => {
        if (f.field === 'YearMonth') {
          filterVal = f.value[0].value || f.value[0];
          _data = _data.filter(row => {
            return row.YearMonth.toString().indexOf(filterVal.toString()) >= 0;
          })          
        }
      }); 
    }
    
    return _data;
  }
}

// Now wrap the Dashboard so we can pass Routing info from the App
class MyDashboard extends Component {
  render() {
    let z = {};
    z.appliedFilters = (this.state) ? this.state.appliedFilters : {};
    const props = Object.assign({}, this.props, z, _settings);
    return <Dash {...props}/>
  }
}

// Wrap Dashboard component in router
class App extends Component {
  render() {
    return (
      <div id="router-container">
        <Router history={browserHistory}>
          <Route path='*' component={MyDashboard} />
          <Route path='/react-dashboard' component={MyDashboard} />
        </Router>
      </div>
    )
  }
}

// Now put it in the DOM!
document.addEventListener('DOMContentLoaded', function(event) {
    ReactDOM.render(<App/>, document.getElementById('root'));
});

React dash is a libary for building apps, not an app itself! We need to provide a data handling framework, and we do that in our app.js. Take a look at /examples/app.js and follow along with the explanations below.

Extend Dashboard

getDashboardData

We extend the dashboard to provide an implementation of the getDashbaordData method.

Note that we assume that data is segmented into dataResources. Each dataResource or dataKey contains a discreet set of data. dataResources should be defined as an array settings.js / props. Each dataKey can contain data required for fetching given data. We leave the implementation details up to you.

The dashboard is initialized with state.isFetching = false. getDashboardData should set this state paramater to false when all data has returned.

In the example we loop through each dataResource and call fetchBackend which sets the response object to state.data[dataKey]

Also, getDashboardData() should set state.isFetching = false once all data has been returned.

As per normal REACT, the setState call will trigger a re-render of the dashboard, updating components as needed.

Note that we use a custom applyFilters method that maps current application state (appliedFilters) onto our data fetching code. You can use this opportunity to apply filter paramters to an API call, to remap current dashboard data, etc.