import React, {Component} from "react"; import {AgGridReact} from "ag-grid-react"; import map from "lodash/map"; import difference from "lodash/difference"; import forEach from "lodash/forEach"; import includes from "lodash/includes"; import assign from "lodash/assign"; import ExchangeService from "../services/ExchangeService.jsx"; export default class extends Component { constructor(props) { super(props); this.state = { columnDefs: [ { field: 'symbol', headerName: 'Symbol', sort: 'asc' }, { field: 'price', headerName: 'Price', cellFormatter: this.numberFormatter, cellRenderer: 'animateShowChange', cellStyle: {'text-align': 'right'} }, { field: 'bid', headerName: 'Bid', cellFormatter: this.numberFormatter, cellRenderer: 'animateShowChange', cellStyle: {'text-align': 'right'} }, { field: 'ask', headerName: 'Ask', cellFormatter: this.numberFormatter, cellRenderer: 'animateShowChange', cellStyle: {'text-align': 'right'} } ] }; this.exchangeService = new ExchangeService(); // grid events this.onGridReady = this.onGridReady.bind(this); this.onSelectionChanged = this.onSelectionChanged.bind(this); // component events this.updateSymbol = this.updateSymbol.bind(this); } numberFormatter(params) { if (typeof params.value === 'number') { return params.value.toFixed(2); } else { return params.value; } } onGridReady(params) { this.gridApi = params.api; this.columnApi = params.columnApi; // make realistic - call in a batch let rowData = map(this.props.selectedExchange.supportedStocks, symbol => this.exchangeService.getTicker(symbol)); this.gridApi.addItems(rowData); // select the first symbol to show the chart this.gridApi.getModel().getRow(0).setSelected(true); this.gridApi.sizeColumnsToFit(); } onSelectionChanged() { let selectedNode = this.gridApi.getSelectedNodes()[0]; this.props.onSelectionChanged(selectedNode ? selectedNode.data.symbol : null); } componentWillMount() { this.props.selectedExchange.supportedStocks.forEach(symbol => { this.exchangeService.addSubscriber(this.updateSymbol, symbol); }); } componentWillUnmount() { this.exchangeService.removeSubscribers(); } componentWillReceiveProps(nextProps) { if (nextProps.selectedExchange.supportedStocks !== this.props.selectedExchange.supportedStocks) { if (!this.gridApi) { return; } this.gridApi.deselectAll(); const currentSymbols = this.props.selectedExchange.supportedStocks; const nextSymbols = nextProps.selectedExchange.supportedStocks; // Unsubscribe to current ones that will be removed const symbolsRemoved = difference(currentSymbols, nextSymbols); forEach(symbolsRemoved, symbol => { this.exchangeService.removeSubscriber(this.updateSymbol, symbol); }); // Subscribe to new ones that need to be added const symbolsAdded = difference(nextSymbols, currentSymbols); forEach(symbolsAdded, symbol => { this.exchangeService.addSubscriber(this.updateSymbol, symbol); }); // Remove ag-grid nodes as necessary const nodesToRemove = []; this.gridApi.forEachNode(node => { const {data} = node; if (includes(symbolsRemoved, data.symbol)) { nodesToRemove.push(node); } }); this.gridApi.removeItems(nodesToRemove); // Insert new ag-grid nodes as necessary let rowData = map(symbolsAdded, symbol => this.exchangeService.getTicker(symbol)); this.gridApi.addItems(rowData); // select the first symbol to show the chart this.gridApi.getModel().getRow(0).setSelected(true); } } updateSymbol(symbol) { if (!this.gridApi) { // the grid isn't ready yet return; } const updatedNodes = []; const updatedCols = []; this.gridApi.forEachNode(node => { const {data} = node; if (data.symbol === symbol.symbol) { for (const def of this.state.columnDefs) { if (data[def.field] !== symbol[def.field]) { updatedCols.push(def.field); } } assign(data, symbol); updatedNodes.push(node); } }); this.gridApi.refreshCells(updatedNodes, updatedCols); }; render() { return (