diff --git a/images/alberto.png b/images/alberto.png new file mode 100644 index 0000000..e35f976 Binary files /dev/null and b/images/alberto.png differ diff --git a/images/favicon.ico b/images/favicon.ico new file mode 100644 index 0000000..b338d73 Binary files /dev/null and b/images/favicon.ico differ diff --git a/images/fire.png b/images/fire.png new file mode 100644 index 0000000..2a81112 Binary files /dev/null and b/images/fire.png differ diff --git a/images/flags/ar.png b/images/flags/ar.png new file mode 100644 index 0000000..159e8fd Binary files /dev/null and b/images/flags/ar.png differ diff --git a/images/flags/br.png b/images/flags/br.png new file mode 100644 index 0000000..a549555 Binary files /dev/null and b/images/flags/br.png differ diff --git a/images/flags/co.png b/images/flags/co.png new file mode 100644 index 0000000..477177b Binary files /dev/null and b/images/flags/co.png differ diff --git a/images/flags/de.png b/images/flags/de.png new file mode 100644 index 0000000..2dbd9b0 Binary files /dev/null and b/images/flags/de.png differ diff --git a/images/flags/es.png b/images/flags/es.png new file mode 100644 index 0000000..4c31326 Binary files /dev/null and b/images/flags/es.png differ diff --git a/images/flags/fr.png b/images/flags/fr.png new file mode 100644 index 0000000..ac1412a Binary files /dev/null and b/images/flags/fr.png differ diff --git a/images/flags/gb.png b/images/flags/gb.png new file mode 100644 index 0000000..be5145b Binary files /dev/null and b/images/flags/gb.png differ diff --git a/images/flags/gr.png b/images/flags/gr.png new file mode 100644 index 0000000..f869622 Binary files /dev/null and b/images/flags/gr.png differ diff --git a/images/flags/ie.png b/images/flags/ie.png new file mode 100644 index 0000000..52e2ad4 Binary files /dev/null and b/images/flags/ie.png differ diff --git a/images/flags/is.png b/images/flags/is.png new file mode 100644 index 0000000..74c9e03 Binary files /dev/null and b/images/flags/is.png differ diff --git a/images/flags/it.png b/images/flags/it.png new file mode 100644 index 0000000..eb710fe Binary files /dev/null and b/images/flags/it.png differ diff --git a/images/flags/mt.png b/images/flags/mt.png new file mode 100644 index 0000000..aef8060 Binary files /dev/null and b/images/flags/mt.png differ diff --git a/images/flags/no.png b/images/flags/no.png new file mode 100644 index 0000000..e14f90f Binary files /dev/null and b/images/flags/no.png differ diff --git a/images/flags/pe.png b/images/flags/pe.png new file mode 100644 index 0000000..73e9465 Binary files /dev/null and b/images/flags/pe.png differ diff --git a/images/flags/pt.png b/images/flags/pt.png new file mode 100644 index 0000000..a532665 Binary files /dev/null and b/images/flags/pt.png differ diff --git a/images/flags/se.png b/images/flags/se.png new file mode 100644 index 0000000..9e12578 Binary files /dev/null and b/images/flags/se.png differ diff --git a/images/flags/uy.png b/images/flags/uy.png new file mode 100644 index 0000000..98057bd Binary files /dev/null and b/images/flags/uy.png differ diff --git a/images/flags/ve.png b/images/flags/ve.png new file mode 100644 index 0000000..a1aef2a Binary files /dev/null and b/images/flags/ve.png differ diff --git a/images/frost.png b/images/frost.png new file mode 100644 index 0000000..d4cdac7 Binary files /dev/null and b/images/frost.png differ diff --git a/images/horse.png b/images/horse.png new file mode 100644 index 0000000..660d861 Binary files /dev/null and b/images/horse.png differ diff --git a/images/niall.png b/images/niall.png new file mode 100644 index 0000000..7074010 Binary files /dev/null and b/images/niall.png differ diff --git a/images/phone.png b/images/phone.png new file mode 100644 index 0000000..fd2c11f Binary files /dev/null and b/images/phone.png differ diff --git a/images/sean.png b/images/sean.png new file mode 100644 index 0000000..ebae714 Binary files /dev/null and b/images/sean.png differ diff --git a/images/smiley-sad.png b/images/smiley-sad.png new file mode 100644 index 0000000..122f8ae Binary files /dev/null and b/images/smiley-sad.png differ diff --git a/images/smiley.png b/images/smiley.png new file mode 100644 index 0000000..b4e3290 Binary files /dev/null and b/images/smiley.png differ diff --git a/images/statue.png b/images/statue.png new file mode 100644 index 0000000..a95f57f Binary files /dev/null and b/images/statue.png differ diff --git a/images/sun.png b/images/sun.png new file mode 100644 index 0000000..ff082c8 Binary files /dev/null and b/images/sun.png differ diff --git a/package.json b/package.json index 7e7c77c..1f40354 100644 --- a/package.json +++ b/package.json @@ -29,31 +29,34 @@ }, "homepage": "http://www.ag-grid.com/", "devDependencies": { - "babel-core": "6.24.x", - "babel-loader": "6.4.x", - "babel-preset-es2015": "6.24.x", - "babel-preset-react": "6.24.x", - "babel-preset-stage-0": "6.24.x", - "babel-preset-stage-1": "6.24.x", - "css-loader": "0.23.x", - "style-loader": "0.13.x", - "webpack": "1.12.x", - "webpack-dev-server": "1.14.x" - }, - "dependencies": { - "ag-grid": "10.0.x", - "ag-grid-enterprise": "10.0.x", - "ag-grid-react": "10.0.x", - "bootstrap": "^3.3.7", - "d3": "4.9.1", - "file-loader": "^0.11.1", - "lodash": "4.17.4", - "react": "15.5.x", - "react-dom": "15.5.x", - "react-redux": "5.0.x", - "redux": "3.6.x", - "rimraf": "2.5.x" - } + "babel-core": "6.24.x", + "babel-loader": "6.4.x", + "babel-preset-es2015": "6.24.x", + "babel-preset-react": "6.24.x", + "babel-preset-stage-0": "6.24.x", + "babel-preset-stage-1": "6.24.x", + "css-loader": "0.23.x", + "mkdirp": "0.5.1", + "ncp": "2.0.0", + "rimraf": "2.5.x", + "style-loader": "0.13.x", + "webpack": "1.12.x", + "webpack-dev-server": "1.14.x" + }, + "dependencies": { + "bootstrap": "3.3.7", + "d3": "4.9.1", + "file-loader": "0.11.1", + "lodash": "4.17.4", + "react": "15.5.x", + "react-dom": "15.5.x", + "react-redux": "5.0.x", + "redux": "3.6.x", + "url-search-params-polyfill": "1.2.0", + "ag-grid": "10.1.x", + "ag-grid-enterprise": "10.1.x", + "ag-grid-react": "10.1.x" + } } diff --git a/src-full-width/index.js b/src-full-width/index.js deleted file mode 100644 index 2c44f4d..0000000 --- a/src-full-width/index.js +++ /dev/null @@ -1,18 +0,0 @@ -'use strict'; - -import ReactDOM from 'react-dom'; -import React from 'react'; -import MyApp from './myApp.jsx'; -// is there a better way of doing this? -import 'ag-grid-root/dist/styles/ag-grid.css'; -import 'ag-grid-root/dist/styles/theme-fresh.css'; - -// waiting for dom to load before booting react. we could alternatively -// put the index.js reference at the end fo the index.html, but i prefer this way. -document.addEventListener('DOMContentLoaded', ()=> { - var container = document.getElementById('myAppContainer'); - ReactDOM.render( - React.createElement(MyApp), - container - ); -}); diff --git a/src-full-width/myApp.css b/src-full-width/myApp.css deleted file mode 100644 index 5e7e6f8..0000000 --- a/src-full-width/myApp.css +++ /dev/null @@ -1,28 +0,0 @@ - -.ag-cell { - padding-top: 2px !important; - padding-bottom: 2px !important; -} - -label { - font-weight: normal !important; -} - -.div-percent-bar { - display: inline-block; - height: 20px; - position: relative; -} - -.div-percent-value { - position: absolute; - padding-left: 4px; - font-weight: bold; - font-size: 13px; -} - -.div-outer-div { - display: inline-block; - height: 100%; - width: 100%; -} diff --git a/src-full-width/myApp.jsx b/src-full-width/myApp.jsx deleted file mode 100644 index e97ac25..0000000 --- a/src-full-width/myApp.jsx +++ /dev/null @@ -1,356 +0,0 @@ -import React from "react"; -import {AgGridReact} from "ag-grid-react"; -import "./myApp.css"; -import "ag-grid-enterprise"; - - -export class DetailPanelCellRenderer extends React.Component{ - - - - render (){ - return
-
-
Name: +parentRecord.name+
-
Account: +parentRecord.account+
-
-
-
- - - - - -
-
; - } -} - - - -// take this line out if you do not want to use ag-Grid-Enterprise -// a list of names we pick from when generating data -var firstnames = ['Sophia','Emma','Olivia','Isabella','Mia','Ava','Lily','Zoe','Emily','Chloe','Layla','Madison','Madelyn','Abigail','Aubrey','Charlotte','Amelia','Ella','Kaylee','Avery','Aaliyah','Hailey','Hannah','Addison','Riley','Harper','Aria','Arianna','Mackenzie','Lila','Evelyn','Adalyn','Grace','Brooklyn','Ellie','Anna','Kaitlyn','Isabelle','Sophie','Scarlett','Natalie','Leah','Sarah','Nora','Mila','Elizabeth','Lillian','Kylie','Audrey','Lucy','Maya']; -var lastnames = ['Smith','Jones','Williams','Taylor','Brown','Davies','Evans','Wilson','Thomas','Johnson']; - -var images = ['niall','sean','alberto','statue','horse']; -// each call gets a unique id, nothing to do with the grid, just help make the sample -// data more realistic -var callIdSequence = 555; - -// method creates all the data, both the top level grid and the lower level grids -function createRowData() { - var rowData = []; - - for (var i = 0; i < 20; i++) { - var firstName = firstnames[Math.floor(Math.random()*firstnames.length)]; - var lastName = lastnames[Math.floor(Math.random()*lastnames.length)]; - - var image = images[i % images.length]; - - var totalDuration = 0; - - var callRecords = []; - // call count is random number between 20 and 120 - var callCount = Math.floor(Math.random() * 100) + 20; - for (var j = 0; j.5) ? 'In' : 'Out', - // made up number - number: '(0' + Math.floor(Math.random() * 10) + ') ' + Math.floor(Math.random() * 100000000) - }; - callRecords.push(callRecord); - totalDuration += callDuration; - } - - var record = { - name: firstName + ' ' + lastName, - account: i + 177000, - totalCalls: callCount, - image: image, - // convert from seconds to minutes - totalMinutes: totalDuration / 60, - callRecords: callRecords - }; - rowData.push(record); - } - - return rowData; -} - -var minuteCellFormatter = function (params) { - return params.value.toLocaleString() + 'm'; -}; - -var secondCellFormatter= function (params) { - return params.value.toLocaleString() + 's'; -}; - -var masterColumnDefs = [ - {headerName: 'Name', field: 'name', - // left column is going to act as group column, with the expand / contract controls - cellRenderer: 'group', - // we don't want the child count - it would be one each time anyway as each parent - // not has exactly one child node - cellRendererParams: { suppressCount: true } - }, - {headerName: 'Account', field: 'account'}, - {headerName: 'Calls', field: 'totalCalls'}, - {headerName: 'Minutes', field: 'totalMinutes', cellFormatter: minuteCellFormatter} -]; - -var detailColumnDefs = [ - {headerName: 'Call ID', field: 'callId', cellClass: 'call-record-cell'}, - {headerName: 'Direction', field: 'direction', cellClass: 'call-record-cell'}, - {headerName: 'Number', field: 'number', cellClass: 'call-record-cell'}, - {headerName: 'Duration', field: 'duration', cellClass: 'call-record-cell', cellFormatter: secondCellFormatter}, - {headerName: 'Switch', field: 'switchCode', cellClass: 'call-record-cell'} -]; - -var rowData = createRowData(); -export default class MyApp extends React.Component { - - constructor() { - super(); - - this.state = { - quickFilterText: null, - showGrid: true, - showToolPanel: false, - icons: { - columnRemoveFromGroup: '', - filter: '', - sortAscending: '', - sortDescending: '', - groupExpanded: '', - groupContracted: '', - columnGroupOpened: '', - columnGroupClosed: '' - } - }; - - // the grid options are optional, because you can provide every property - // to the grid via standard React properties. however, the react interface - // doesn't block you from using the standard JavaScript interface if you - // wish. Maybe you have the gridOptions stored as JSON on your server? If - // you do, the providing the gridOptions as a standalone object is just - // what you want! - this.gridOptions = { - columnDefs: masterColumnDefs, - rowData: rowData, - //We register the react date component that ag-grid will use to render - // this is how you listen for events using gridOptions - onModelUpdated: function () { - console.log('event onModelUpdated received'); - }, - defaultColDef : { - headerComponentParams : { - menuIcon: 'fa-bars' - } - }, - // this is a simple property - rowBuffer: 10 // no need to set this, the default is fine for almost all scenarios - }; - } - - onShowGrid(show) { - this.setState({ - showGrid: show - }); - } - - onToggleToolPanel(event) { - this.setState({showToolPanel: event.target.checked}); - } - - onGridReady(params) { - this.api = params.api; - this.columnApi = params.columnApi; - params.api.sizeColumnsToFit(); - } - - selectAll() { - this.api.selectAll(); - } - - deselectAll() { - this.api.deselectAll(); - } - - setCountryVisible(visible) { - this.columnApi.setColumnVisible('country', visible); - } - - onQuickFilterText(event) { - this.setState({quickFilterText: event.target.value}); - } - - onCellClicked(event) { - console.log('onCellClicked: ' + event.data.name + ', col ' + event.colIndex); - } - - onRowSelected(event) { - console.log('onRowSelected: ' + event.node.data.name); - } - - onRefreshData() { - var newRowData = new RowDataFactory().createRowData(); - this.setState({ - rowData: newRowData - }); - } - - invokeSkillsFilterMethod() { - var skillsFilter = this.api.getFilterInstance('skills'); - var componentInstance = skillsFilter.getFrameworkComponentInstance(); - componentInstance.helloFromSkillsFilter(); - } - - dobFilter () { - let dateFilterComponent = this.gridOptions.api.getFilterInstance('dob'); - dateFilterComponent.setFilterType('equals'); - dateFilterComponent.setDateFrom('2000-01-01'); - this.gridOptions.api.onFilterChanged(); - - } - - onIsFullWidthCell (rowNode) { - return rowNode.level === 1; - } - - onGetRowHeight (params) { - var rowIsDetailRow = params.node.level===1; - // return 100 when detail row, otherwise return 25 - return rowIsDetailRow ? 75 : 25; - } - - onGetNodeChildDetails (record) { - if (record.callRecords) { - return { - group: true, - // the key is used by the default group cellRenderer - key: record.name, - // provide ag-Grid with the children of this group - children: [record.callRecords], - // for demo, expand the third row by default - expanded: record.account === 177005 - }; - } else { - return null; - } - } - - render() { - var gridTemplate; - var bottomHeaderTemplate; - var topHeaderTemplate; - - topHeaderTemplate = ( -
-
- - - -
-
- Employees Skills and Contact Details -
-
- ); - - // showing the bottom header and grid is optional, so we put in a switch - if (this.state.showGrid) { - bottomHeaderTemplate = ( -
-
- - Grid API: - - - - - Column API: - - - -
-
-
- - - - - - Filter API: - - - -
-
-
- ); - gridTemplate = ( -
- -
- ); - } - - return
-
- {topHeaderTemplate} - {bottomHeaderTemplate} - {gridTemplate} -
-
; - } - -} \ No newline at end of file diff --git a/src-grouped/ColDefFactory.jsx b/src-grouped/ColDefFactory.jsx deleted file mode 100644 index b53d820..0000000 --- a/src-grouped/ColDefFactory.jsx +++ /dev/null @@ -1,17 +0,0 @@ -export default class ColDefFactory { - createColDefs() { - return [ - {headerName: "Athlete", field: "athlete", width: 200}, - {headerName: "Age", field: "age", width: 90}, - {headerName: "Gold", field: "gold", width: 100, aggFunc: 'sum'}, - {headerName: "Silver", field: "silver", width: 100, aggFunc: 'sum'}, - {headerName: "Bronze", field: "bronze", width: 100, aggFunc: 'sum'}, - {headerName: "Total", field: "total", width: 100, aggFunc: 'sum'}, - {headerName: "Country", field: "country", width: 120, rowGroupIndex: 0}, - {headerName: "Year", field: "year", width: 90}, - {headerName: "Date", field: "date", width: 110}, - {headerName: "Sport", field: "sport", width: 110} - ]; - } -} - diff --git a/src-grouped/index.js b/src-grouped/index.js deleted file mode 100644 index 2c44f4d..0000000 --- a/src-grouped/index.js +++ /dev/null @@ -1,18 +0,0 @@ -'use strict'; - -import ReactDOM from 'react-dom'; -import React from 'react'; -import MyApp from './myApp.jsx'; -// is there a better way of doing this? -import 'ag-grid-root/dist/styles/ag-grid.css'; -import 'ag-grid-root/dist/styles/theme-fresh.css'; - -// waiting for dom to load before booting react. we could alternatively -// put the index.js reference at the end fo the index.html, but i prefer this way. -document.addEventListener('DOMContentLoaded', ()=> { - var container = document.getElementById('myAppContainer'); - ReactDOM.render( - React.createElement(MyApp), - container - ); -}); diff --git a/src-grouped/myApp.jsx b/src-grouped/myApp.jsx deleted file mode 100644 index 5fc4c6a..0000000 --- a/src-grouped/myApp.jsx +++ /dev/null @@ -1,99 +0,0 @@ -import React from "react"; -import {AgGridReact} from "ag-grid-react"; -import ColDefFactory from "./ColDefFactory.jsx"; -import "./myApp.css"; - -// take this line out if you do not want to use ag-Grid-Enterprise -import "ag-grid-enterprise"; - - -export default class MyApp extends React.Component { - - constructor() { - super(); - - this.state = { - showToolPanel: false, - columnDefs: new ColDefFactory().createColDefs(), - rowData: null, - }; - - this.gridOptions = { - groupRowInnerRenderer: function (params) { - var FLAG_CODES = { - 'Ireland': 'ie', - 'United States': 'us', - 'Russia': 'ru', - 'Australia': 'au', - 'Canada': 'ca', - 'Norway': 'no', - 'China': 'cn', - 'Zimbabwe': 'zw', - 'Netherlands': 'nl', - 'South Korea': 'kr', - 'Croatia': 'hr', - 'France': 'fr' - }; - - var flagCode = FLAG_CODES[params.node.key]; - - var html = ''; - if (flagCode) { - html += '' - } - - html += ' COUNTRY_NAME'.replace('COUNTRY_NAME', params.node.key); - html += ' Gold: GOLD_COUNT'.replace('GOLD_COUNT', params.data.gold); - html += ' Silver: SILVER_COUNT'.replace('SILVER_COUNT', params.data.silver); - html += ' Bronze: BRONZE_COUNT'.replace('BRONZE_COUNT', params.data.bronze); - - return html; - } - }; - } - - onGridReady(params) { - this.api = params.api; - this.columnApi = params.columnApi; - - var that = this; - var httpRequest = new XMLHttpRequest(); - httpRequest.open('GET', '/olympicWinners.json'); - httpRequest.send(); - httpRequest.onreadystatechange = function() { - if (httpRequest.readyState == 4 && httpRequest.status == 200) { - var httpResult = JSON.parse(httpRequest.responseText); - that.api.setRowData(httpResult); - } - }; - - } - - render() { - var gridTemplate = ( -
- -
- ); - - return
-
- {gridTemplate} -
-
; - } - -} diff --git a/src-standard/index.js b/src-standard/index.js deleted file mode 100644 index 001f895..0000000 --- a/src-standard/index.js +++ /dev/null @@ -1,18 +0,0 @@ -'use strict'; - -import ReactDOM from 'react-dom'; -import React from 'react'; -import MyApp from './myApp.jsx'; - -import 'ag-grid-root/dist/styles/ag-grid.css'; -import 'ag-grid-root/dist/styles/theme-fresh.css'; - -// waiting for dom to load before booting react. we could alternatively -// put the index.js reference at the end fo the index.html, but i prefer this way. -document.addEventListener('DOMContentLoaded', ()=> { - var container = document.getElementById('myAppContainer'); - ReactDOM.render( - React.createElement(MyApp), - container - ); -}); diff --git a/src-standard/myApp.css b/src-standard/myApp.css deleted file mode 100644 index 5e7e6f8..0000000 --- a/src-standard/myApp.css +++ /dev/null @@ -1,28 +0,0 @@ - -.ag-cell { - padding-top: 2px !important; - padding-bottom: 2px !important; -} - -label { - font-weight: normal !important; -} - -.div-percent-bar { - display: inline-block; - height: 20px; - position: relative; -} - -.div-percent-value { - position: absolute; - padding-left: 4px; - font-weight: bold; - font-size: 13px; -} - -.div-outer-div { - display: inline-block; - height: 100%; - width: 100%; -} diff --git a/src-trader-dashboard/actions/fxDataActions.js b/src-trader-dashboard/actions/fxDataActions.js new file mode 100644 index 0000000..4023207 --- /dev/null +++ b/src-trader-dashboard/actions/fxDataActions.js @@ -0,0 +1,16 @@ +import cloneDeep from "lodash/cloneDeep"; + +export function fxDataUpdated(fxData) { + return { + type: 'FX_DATA_CHANGED', + fxData: cloneDeep(fxData) + }; +} + +export function fxTopMoversUpdated(fxTopMovers) { + return { + type: 'FX_TOP_MOVERS_CHANGED', + fxTopMovers: cloneDeep(fxTopMovers) + }; +} + diff --git a/src-trader-dashboard/components/ControlPanel.jsx b/src-trader-dashboard/components/ControlPanel.jsx index 2f01567..127c8aa 100644 --- a/src-trader-dashboard/components/ControlPanel.jsx +++ b/src-trader-dashboard/components/ControlPanel.jsx @@ -1,4 +1,4 @@ -import React, { Component } from 'react'; +import React, {Component} from "react"; export default class extends Component { constructor(props) { diff --git a/src-trader-dashboard/components/FxPanel.jsx b/src-trader-dashboard/components/FxPanel.jsx new file mode 100644 index 0000000..db3582b --- /dev/null +++ b/src-trader-dashboard/components/FxPanel.jsx @@ -0,0 +1,26 @@ +import React, {Component} from "react"; + +import FxDataService from "../services/FxDataService.jsx"; +import FxQuoteMatrix from "./FxQuoteMatrix.jsx"; +import TopMoversGrid from "./TopMoversGrid.jsx"; + +export default class extends Component { + constructor(props) { + super(props); + + this.fxDataService = new FxDataService(); + } + + render() { + return ( +
+
+ +
+
+ +
+
+ ); + } +}; diff --git a/src-trader-dashboard/components/FxQuoteMatrix.jsx b/src-trader-dashboard/components/FxQuoteMatrix.jsx index a95e8d0..74e684e 100644 --- a/src-trader-dashboard/components/FxQuoteMatrix.jsx +++ b/src-trader-dashboard/components/FxQuoteMatrix.jsx @@ -1,18 +1,17 @@ import React, {Component} from "react"; import {connect} from "react-redux"; + import {AgGridReact} from "ag-grid-react"; -import ExchangeService from "../services/ExchangeService.jsx"; +import assign from "lodash/assign"; +import uniq from "lodash/uniq"; class FxQuoteMatrix extends Component { constructor(props) { super(props); - this.exchangeService = new ExchangeService(); - this.state = { - columnDefs: this.exchangeService.getFxMatrixHeaderNames(), - // rowData: this.exchangeService.getFxMatrixSnapshot() + columnDefs: this.props.fxDataService.getFxMatrixHeaderNames() }; // grid events @@ -22,58 +21,49 @@ class FxQuoteMatrix extends Component { onGridReady(params) { this.gridApi = params.api; this.columnApi = params.columnApi; + + if (this.props.rowData) { + this.gridApi.setRowData(this.props.rowData) + } } - - // componentWillUnmount() { - // this.exchangeService.removeSubscribers(); - // } - componentWillReceiveProps(nextProps) { - console.log(props); - // if (nextProps.selectedExchange.supportedStocks !== this.props.selectedExchange.supportedStocks) { - // if (!this.gridApi) { - // return; - // } - // - // 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.updateQuote, symbol); - // }); - // - // // Subscribe to new ones that need to be added - // const symbolsAdded = difference(nextSymbols, currentSymbols); - // forEach(symbolsAdded, symbol => { - // this.exchangeService.addSubscriber(this.updateQuote, 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 - // this.gridApi.addItems(map(symbolsAdded, symbol => this.exchangeService.getTicker(symbol))); - // } + const newRowData = nextProps.rowData; + + const updatedNodes = []; + const updatedCols = []; + + for (let i = 0; i < newRowData.length; i++) { + // note that for this use case we assume the existing and new row data have the same + // row and column order + let node = this.gridApi.getModel().getRow(i); + let newRow = newRowData[i]; + + const {data} = node; + let updated = false; + for (const def of this.state.columnDefs) { + if (data[def.field] !== newRow[def.field]) { + updatedCols.push(def.field); + + updated = true; + } + } + if (updated) { + assign(data, newRow); + updatedNodes.push(node); + } + } + + this.gridApi.refreshCells(updatedNodes, uniq(updatedCols)); } render() { return ( -
{ return { - rowData: state.fxRowData + rowData: state.fxData } } )(FxQuoteMatrix); \ No newline at end of file diff --git a/src-trader-dashboard/components/PriceChangesGrid.jsx b/src-trader-dashboard/components/PriceChangesGrid.jsx index 257b2ac..9daf03f 100644 --- a/src-trader-dashboard/components/PriceChangesGrid.jsx +++ b/src-trader-dashboard/components/PriceChangesGrid.jsx @@ -1,8 +1,5 @@ import React, {Component} from "react"; -// take this line out if you do not want to use ag-Grid-Enterprise -import "ag-grid-enterprise"; - import {AgGridReact} from "ag-grid-react"; import map from "lodash/map"; @@ -44,15 +41,6 @@ export default class extends Component { cellFormatter: this.numberFormatter, cellRenderer: 'animateShowChange', cellStyle: {'text-align': 'right'} - }, - { - headerName: 'Recommendation', - field: 'recommendation', - cellEditor: 'richSelect', - cellEditorParams: { - values: ['Buy', 'Hold', 'Sell'] - }, - editable: true } ] }; @@ -61,10 +49,10 @@ export default class extends Component { // grid events this.onGridReady = this.onGridReady.bind(this); - this.onRowClicked = this.onRowClicked.bind(this); + this.onSelectionChanged = this.onSelectionChanged.bind(this); // component events - this.updateQuote = this.updateQuote.bind(this); + this.updateSymbol = this.updateSymbol.bind(this); } numberFormatter(params) { @@ -83,16 +71,20 @@ export default class extends Component { 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(); } - onRowClicked(params) { - this.props.onRowClicked(params.data.symbol); + 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.updateQuote, symbol); + this.exchangeService.addSubscriber(this.updateSymbol, symbol); }); } @@ -105,6 +97,7 @@ export default class extends Component { if (!this.gridApi) { return; } + this.gridApi.deselectAll(); const currentSymbols = this.props.selectedExchange.supportedStocks; const nextSymbols = nextProps.selectedExchange.supportedStocks; @@ -112,13 +105,13 @@ export default class extends Component { // Unsubscribe to current ones that will be removed const symbolsRemoved = difference(currentSymbols, nextSymbols); forEach(symbolsRemoved, symbol => { - this.exchangeService.removeSubscriber(this.updateQuote, 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.updateQuote, symbol); + this.exchangeService.addSubscriber(this.updateSymbol, symbol); }); // Remove ag-grid nodes as necessary @@ -132,11 +125,15 @@ export default class extends Component { this.gridApi.removeItems(nodesToRemove); // Insert new ag-grid nodes as necessary - this.gridApi.addItems(map(symbolsAdded, symbol => this.exchangeService.getTicker(symbol))); + 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); } } - updateQuote(quote) { + updateSymbol(symbol) { if (!this.gridApi) { // the grid isn't ready yet return; @@ -147,13 +144,13 @@ export default class extends Component { this.gridApi.forEachNode(node => { const {data} = node; - if (data.symbol === quote.symbol) { + if (data.symbol === symbol.symbol) { for (const def of this.state.columnDefs) { - if (data[def.field] !== quote[def.field]) { + if (data[def.field] !== symbol[def.field]) { updatedCols.push(def.field); } } - assign(data, quote); + assign(data, symbol); updatedNodes.push(node); } }); @@ -163,16 +160,17 @@ export default class extends Component { render() { return ( -
+ onSelectionChanged={this.onSelectionChanged}>
); diff --git a/src-trader-dashboard/components/StockDetailPanel.jsx b/src-trader-dashboard/components/StockDetailPanel.jsx index 76d4dda..b008e73 100644 --- a/src-trader-dashboard/components/StockDetailPanel.jsx +++ b/src-trader-dashboard/components/StockDetailPanel.jsx @@ -21,7 +21,7 @@ export default class extends Component { } } - componentWillReceiveProps(nextProps, nextState) { + componentWillReceiveProps(nextProps) { if (nextProps.selectedSymbol && nextProps.selectedSymbol !== this.props.selectedSymbol) { let stockDetail = this.exchangeService.getTickerDetail(nextProps.selectedSymbol); @@ -35,7 +35,7 @@ export default class extends Component { } } - shouldComponentUpdate(nextProps, nextState) { + shouldComponentUpdate(nextProps) { return nextProps.selectedSymbol !== this.props.selectedSymbol; } diff --git a/src-trader-dashboard/components/StockHistoricalChartPanel.jsx b/src-trader-dashboard/components/StockHistoricalChartPanel.jsx index 25befb7..ae78563 100644 --- a/src-trader-dashboard/components/StockHistoricalChartPanel.jsx +++ b/src-trader-dashboard/components/StockHistoricalChartPanel.jsx @@ -29,7 +29,7 @@ export default class extends Component { static get defaultProps() { return { - graphHeight: 320, + graphHeight: 230, graphWidth: 400 } } diff --git a/src-trader-dashboard/components/StockPanel.jsx b/src-trader-dashboard/components/StockPanel.jsx index 6f2149c..0ab7b67 100644 --- a/src-trader-dashboard/components/StockPanel.jsx +++ b/src-trader-dashboard/components/StockPanel.jsx @@ -2,7 +2,7 @@ import React, {Component} from "react"; import PriceChangesGrid from "./PriceChangesGrid.jsx"; import StockDetailPanel from "./StockDetailPanel.jsx"; -import FxQuoteMatrix from "./FxQuoteMatrix.jsx"; +import FxPanel from "./FxPanel.jsx"; export default class extends Component { constructor(props) { @@ -12,7 +12,7 @@ export default class extends Component { selectedSymbol: null }; - this.onRowClicked = this.onRowClicked.bind(this); + this.onSelectionChanged = this.onSelectionChanged.bind(this); } componentWillReceiveProps(nextProps) { @@ -28,7 +28,7 @@ export default class extends Component { nextState.selectedSymbol !== this.state.selectedSymbol; } - onRowClicked(selectedSymbol) { + onSelectionChanged(selectedSymbol) { this.setState({ selectedSymbol }) @@ -37,18 +37,18 @@ export default class extends Component { render() { return (
- {/*
*/} - {/*
*/} - {/**/} - {/*
*/} - {/*
*/} - {/**/} - {/*
*/} - {/*
*/} +
+
+ +
+
+ +
+
- +
); diff --git a/src-trader-dashboard/components/StockPriceDeltaPanel.jsx b/src-trader-dashboard/components/StockPriceDeltaPanel.jsx index a41f998..62d0bf3 100644 --- a/src-trader-dashboard/components/StockPriceDeltaPanel.jsx +++ b/src-trader-dashboard/components/StockPriceDeltaPanel.jsx @@ -18,7 +18,7 @@ export default class extends Component { this.updatePriceDelta(this.props.pricingDelta); } - componentWillReceiveProps(nextProps, nextState) { + componentWillReceiveProps(nextProps) { if (!isEqual(this.props.pricingDelta, nextProps.pricingDelta)) { this.updatePriceDelta(nextProps.pricingDelta); } @@ -73,7 +73,7 @@ export default class extends Component { let swingStyle = this.state.delta >= 0 ? positiveSwingStyle : negativeSwingStyle; - if(!this.props.pricingDelta) { + if (!this.props.pricingDelta) { return null; } else { return ( diff --git a/src-trader-dashboard/components/TopMoversGrid.jsx b/src-trader-dashboard/components/TopMoversGrid.jsx new file mode 100644 index 0000000..1e6fa96 --- /dev/null +++ b/src-trader-dashboard/components/TopMoversGrid.jsx @@ -0,0 +1,88 @@ +import React, {Component} from "react"; +import {connect} from "react-redux"; + +import {AgGridReact} from "ag-grid-react"; + +class TopMoversGrid extends Component { + constructor(props) { + super(props); + + this.state = { + columnDefs: [ + { + field: 'symbol', + headerName: 'Symbol' + }, + { + field: 'last', + headerName: 'Last', + headerClass: 'align-right', + cellRenderer: 'animateShowChange', + cellClass: 'align-right' + }, + { + field: 'net', + headerName: 'Net', + headerClass: 'align-right', + cellRenderer: 'animateShowChange', + cellClass: 'align-right' + }, + { + field: 'pct_net_change', + headerName: '% NC', + headerClass: 'align-right', + cellRenderer: 'animateShowChange', + cellClass: 'align-right', + sort: 'desc', + cellFormatter(params) { + return params.value.toFixed(2) + } + }, + ] + }; + + // grid events + this.onGridReady = this.onGridReady.bind(this); + this.getRowNodeId = this.getRowNodeId.bind(this); + } + + getRowNodeId(data) { + return data.symbol; + } + + onGridReady(params) { + this.gridApi = params.api; + this.columnApi = params.columnApi; + + this.gridApi.sizeColumnsToFit(); + } + + render() { + return ( +
+ + +
+ ); + } +} + +export default connect( + (state) => { + return { + rowData: state.fxTopMovers + } + } +)(TopMoversGrid); \ No newline at end of file diff --git a/src-trader-dashboard/components/renderers/HorizontalBarComponent.jsx b/src-trader-dashboard/components/renderers/HorizontalBarComponent.jsx new file mode 100644 index 0000000..8111b1e --- /dev/null +++ b/src-trader-dashboard/components/renderers/HorizontalBarComponent.jsx @@ -0,0 +1,42 @@ +import React, {Component} from "react"; + +export default class HorizontalBarComponent extends Component { + + render() { + let positiveChange = { + fill: "green" + }; + + let negativeChange = { + fill: "red" + }; + + let text = { + position: "absolute", + top: 0, + width: "100%", + textAlign: "right" + }; + + let pctNetChange = this.props.value; + let pctNetChangeBar = Math.min(Math.abs(pctNetChange) * 100, 100) / 2; + + let barWidth = `${pctNetChangeBar}%`; + let barStyle = pctNetChange >= 0 ? positiveChange : negativeChange; + let barX = `${pctNetChange >= 0 ? '50' : 50 - pctNetChangeBar}%`; + return ( +
+
+ + + +
+
{pctNetChange}
+
+ ) + } +} + +HorizontalBarComponent.propTypes = { + params: React.PropTypes.object +}; \ No newline at end of file diff --git a/src-trader-dashboard/index.html b/src-trader-dashboard/index.html index f69d9b6..844ccd4 100644 --- a/src-trader-dashboard/index.html +++ b/src-trader-dashboard/index.html @@ -3,13 +3,17 @@ - + + + + +
+ + \ No newline at end of file diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..c190fbd --- /dev/null +++ b/src/index.js @@ -0,0 +1,18 @@ +'use strict'; + +import React from "react"; +import {render} from "react-dom"; + +import "ag-grid-root/dist/styles/ag-grid.css"; +import "ag-grid-root/dist/styles/theme-fresh.css"; +import "../node_modules/bootstrap/dist/css/bootstrap.css"; + +import App from "./App"; + +document.addEventListener('DOMContentLoaded', () => { + render( + , + document.querySelector('#app') + ); +}); + diff --git a/src/masterDetailExample/DetailPanelComponent.css b/src/masterDetailExample/DetailPanelComponent.css new file mode 100644 index 0000000..9ba19f0 --- /dev/null +++ b/src/masterDetailExample/DetailPanelComponent.css @@ -0,0 +1,38 @@ +.ag-full-width-row .ag-react-container { + height: 100% +} + +.full-width-panel { + position: relative; + background: #fafafa; + height: 100%; + width: 100%; + padding: 5px; + box-sizing: border-box; + border-left: 2px solid grey; + border-bottom: 2px solid lightgray; + border-right: 2px solid lightgray; +} + +.call-record-cell { + text-align: right; +} + +.full-width-detail { + padding-top: 4px; +} + +.full-width-details { + float: left; + padding: 5px; + margin: 5px; + width: 150px; +} + +.full-width-grid { + margin-left: 150px; + padding-top: 25px; + box-sizing: border-box; + display: block; + height: 100%; +} diff --git a/src/masterDetailExample/DetailPanelComponent.jsx b/src/masterDetailExample/DetailPanelComponent.jsx new file mode 100644 index 0000000..7691f0a --- /dev/null +++ b/src/masterDetailExample/DetailPanelComponent.jsx @@ -0,0 +1,81 @@ +import React, {Component} from "react"; + +import {AgGridReact} from "ag-grid-react"; + +import "./DetailPanelComponent.css"; + +export default class DetailPanelComponent extends Component { + constructor(props) { + super(props); + + this.state = { + columnDefs: this.createColumnDefs(), + parentRecord: this.props.node.parent.data, + img: `images/${this.props.node.parent.data.image}.png` + }; + + this.onGridReady = this.onGridReady.bind(this); + this.onSearchTextChange = this.onSearchTextChange.bind(this); + } + + onGridReady(params) { + this.gridApi = params.api; + this.columnApi = params.columnApi; + + this.gridApi.setRowData(this.state.parentRecord.callRecords); + setTimeout(() => { + this.gridApi.sizeColumnsToFit(); + }) + } + + onSearchTextChange(newData) { + this.gridApi.setQuickFilter(newData); + } + + createColumnDefs() { + return [ + {headerName: 'Call ID', field: 'callId', cellClass: 'call-record-cell'}, + {headerName: 'Direction', field: 'direction', cellClass: 'call-record-cell'}, + {headerName: 'Number', field: 'number', cellClass: 'call-record-cell'}, + { + headerName: 'Duration', + field: 'duration', + cellClass: 'call-record-cell', + cellFormatter: this.secondCellFormatter + }, + {headerName: 'Switch', field: 'switchCode', cellClass: 'call-record-cell'}]; + + } + + secondCellFormatter(params) { + return params.value.toLocaleString() + 's'; + } + + onButtonClick() { + window.alert('Sample button pressed!!'); + } + + render() { + return ( +
+
+
+
+
Name: {this.state.parentRecord.name}
+
Account: {this.state.parentRecord.account}
+
+ + +
+ ); + } +} diff --git a/src/masterDetailExample/MasterDetailExample.jsx b/src/masterDetailExample/MasterDetailExample.jsx new file mode 100644 index 0000000..c172ee9 --- /dev/null +++ b/src/masterDetailExample/MasterDetailExample.jsx @@ -0,0 +1,152 @@ +import React, {Component} from "react"; + +import {AgGridReact} from "ag-grid-react"; + +import DetailPanelComponent from "./DetailPanelComponent"; + +export default class MasterDetailExample extends Component { + constructor(props) { + super(props); + + this.state = { + rowData: this.createRowData(), + columnDefs: this.createColumnDefs(), + }; + + this.onGridReady = this.onGridReady.bind(this); + } + + onGridReady(params) { + this.gridApi = params.api; + this.columnApi = params.columnApi; + + this.gridApi.sizeColumnsToFit(); + } + + createColumnDefs() { + return [ + { + headerName: 'Name', field: 'name', + // left column is going to act as group column, with the expand / contract controls + cellRenderer: 'group', + // we don't want the child count - it would be one each time anyway as each parent + // not has exactly one child node + cellRendererParams: {suppressCount: true} + }, + {headerName: 'Account', field: 'account'}, + {headerName: 'Calls', field: 'totalCalls'}, + {headerName: 'Minutes', field: 'totalMinutes', cellFormatter: this.minuteCellFormatter} + ]; + } + + createRowData() { + let rowData = []; + + for (let i = 0; i < 20; i++) { + let firstName = this.firstnames[Math.floor(Math.random() * this.firstnames.length)]; + let lastName = this.lastnames[Math.floor(Math.random() * this.lastnames.length)]; + + let image = this.images[i % this.images.length]; + + let totalDuration = 0; + + let callRecords = []; + // call count is random number between 20 and 120 + let callCount = Math.floor(Math.random() * 100) + 20; + for (let j = 0; j < callCount; j++) { + // duration is random number between 20 and 120 + let callDuration = Math.floor(Math.random() * 100) + 20; + let callRecord = { + callId: this.callIdSequence++, + duration: callDuration, + switchCode: 'SW' + Math.floor(Math.random() * 10), + // 50% chance of in vs out + direction: (Math.random() > .5) ? 'In' : 'Out', + // made up number + number: '(0' + Math.floor(Math.random() * 10) + ') ' + Math.floor(Math.random() * 100000000) + }; + callRecords.push(callRecord); + totalDuration += callDuration; + } + + let record = { + name: firstName + ' ' + lastName, + account: i + 177000, + totalCalls: callCount, + image: image, + // convert from seconds to minutes + totalMinutes: totalDuration / 60, + callRecords: callRecords + }; + rowData.push(record); + } + + return rowData; + } + + minuteCellFormatter(params) { + return params.value.toLocaleString() + 'm'; + }; + + isFullWidthCell(rowNode) { + return rowNode.level === 1; + } + + getRowHeight(params) { + let rowIsDetailRow = params.node.level === 1; + // return 100 when detail row, otherwise return 25 + return rowIsDetailRow ? 200 : 25; + } + + getNodeChildDetails(record) { + if (record.callRecords) { + return { + group: true, + // the key is used by the default group cellRenderer + key: record.name, + // provide ag-Grid with the children of this group + children: [record.callRecords], + // for demo, expand the third row by default + expanded: record.account === 177002 + }; + } else { + return null; + } + } + + render() { + return ( +
+

Master-Detail Example

+ + +
+ ); + } + + // a list of names we pick from when generating data + firstnames = ['Sophia', 'Emma', 'Olivia', 'Isabella', 'Mia', 'Ava', 'Lily', 'Zoe', 'Emily', 'Chloe', 'Layla', 'Madison', 'Madelyn', 'Abigail', 'Aubrey', 'Charlotte', 'Amelia', 'Ella', 'Kaylee', 'Avery', 'Aaliyah', 'Hailey', 'Hannah', 'Addison', 'Riley', 'Harper', 'Aria', 'Arianna', 'Mackenzie', 'Lila', 'Evelyn', 'Adalyn', 'Grace', 'Brooklyn', 'Ellie', 'Anna', 'Kaitlyn', 'Isabelle', 'Sophie', 'Scarlett', 'Natalie', 'Leah', 'Sarah', 'Nora', 'Mila', 'Elizabeth', 'Lillian', 'Kylie', 'Audrey', 'Lucy', 'Maya']; + lastnames = ['Smith', 'Jones', 'Williams', 'Taylor', 'Brown', 'Davies', 'Evans', 'Wilson', 'Thomas', 'Johnson']; + + images = ['niall', 'sean', 'alberto', 'statue', 'horse']; + + // each call gets a unique id, nothing to do with the grid, just help make the sample + // data more realistic + callIdSequence = 555; +}; diff --git a/src/richComponentExample/ClickableRenderer.jsx b/src/richComponentExample/ClickableRenderer.jsx new file mode 100644 index 0000000..baf6a46 --- /dev/null +++ b/src/richComponentExample/ClickableRenderer.jsx @@ -0,0 +1,26 @@ +import React, {Component} from "react"; + +export default class ClickableRenderer extends Component { + constructor(props) { + super(props); + + this.state = { + cell: { + row: this.props.value, + col: this.props.colDef.headerName + } + }; + + this.clicked = this.clicked.bind(this); + } + + clicked() { + console.log("Child Cell Clicked: " + JSON.stringify(this.state.cell)); + } + + render() { + return ( + + ); + } +} \ No newline at end of file diff --git a/src/richComponentExample/RatioRenderer.jsx b/src/richComponentExample/RatioRenderer.jsx new file mode 100644 index 0000000..3be8a7c --- /dev/null +++ b/src/richComponentExample/RatioRenderer.jsx @@ -0,0 +1,41 @@ +import React, {Component} from "react"; + +export default class RatioRenderer extends Component { + constructor(props) { + super(props); + } + + render() { + let agRatioStyle = { + display: "block", + overflow: "hidden", + border: "1px solid #ccc", + borderRadius: "6px", + background: "#fff", + height: 20 + }; + + let svg = { + width: "100%", + height: "100%", + pointerEvents: "none" + }; + + let topBar = { + fill: "#ff9933" + }; + + let bottomBar = { + fill: "#6699ff" + }; + + return ( + + + + + + + ); + } +} \ No newline at end of file diff --git a/src/richComponentExample/RichComponentsExample.jsx b/src/richComponentExample/RichComponentsExample.jsx new file mode 100644 index 0000000..715b113 --- /dev/null +++ b/src/richComponentExample/RichComponentsExample.jsx @@ -0,0 +1,78 @@ +import React, {Component} from "react"; + +import {AgGridReact} from "ag-grid-react"; +import RatioRenderer from "./RatioRenderer"; +import ClickableRenderer from "./ClickableRenderer"; + +export default class RichComponentsExample extends Component { + constructor(props) { + super(props); + + this.state = { + rowData: this.createRowData(), + columnDefs: this.createColumnDefs() + }; + + this.onGridReady = this.onGridReady.bind(this); + } + + onGridReady(params) { + this.gridApi = params.api; + this.columnApi = params.columnApi; + + this.gridApi.sizeColumnsToFit(); + } + + createColumnDefs() { + return [ + {headerName: "Name", field: "name", width: 200}, + { + headerName: "Ratio Component", + field: "ratios", + cellRendererFramework: RatioRenderer, + width: 350 + }, + { + headerName: "Clickable Component", + field: "name", + cellRendererFramework: ClickableRenderer, + width: 250 + } + ]; } + + createRowData() { + return [ + {name: 'Homer Simpson', ratios: {top: 0.25, bottom: 0.75}}, + {name: 'Marge Simpson', ratios: {top: 0.67, bottom: 0.39}}, + {name: 'Bart Simpson', ratios: {top: 0.82, bottom: 0.47}}, + {name: 'Lisa Simpson', ratios: {top: 0.39, bottom: 1}}, + {name: 'Barney', ratios: {top: 0.22, bottom: 0.78}}, + {name: 'Sideshow Bob', ratios: {top: 0.13, bottom: 0.87}}, + {name: 'Ned Flanders', ratios: {top: 0.49, bottom: 0.51}}, + {name: 'Milhouse', ratios: {top: 0.69, bottom: 0.31}}, + {name: 'Apu', ratios: {top: 0.89, bottom: 0.11}}, + {name: 'Moe', ratios: {top: 0.64, bottom: 0.36}}, + {name: 'Smithers', ratios: {top: 0.09, bottom: 0.91}}, + {name: 'Edna Krabappel', ratios: {top: 0.39, bottom: 0.61}}, + {name: 'Krusty', ratios: {top: 0.74, bottom: 0.26}} + ]; + } + + render() { + return ( +
+

Dynamic React Components - Richer Example

+ + +
+ ); + } +}; diff --git a/src-standard/ColDefFactory.jsx b/src/richGridExample/ColDefFactory.jsx similarity index 100% rename from src-standard/ColDefFactory.jsx rename to src/richGridExample/ColDefFactory.jsx diff --git a/src-standard/MyReactDateComponent.jsx b/src/richGridExample/MyReactDateComponent.jsx similarity index 100% rename from src-standard/MyReactDateComponent.jsx rename to src/richGridExample/MyReactDateComponent.jsx diff --git a/src-standard/MyReactHeaderComponent.jsx b/src/richGridExample/MyReactHeaderComponent.jsx similarity index 100% rename from src-standard/MyReactHeaderComponent.jsx rename to src/richGridExample/MyReactHeaderComponent.jsx diff --git a/src-standard/MyReactHeaderGroupComponent.jsx b/src/richGridExample/MyReactHeaderGroupComponent.jsx similarity index 100% rename from src-standard/MyReactHeaderGroupComponent.jsx rename to src/richGridExample/MyReactHeaderGroupComponent.jsx diff --git a/src-standard/NameCellEditor.jsx b/src/richGridExample/NameCellEditor.jsx similarity index 100% rename from src-standard/NameCellEditor.jsx rename to src/richGridExample/NameCellEditor.jsx diff --git a/src-standard/ProficiencyCellRenderer.jsx b/src/richGridExample/ProficiencyCellRenderer.jsx similarity index 100% rename from src-standard/ProficiencyCellRenderer.jsx rename to src/richGridExample/ProficiencyCellRenderer.jsx diff --git a/src-standard/ProficiencyFilter.jsx b/src/richGridExample/ProficiencyFilter.jsx similarity index 100% rename from src-standard/ProficiencyFilter.jsx rename to src/richGridExample/ProficiencyFilter.jsx diff --git a/src-standard/RefData.js b/src/richGridExample/RefData.js similarity index 100% rename from src-standard/RefData.js rename to src/richGridExample/RefData.js diff --git a/src-grouped/myApp.css b/src/richGridExample/RichGridExample.css similarity index 90% rename from src-grouped/myApp.css rename to src/richGridExample/RichGridExample.css index 5e7e6f8..0cc69a8 100644 --- a/src-grouped/myApp.css +++ b/src/richGridExample/RichGridExample.css @@ -1,3 +1,6 @@ +.ag-react-container { + display: block; +} .ag-cell { padding-top: 2px !important; diff --git a/src-standard/myApp.jsx b/src/richGridExample/RichGridExample.jsx similarity index 97% rename from src-standard/myApp.jsx rename to src/richGridExample/RichGridExample.jsx index 2d06bb7..b028e0e 100644 --- a/src-standard/myApp.jsx +++ b/src/richGridExample/RichGridExample.jsx @@ -1,14 +1,16 @@ -import React from "react"; +import React, {Component} from "react"; import {AgGridReact} from "ag-grid-react"; import RowDataFactory from "./RowDataFactory"; import ColDefFactory from "./ColDefFactory.jsx"; import MyReactDateComponent from "./MyReactDateComponent.jsx"; import MyReactHeaderComponent from "./MyReactHeaderComponent.jsx"; -import "./myApp.css"; + +import "./RichGridExample.css"; + // take this line out if you do not want to use ag-Grid-Enterprise import "ag-grid-enterprise"; -export default class MyApp extends React.Component { +export default class RichGridExample extends Component { constructor() { super(); @@ -177,7 +179,7 @@ export default class MyApp extends React.Component {
); gridTemplate = ( -
+
+ return
{topHeaderTemplate} {bottomHeaderTemplate} diff --git a/src-standard/RowDataFactory.js b/src/richGridExample/RowDataFactory.js similarity index 100% rename from src-standard/RowDataFactory.js rename to src/richGridExample/RowDataFactory.js diff --git a/src-standard/SkillsCellRenderer.jsx b/src/richGridExample/SkillsCellRenderer.jsx similarity index 100% rename from src-standard/SkillsCellRenderer.jsx rename to src/richGridExample/SkillsCellRenderer.jsx diff --git a/src-standard/SkillsFilter.jsx b/src/richGridExample/SkillsFilter.jsx similarity index 100% rename from src-standard/SkillsFilter.jsx rename to src/richGridExample/SkillsFilter.jsx diff --git a/src/simpleReduxExample/GridComponent.jsx b/src/simpleReduxExample/GridComponent.jsx new file mode 100644 index 0000000..f674d45 --- /dev/null +++ b/src/simpleReduxExample/GridComponent.jsx @@ -0,0 +1,106 @@ +import React, {Component} from "react"; + +import {AgGridReact} from "ag-grid-react"; +import {connect} from "react-redux"; +// take this line out if you do not want to use ag-Grid-Enterprise +import "ag-grid-enterprise"; + +import {updateRowSelection} from "./gridDataActions"; + +/* + * This component serves to display the row data (provided by redux) + */ +class GridComponent extends Component { + constructor(props) { + super(props); + + this.state = { + columnDefs: [ + {headerName: 'Symbol', field: 'symbol'}, + {headerName: 'Price', field: 'price'}, + {headerName: 'Group', field: 'group'} + ] + }; + + this.onGridReady = this.onGridReady.bind(this); + this.onSelectionChanged = this.onSelectionChanged.bind(this); + this.setGroupingEnabled = this.setGroupingEnabled.bind(this); + } + + onGridReady(params) { + this.gridApi = params.api; + this.columnApi = params.columnApi; + + this.gridApi.sizeColumnsToFit(); + + // set the initial group state + this.setGroupingEnabled(false); + } + + // on selection publish selected row ids + onSelectionChanged() { + let selectedRowNodes = this.gridApi.getSelectedNodes(); + let selectedIds = selectedRowNodes.map((rowNode) => rowNode.id); + + this.props.dispatch(updateRowSelection(selectedIds)); + } + + setGroupingEnabled(enabled) { + if (enabled) { + this.columnApi.addRowGroupColumn('group'); + this.columnApi.setColumnVisible('group', false); + this.columnApi.setColumnVisible('symbol', false); + } else { + this.columnApi.removeRowGroupColumn('group'); + this.columnApi.setColumnVisible('group', true); + this.columnApi.setColumnVisible('symbol', true); + } + } + + // row data will be provided via redux on this.props.rowData + // we bind to this and using "deltaRowDataMode" the grid will only re-render rows that have changed + // this requires each row to have a uniquely identifying property - in this case the row data "symbol" (see getRowNodeId) + render() { + return ( +
+ data.symbol} + + // events + onGridReady={this.onGridReady} + onSelectionChanged={this.onSelectionChanged}> + +
+ ) + } +} + +// pull off row data changes +export default connect( + (state) => { + return { + rowData: state.rowData + } + }, + null, + null, + {withRef: true} +)(GridComponent); \ No newline at end of file diff --git a/src/simpleReduxExample/HeaderComponent.jsx b/src/simpleReduxExample/HeaderComponent.jsx new file mode 100644 index 0000000..568a483 --- /dev/null +++ b/src/simpleReduxExample/HeaderComponent.jsx @@ -0,0 +1,174 @@ +import React, {Component} from "react"; +import {connect} from "react-redux"; +// take this line out if you do not want to use ag-Grid-Enterprise +import "ag-grid-enterprise"; + +import {updateRowData} from "./gridDataActions"; + +/* + * This component serves both to host the demo controls, which in turn will drive row data state changes + */ +class HeaderComponent extends Component { + constructor(props) { + super(props); + + this.addFiveItems = this.addFiveItems.bind(this); + this.removeSelected = this.removeSelected.bind(this); + this.updatePrices = this.updatePrices.bind(this); + this.setGroupingEnabled = this.setGroupingEnabled.bind(this); + this.setItemVisible = this.setItemVisible.bind(this); + this.setSelectedToGroup = this.setSelectedToGroup.bind(this); + } + + componentDidMount() { + // provide the initial data to the store (which in turn will populate the grid) + this.props.dispatch(updateRowData(this.createRowData())); + } + + // add five new items to the row data and publish the change + addFiveItems() { + let newRowData = this.props.rowData.slice(); + for (let i = 0; i < 5; i++) { + let newItem = this.createItem(); + newRowData.push(newItem); + } + + this.props.dispatch(updateRowData(newRowData)); + } + + // remove selected rows from the data set and publish the change + removeSelected() { + let newRowData = this.props.rowData.filter((dataItem) => (this.props.rowSelection.indexOf(dataItem.symbol) < 0)); + this.props.dispatch(updateRowData(newRowData)); + } + + // group data based on selection and publish the change + setSelectedToGroup(newGroup) { + let selectedIds = this.props.rowSelection; + let newRowData = this.props.rowData.map((dataItem) => { + let itemSelected = selectedIds.indexOf(dataItem.symbol) >= 0; + if (itemSelected) { + return { + // symbol and price stay the same + symbol: dataItem.symbol, + price: dataItem.price, + // group gets the group + group: newGroup + }; + } else { + return dataItem; + } + }); + + this.props.dispatch(updateRowData(newRowData)); + } + + // randomly update prices in the row data and publish the change + updatePrices() { + let newRowData = []; + this.props.rowData.forEach(function (item) { + newRowData.push({ + // use same symbol as last time, this is the unique id + symbol: item.symbol, + // group also stays the same + group: item.group, + // add random price + price: Math.floor(Math.random() * 100) + }); + }); + + this.props.dispatch(updateRowData(newRowData)); + } + + + setGroupingEnabled(enabled) { + // let the parent (and the grid in turn) know about the grouping state change + this.props.setGroupingEnabled(enabled); + + // toggle the grouping buttons visibility + this.setItemVisible('groupingOn', !enabled); + this.setItemVisible('groupingOff', enabled); + } + + setItemVisible(id, visible) { + let element = document.querySelector('#' + id); + element.style.display = visible ? null : 'none'; + } + + render() { + return ( +
+ + + + + + + + + + Group Selected: + + + + +
+ ) + } + + // the following methods are for creating dummy row data + createRowData() { + let rowData = []; + + for (let i = 0; i < 14; i++) { + let newItem = this.createItem(); + rowData.push(newItem); + } + + return rowData; + } + + createItem() { + return { + group: 'A', + symbol: this.createUniqueRandomSymbol(), + price: Math.floor(Math.random() * 100) + }; + } + + // creates a unique symbol, eg 'ADG' or 'ZJD' + createUniqueRandomSymbol() { + let symbol; + let possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + + let isUnique = false; + while (!isUnique) { + symbol = ''; + // create symbol + for (let i = 0; i < 3; i++) { + symbol += possible.charAt(Math.floor(Math.random() * possible.length)); + } + // check uniqueness + isUnique = true; + this.props.rowData.forEach(function (oldItem) { + if (oldItem.symbol === symbol) { + isUnique = false; + } + }); + } + + return symbol; + } +} + +// pull off row data and selected row changes +export default connect( + (state) => { + return { + rowData: state.rowData, + rowSelection: state.rowSelection + } + } +)(HeaderComponent); \ No newline at end of file diff --git a/src/simpleReduxExample/SimpleReduxExample.jsx b/src/simpleReduxExample/SimpleReduxExample.jsx new file mode 100644 index 0000000..4c2c928 --- /dev/null +++ b/src/simpleReduxExample/SimpleReduxExample.jsx @@ -0,0 +1,40 @@ +import React, {Component} from "react"; +import {Provider} from "react-redux"; +import {createStore} from "redux"; +// take this line out if you do not want to use ag-Grid-Enterprise +import "ag-grid-enterprise"; + +import HeaderComponent from "./HeaderComponent"; +import GridComponent from "./GridComponent"; + +import gridData from "./gridDataReducer"; + +let store = createStore(gridData); + +/* + * This component serves as a container for both the header and grid components. It's primarily here to act as a container + * for the redux Provider + */ +export default class SimpleReduxExample extends Component { + constructor(props) { + super(props); + + this.setGroupingEnabled = this.setGroupingEnabled.bind(this); + } + + setGroupingEnabled(enabled) { + this.grid.setGroupingEnabled(enabled); + } + + render() { + return ( + +
+

Simple Redux Example using ag-Grid's deltaRowMode

+ + { this.grid = grid ? grid.getWrappedInstance() : null }} /> +
+
+ ) + } +}; diff --git a/src/simpleReduxExample/gridDataActions.jsx b/src/simpleReduxExample/gridDataActions.jsx new file mode 100644 index 0000000..8768140 --- /dev/null +++ b/src/simpleReduxExample/gridDataActions.jsx @@ -0,0 +1,13 @@ +export function updateRowData(rowData) { + return { + type: 'ROW_DATA_CHANGED', + rowData + } +} + +export function updateRowSelection(rowSelection) { + return { + type: 'ROW_SELECTION_CHANGED', + rowSelection + } +} \ No newline at end of file diff --git a/src/simpleReduxExample/gridDataReducer.jsx b/src/simpleReduxExample/gridDataReducer.jsx new file mode 100644 index 0000000..6e7b867 --- /dev/null +++ b/src/simpleReduxExample/gridDataReducer.jsx @@ -0,0 +1,16 @@ +export default (state = {rowData: [], rowSelection: []}, action) => { + switch (action.type) { + case 'ROW_DATA_CHANGED': + return { + ...state, + rowData: action.rowData, + }; + case 'ROW_SELECTION_CHANGED': + return { + ...state, + rowSelection: action.rowSelection, + }; + default: + return state; + } +}; \ No newline at end of file diff --git a/webpack.config.examples.js b/webpack.config.examples.js new file mode 100644 index 0000000..4abeee4 --- /dev/null +++ b/webpack.config.examples.js @@ -0,0 +1,42 @@ +const path = require('path'); + +const SRC_DIR = path.resolve(__dirname, 'src'); + +module.exports = { + entry: SRC_DIR + "/index.js", + output: { + path: __dirname, + filename: "dist/react-examples.js" + }, + module: { + loaders: [ + { + test: /\.css$/, + loader: "style!css" + }, + { + test: /\.js$|\.jsx$/, + include: SRC_DIR, + loader: 'babel-loader', + query: { + presets: ['react', 'es2015', 'stage-0'] + } + }, + { + test: /\.(png|jpe?g|gif|svg|woff|woff2|ttf|eot|ico)$/, + loader: 'file?name=[path]/[name].[ext]' + } + ] + }, + resolve: { + alias: { + "ag-grid-root" : __dirname + "/node_modules/ag-grid" + }, + extensions: ['', '.js', '.jsx'] + }, + devServer: { + historyApiFallback: true, + contentBase: './', + hot: true + } +}; \ No newline at end of file diff --git a/webpack.config.full-width.js b/webpack.config.full-width.js deleted file mode 100644 index 78a1165..0000000 --- a/webpack.config.full-width.js +++ /dev/null @@ -1,27 +0,0 @@ -module.exports = { - entry: "./src-full-width/index.js", - output: { - path: __dirname, - filename: "dist/bundle.js" - }, - module: { - loaders: [ - { - test: /\.css$/, - loader: "style!css" - }, - { - test: /\.js$|\.jsx$/, - loader: 'babel-loader', - query: { - presets: ['react', 'es2015'] - } - } - ] - }, - resolve: { - alias: { - "ag-grid-root" : __dirname + "/node_modules/ag-grid" - } - } -}; \ No newline at end of file diff --git a/webpack.config.grouped.js b/webpack.config.grouped.js deleted file mode 100644 index 5044346..0000000 --- a/webpack.config.grouped.js +++ /dev/null @@ -1,27 +0,0 @@ -module.exports = { - entry: "./src-grouped/index.js", - output: { - path: __dirname, - filename: "dist/bundle.js" - }, - module: { - loaders: [ - { - test: /\.css$/, - loader: "style!css" - }, - { - test: /\.js$|\.jsx$/, - loader: 'babel-loader', - query: { - presets: ['react', 'es2015'] - } - } - ] - }, - resolve: { - alias: { - "ag-grid-root" : __dirname + "/node_modules/ag-grid" - } - } -}; \ No newline at end of file diff --git a/webpack.config.standard.js b/webpack.config.standard.js deleted file mode 100644 index 6f691d7..0000000 --- a/webpack.config.standard.js +++ /dev/null @@ -1,27 +0,0 @@ -module.exports = { - entry: "./src-standard/index.js", - output: { - path: __dirname, - filename: "dist/bundle.js" - }, - module: { - loaders: [ - { - test: /\.css$/, - loader: "style!css" - }, - { - test: /\.js$|\.jsx$/, - loader: 'babel-loader', - query: { - presets: ['react', 'es2015'] - } - } - ] - }, - resolve: { - alias: { - "ag-grid-root" : __dirname + "/node_modules/ag-grid" - } - } -}; \ No newline at end of file diff --git a/webpack.config.trader.js b/webpack.config.trader.js index 03cf79e..46b3aae 100644 --- a/webpack.config.trader.js +++ b/webpack.config.trader.js @@ -1,4 +1,3 @@ -const webpack = require('webpack'); const path = require('path'); const SRC_DIR = path.resolve(__dirname, 'src-trader-dashboard'); @@ -7,7 +6,7 @@ module.exports = { entry: SRC_DIR + "/index.js", output: { path: __dirname, - filename: "dist/bundle.js" + filename: "dist/react-trader.js" }, module: { loaders: [ @@ -20,7 +19,7 @@ module.exports = { include: SRC_DIR, loader: 'babel-loader', query: { - presets: ['react', 'es2015'] + presets: ['react', 'es2015', 'stage-0'] } } ] @@ -28,6 +27,7 @@ module.exports = { resolve: { alias: { "ag-grid-root" : __dirname + "/node_modules/ag-grid" - } + }, + extensions: ['', '.js', '.jsx'] } }; \ No newline at end of file