From 3ede755cb3c3c79a49b7a348c363211a45e1b6b0 Mon Sep 17 00:00:00 2001 From: Ceolter Date: Tue, 26 Jan 2016 18:55:42 +0000 Subject: [PATCH] iteration --- README.md | 22 ++ index.html | 16 +- package.json | 7 +- src/AgGridReact.jsx | 98 ------- src/ColDefFactory.jsx | 208 ++++++++++++++ src/ComponentUtil.js | 154 ---------- src/ProficiencyCellRenderer.jsx | 34 +++ src/ProficiencyFilter.jsx | 82 ++++++ src/RowDataFactory.js | 44 +++ src/SkillsCellRenderer.jsx | 9 +- src/SkillsFilter.jsx | 98 +++++++ src/myApp.jsx | 490 +++++++++++--------------------- webpack.config.js | 1 + 13 files changed, 668 insertions(+), 595 deletions(-) create mode 100644 README.md delete mode 100644 src/AgGridReact.jsx create mode 100644 src/ColDefFactory.jsx delete mode 100644 src/ComponentUtil.js create mode 100644 src/ProficiencyCellRenderer.jsx create mode 100644 src/ProficiencyFilter.jsx create mode 100644 src/RowDataFactory.js create mode 100644 src/SkillsFilter.jsx diff --git a/README.md b/README.md new file mode 100644 index 0000000..d7294cd --- /dev/null +++ b/README.md @@ -0,0 +1,22 @@ + +ag-Grid React Example +============== + +Example of running ag-Grid inside React application. + +See the [www.ag-grid.com](http://www.ag-grid.com). + + +Building +============== + +To build: +- `npm install` +- `npm install webpack -g` +- `webpack` + +Contributing +============== + +If you notice anything wrong with this example or have ideas on how it can be improved, +please raise a Github task. diff --git a/index.html b/index.html index c27a671..a8ef2f9 100644 --- a/index.html +++ b/index.html @@ -1,12 +1,14 @@ - - - - - -
+ + + + + + - + +
+ \ No newline at end of file diff --git a/package.json b/package.json index 06df2ec..d5b49bc 100644 --- a/package.json +++ b/package.json @@ -24,8 +24,6 @@ "homepage": "http://www.ag-grid.com/", "devDependencies": { "webpack": "1.12.11", - "react": "0.14.6", - "react-dom": "0.14.6", "css-loader": "0.23.1", "style-loader": "0.13.0", "babel-loader": "6.2.1", @@ -33,6 +31,9 @@ "babel-preset-es2015": "6.3.13" }, "dependencies": { - "ag-grid": "latest" + "react": "0.14.6", + "react-dom": "0.14.6", + "ag-grid": "latest", + "ag-grid-react-component": "latest" } } diff --git a/src/AgGridReact.jsx b/src/AgGridReact.jsx deleted file mode 100644 index 8bc715d..0000000 --- a/src/AgGridReact.jsx +++ /dev/null @@ -1,98 +0,0 @@ -import ReactDOM from 'react-dom'; -import React from 'react'; -import AgGrid from 'ag-grid'; -import ComponentUtil from './ComponentUtil'; - -export default class AgGridReact extends React.Component { - - componentDidMount() { - var domNode = ReactDOM.findDOMNode(this); - this.gridOptions = ComponentUtil.copyAttributesToGridOptions(this.props.gridOptions, this.props); - AgGrid(domNode, this.gridOptions); - - this.api = this.gridOptions.api; - this.columnApi = this.gridOptions.columnApi; - this.api.addGlobalListener(this.globalEventListener.bind(this)); - } - - // this method is duplicated, taken from gridOptionsWrapper - getCallbackForEvent(eventName) { - if (!eventName || eventName.length < 2) { - return eventName; - } else { - return 'on' + eventName[0].toUpperCase() + eventName.substr(1); - } - } - - // duplicated, taken from gridOptionsWrapper - globalEventListener(eventName, event) { - var callbackMethodName = this.getCallbackForEvent(eventName); - var callbackFromProps = this.props[callbackMethodName]; - if (callbackFromProps) { - callbackFromProps(event); - } - } - - shouldComponentUpdate () { - // we want full control of the dom, as ag-Grid doesn't use React internally, - // so for performance reasons we tell React we don't need render called after - // property changes. - return false; - } - - componentWillReceiveProps(nextProps) { - // keeping consistent with web components, put changing - // values in currentValue and previousValue pairs and - // not include items that have not changed. - var changes = {}; - ComponentUtil.ALL_PROPERTIES.forEach( (propKey)=> { - if (this.props[propKey]!==nextProps[propKey]) { - changes[propKey] = { - previousValue: this.props[propKey], - currentValue: nextProps[propKey] - }; - } - }); - ComponentUtil.processOnChange(changes, this.gridOptions, this.api, this.columnApi); - } - - componentWillUnmount() { - this.api.destroy(); - } - - render() { - return
; - } -} - -AgGridReact.propTypes = { - style: React.PropTypes.object, - className: React.PropTypes.string, - gridOptions: React.PropTypes.object - - // we should iterate through all the properties and add them here - //onRowSelected: React.PropTypes.func, - //showToolPanel: React.PropTypes.bool -}; - -ComponentUtil.SIMPLE_BOOLEAN_PROPERTIES - .concat(ComponentUtil.WITH_IMPACT_BOOLEAN_PROPERTIES) - .forEach( (propKey)=> { - AgGridReact.propTypes[propKey] = React.PropTypes.bool; - }); - -//ComponentUtil.SIMPLE_PROPERTIES -// .concat(ComponentUtil.WITH_IMPACT_STRING_PROPERTIES) -// .forEach( (propKey)=> { -// AgGridReact.propTypes[propKey] = React.PropTypes.bool; -// }); - - //.concat(ComponentUtil.) - //.concat(ComponentUtil.SIMPLE_NUMBER_PROPERTIES) - //.concat(ComponentUtil.WITH_IMPACT_OTHER_PROPERTIES) - //.concat(ComponentUtil.WITH_IMPACT_NUMBER_PROPERTIES) - //.concat(ComponentUtil.CALLBACKS), - - -var i = new AgGridReact(); -console.log(i); diff --git a/src/ColDefFactory.jsx b/src/ColDefFactory.jsx new file mode 100644 index 0000000..41d049a --- /dev/null +++ b/src/ColDefFactory.jsx @@ -0,0 +1,208 @@ +import SkillsCellRenderer from './SkillsCellRenderer.jsx'; +import ProficiencyCellRenderer from './ProficiencyCellRenderer.jsx'; +import RefData from './RefData'; +import ReactDOM from 'react-dom'; +import React from 'react'; +import AgGrid from 'ag-grid-react-component'; +import {reactCellRendererFactory} from 'ag-grid-react-component'; +import {reactFilterFactory} from 'ag-grid-react-component'; +import SkillsFilter from './SkillsFilter.jsx'; +import ProficiencyFilter from './ProficiencyFilter.jsx'; + +export default class ColDefFactory { + + createColDefs() { + + var columnDefs = [ + {headerName: '', width: 30, checkboxSelection: true, suppressSorting: true, + suppressMenu: true, pinned: true}, + { + headerName: 'Employee', + children: [ + {headerName: "Name", field: "name", + width: 150, pinned: true}, + {headerName: "Country", field: "country", width: 150, + cellRenderer: countryCellRenderer, pinned: true, + filterParams: {cellRenderer: countryCellRenderer, cellHeight: 20}}, + ] + }, + { + headerName: 'IT Skills', + children: [ + {headerName: "Skills", width: 125, suppressSorting: true, field: 'skills', + cellRenderer: reactCellRendererFactory(SkillsCellRenderer), + filter: reactFilterFactory(SkillsFilter) + }, + {headerName: "Proficiency", field: "proficiency", filter: 'number', width: 120, + cellRenderer: reactCellRendererFactory(ProficiencyCellRenderer), + filter: reactFilterFactory(ProficiencyFilter)} + ] + }, + { + headerName: 'Contact', + children: [ + {headerName: "Mobile", field: "mobile", width: 150, filter: 'text'}, + {headerName: "Land-line", field: "landline", width: 150, filter: 'text'}, + {headerName: "Address", field: "address", width: 500, filter: 'text'} + ] + } + ]; + return columnDefs; + } +} + +// this is a simple cell renderer, putting together static html, no +// need to use React for it. +function countryCellRenderer(params) { + var flag = ""; + return flag + " " + params.value; +} +/* +var SKILL_TEMPLATE = + ''; + +var FILTER_TITLE = + '
' + + 'TITLE_NAME' + + '
';*/ +/* +function SkillFilter() { +} + +SkillFilter.prototype.init = function (params) { + this.filterChangedCallback = params.filterChangedCallback; + this.model = { + android: false, + css: false, + html5: false, + mac: false, + windows: false + }; +}; + +SkillFilter.prototype.getGui = function () { + var eGui = document.createElement('div'); + eGui.style.width = '380px'; + var eInstructions = document.createElement('div'); + eInstructions.innerHTML = FILTER_TITLE.replace('TITLE_NAME', 'Custom Skills Filter'); + eGui.appendChild(eInstructions); + + var that = this; + + RefData.IT_SKILLS.forEach(function (skill, index) { + var skillName = RefData.IT_SKILLS_NAMES[index]; + var eSpan = document.createElement('span'); + var html = SKILL_TEMPLATE.replace("SKILL_NAME", skillName).replace("SKILL", skill); + eSpan.innerHTML = html; + + var eCheckbox = eSpan.querySelector('input'); + eCheckbox.addEventListener('click', function () { + that.model[skill] = eCheckbox.checked; + that.filterChangedCallback(); + }); + + eGui.appendChild(eSpan); + }); + + return eGui; +}; + +SkillFilter.prototype.doesFilterPass = function (params) { + + var rowSkills = params.data.skills; + var model = this.model; + var passed = true; + + IT_SKILLS.forEach(function (skill) { + if (model[skill]) { + if (!rowSkills[skill]) { + passed = false; + } + } + }); + + return passed; +}; + +SkillFilter.prototype.isFilterActive = function () { + var model = this.model; + var somethingSelected = model.android || model.css || model.html5 || model.mac || model.windows; + return somethingSelected; +};*/ +// +//var PROFICIENCY_TEMPLATE = +// ''; +// +//var PROFICIENCY_NONE = 'none'; +//var PROFICIENCY_ABOVE40 = 'above40'; +//var PROFICIENCY_ABOVE60 = 'above60'; +//var PROFICIENCY_ABOVE80 = 'above80'; +// +//var PROFICIENCY_NAMES = ['No Filter', 'Above 40%', 'Above 60%', 'Above 80%']; +//var PROFICIENCY_VALUES = [PROFICIENCY_NONE, PROFICIENCY_ABOVE40, PROFICIENCY_ABOVE60, PROFICIENCY_ABOVE80]; +// +//function ProficiencyFilter() { +//} +// +//ProficiencyFilter.prototype.init = function (params) { +// this.filterChangedCallback = params.filterChangedCallback; +// this.selected = PROFICIENCY_NONE; +// this.valueGetter = params.valueGetter; +//}; +// +//ProficiencyFilter.prototype.getGui = function () { +// var eGui = document.createElement('div'); +// var eInstructions = document.createElement('div'); +// eInstructions.innerHTML = FILTER_TITLE.replace('TITLE_NAME', 'Custom Proficiency Filter'); +// eGui.appendChild(eInstructions); +// +// var random = '' + Math.random(); +// +// var that = this; +// PROFICIENCY_NAMES.forEach( function (name, index) { +// var eFilter = document.createElement('div'); +// var html = PROFICIENCY_TEMPLATE.replace('PROFICIENCY_NAME', name).replace('RANDOM', random); +// eFilter.innerHTML = html; +// var eRadio = eFilter.querySelector('input'); +// if (index === 0) { +// eRadio.checked = true; +// } +// eGui.appendChild(eFilter); +// +// eRadio.addEventListener('click', function () { +// that.selected = PROFICIENCY_VALUES[index]; +// that.filterChangedCallback(); +// }); +// }); +// +// return eGui; +//}; +// +//ProficiencyFilter.prototype.doesFilterPass = function (params) { +// +// var value = this.valueGetter(params); +// var valueAsNumber = parseFloat(value); +// +// switch (this.selected) { +// case PROFICIENCY_ABOVE40 : return valueAsNumber >= 40; +// case PROFICIENCY_ABOVE60 : return valueAsNumber >= 60; +// case PROFICIENCY_ABOVE80 : return valueAsNumber >= 80; +// default : return true; +// } +// +//}; +// +//ProficiencyFilter.prototype.isFilterActive = function () { +// return this.selected !== PROFICIENCY_NONE; +//}; diff --git a/src/ComponentUtil.js b/src/ComponentUtil.js deleted file mode 100644 index b2a1aac..0000000 --- a/src/ComponentUtil.js +++ /dev/null @@ -1,154 +0,0 @@ -export default class ComponentUtil {} - -ComponentUtil.SIMPLE_PROPERTIES = [ - 'sortingOrder', - 'icons','localeText','localeTextFunc', - 'groupColumnDef','context','rowStyle','rowClass','headerCellRenderer', - 'groupDefaultExpanded','slaveGrids','rowSelection', - 'overlayLoadingTemplate','overlayNoRowsTemplate', - 'headerCellTemplate' - ]; - -ComponentUtil.SIMPLE_NUMBER_PROPERTIES = [ - 'rowHeight','rowBuffer','colWidth' - ]; - -ComponentUtil.SIMPLE_BOOLEAN_PROPERTIES = [ - 'virtualPaging','toolPanelSuppressGroups','toolPanelSuppressValues','rowsAlreadyGrouped', - 'suppressRowClickSelection','suppressCellSelection','suppressHorizontalScroll','debug', - 'enableColResize','enableCellExpressions','enableSorting','enableServerSideSorting', - 'enableFilter','enableServerSideFilter','angularCompileRows','angularCompileFilters', - 'angularCompileHeaders','groupSuppressAutoColumn','groupSelectsChildren','groupHideGroupColumns', - 'groupIncludeFooter','groupUseEntireRow','groupSuppressRow','groupSuppressBlankHeader','forPrint', - 'suppressMenuHide','rowDeselection','unSortIcon','suppressMultiSort','suppressScrollLag', - 'singleClickEdit','suppressLoadingOverlay','suppressNoRowsOverlay','suppressAutoSize', - 'suppressParentsInRowNodes' - ]; - -ComponentUtil.WITH_IMPACT_STRING_PROPERTIES = ['quickFilterText']; -ComponentUtil.WITH_IMPACT_NUMBER_PROPERTIES = ['headerHeight']; -ComponentUtil.WITH_IMPACT_BOOLEAN_PROPERTIES = ['showToolPanel']; -ComponentUtil.WITH_IMPACT_OTHER_PROPERTIES = [ - 'rowData','floatingTopRowData','floatingBottomRowData', - 'columnDefs','datasource']; - -ComponentUtil.CALLBACKS = ['groupRowInnerRenderer', 'groupRowRenderer', 'groupAggFunction', - 'isScrollLag','isExternalFilterPresent','doesExternalFilterPass','getRowClass','getRowStyle', - 'headerCellRenderer','getHeaderCellTemplate']; - -ComponentUtil.ALL_PROPERTIES = ComponentUtil.SIMPLE_PROPERTIES - .concat(ComponentUtil.SIMPLE_NUMBER_PROPERTIES) - .concat(ComponentUtil.SIMPLE_BOOLEAN_PROPERTIES) - .concat(ComponentUtil.WITH_IMPACT_NUMBER_PROPERTIES) - .concat(ComponentUtil.WITH_IMPACT_BOOLEAN_PROPERTIES) - .concat(ComponentUtil.WITH_IMPACT_OTHER_PROPERTIES); - -ComponentUtil.copyAttributesToGridOptions = function(gridOptions, component) { - // create empty grid options if none were passed - if (typeof gridOptions !== 'object') { - gridOptions = {}; - } - // to allow array style lookup in TypeScript, take type away from 'this' and 'gridOptions' - var pGridOptions = gridOptions; - // add in all the simple properties - ComponentUtil.SIMPLE_PROPERTIES.concat(ComponentUtil.WITH_IMPACT_OTHER_PROPERTIES).forEach( (key)=> { - if (typeof (component)[key] !== 'undefined') { - pGridOptions[key] = component[key]; - } - }); - ComponentUtil.SIMPLE_BOOLEAN_PROPERTIES.concat(ComponentUtil.WITH_IMPACT_BOOLEAN_PROPERTIES).forEach( (key)=> { - if (typeof (component)[key] !== 'undefined') { - pGridOptions[key] = ComponentUtil.toBoolean(component[key]); - } - }); - ComponentUtil.SIMPLE_NUMBER_PROPERTIES.concat(ComponentUtil.WITH_IMPACT_NUMBER_PROPERTIES).forEach( (key)=> { - if (typeof (component)[key] !== 'undefined') { - pGridOptions[key] = ComponentUtil.toNumber(component[key]); - } - }); - - return gridOptions; - }; - -ComponentUtil.processOnChange = function(changes, gridOptions, api) { - // to allow array style lookup in TypeScript, take type away from 'this' and 'gridOptions' - var pGridOptions = gridOptions; - - // check if any change for the simple types, and if so, then just copy in the new value - ComponentUtil.SIMPLE_PROPERTIES.forEach( (key)=> { - if (changes[key]) { - pGridOptions[key] = changes[key].currentValue; - } - }); - ComponentUtil.SIMPLE_BOOLEAN_PROPERTIES.forEach( (key)=> { - if (changes[key]) { - pGridOptions[key] = ComponentUtil.toBoolean(changes[key].currentValue); - } - }); - ComponentUtil.SIMPLE_NUMBER_PROPERTIES.forEach( (key)=> { - if (changes[key]) { - pGridOptions[key] = ComponentUtil.toNumber(changes[key].currentValue); - } - }); - - if (changes.showToolPanel) { - api.showToolPanel(changes.showToolPanel.currentValue); - } - - if (changes.quickFilterText) { - api.setQuickFilter(changes.quickFilterText.currentValue); - } - - if (changes.rowData) { - api.setRowData(changes.rowData.currentValue); - } - - if (changes.floatingTopRowData) { - api.setFloatingTopRowData(changes.floatingTopRowData.currentValue); - } - - if (changes.floatingBottomRowData) { - api.setFloatingBottomRowData(changes.floatingBottomRowData.currentValue); - } - - if (changes.columnDefs) { - api.setColumnDefs(changes.columnDefs.currentValue); - } - - if (changes.datasource) { - api.setDatasource(changes.datasource.currentValue); - } - - if (changes.headerHeight) { - api.setHeaderHeight(changes.headerHeight.currentValue); - } - - // need to review this, it is not impacting anything, they should - // call something on the API to update the grid - if (changes.groupAggFunction) { - gridOptions.groupAggFunction = changes.groupAggFunction; - } - }; - -ComponentUtil.toBoolean = function(value) { - if (typeof value === 'boolean') { - return value; - } else if (typeof value === 'string') { - // for boolean, compare to empty String to allow attributes appearing with - // not value to be treated as 'true' - return value.toUpperCase() === 'TRUE' || value==''; - } else { - return false; - } - }; - -ComponentUtil.toNumber = function(value) { - if (typeof value === 'number') { - return value; - } else if (typeof value === 'string') { - return Number(value); - } else { - return undefined; - } - }; - diff --git a/src/ProficiencyCellRenderer.jsx b/src/ProficiencyCellRenderer.jsx new file mode 100644 index 0000000..ca47e13 --- /dev/null +++ b/src/ProficiencyCellRenderer.jsx @@ -0,0 +1,34 @@ +import React from 'react'; +import RefData from './RefData'; + +// cell renderer for the proficiency column. this is a very basic cell renderer, +// it is arguable that we should not of used React and just returned a string of +// html as a normal ag-Grid cellRenderer. +export default class ProficiencyCellRenderer extends React.Component { + + render() { + var params = this.props.params; + var backgroundColor; + if (params.value < 20) { + backgroundColor = 'red'; + } else if (params.value < 60) { + backgroundColor = '#ff9900'; + } else { + backgroundColor = '#00A000'; + } + + return ( +
+
{params.value}%
+
+ ); + } +} + +// the grid will always pass in one props called 'params', +// which is the grid passing you the params for the cellRenderer. +// this piece is optional. the grid will always pass the 'params' +// props, so little need for adding this validation meta-data. +ProficiencyCellRenderer.propTypes = { + params: React.PropTypes.object +}; \ No newline at end of file diff --git a/src/ProficiencyFilter.jsx b/src/ProficiencyFilter.jsx new file mode 100644 index 0000000..5c86c35 --- /dev/null +++ b/src/ProficiencyFilter.jsx @@ -0,0 +1,82 @@ +import React from 'react'; +import RefData from './RefData'; + +var PROFICIENCY_NAMES = ['No Filter', 'Above 40%', 'Above 60%', 'Above 80%']; + +// the proficiency filter component. this demonstrates how to integrate +// a React filter component with ag-Grid. +export default class ProficiencyFilter extends React.Component { + + constructor() { + super(); + this.state = { + selected: PROFICIENCY_NAMES[0] + }; + } + + // called by agGrid + init(params) { + this.filterChangedCallback = params.filterChangedCallback; + this.valueGetter = params.valueGetter; + } + + // called by agGrid + doesFilterPass(params) { + var value = this.valueGetter(params); + var valueAsNumber = parseFloat(value); + + switch (this.state.selected) { + case PROFICIENCY_NAMES[1] : return valueAsNumber >= 40; + case PROFICIENCY_NAMES[2] : return valueAsNumber >= 60; + case PROFICIENCY_NAMES[3] : return valueAsNumber >= 80; + default : return true; + } + }; + + // called by agGrid + isFilterActive() { + return this.state.selected !== PROFICIENCY_NAMES[0]; + }; + + onButtonPressed(name) { + console.log(name); + this.setState({ + selected: name + }, ()=> { + this.filterChangedCallback(); + }); + console.log(name); + } + + render() { + var rows = []; + PROFICIENCY_NAMES.forEach( (name)=> { + var selected = this.state.selected === name; + rows.push( +
+ +
+ ); + }); + + return ( +
+
+ Custom Proficiency Filter +
+ {rows} +
+ ); + } + + // these are other method that agGrid calls that we + // could of implemented, but they are optional and + // we have no use for them in this particular filter. + //getApi() {} + //afterGuiAttached(params) {} + //onNewRowsLoaded() {} + //onAnyFilterChanged() {} +} diff --git a/src/RowDataFactory.js b/src/RowDataFactory.js new file mode 100644 index 0000000..98f292c --- /dev/null +++ b/src/RowDataFactory.js @@ -0,0 +1,44 @@ +import RefData from './RefData'; + +export default class RowDataFactory { + + createRowData() { + var rowData = []; + + for (var i = 0; i < 1000; i++) { + var countryData = RefData.COUNTRIES[i % RefData.COUNTRIES.length]; + rowData.push({ + name: RefData.FIRST_NAMES[i % RefData.FIRST_NAMES.length] + ' ' + RefData.LAST_NAMES[i % RefData.LAST_NAMES.length], + skills: { + android: Math.random() < 0.4, + html5: Math.random() < 0.4, + mac: Math.random() < 0.4, + windows: Math.random() < 0.4, + css: Math.random() < 0.4 + }, + address: RefData.ADDRESSES[i % RefData.ADDRESSES.length], + years: Math.round(Math.random() * 100), + proficiency: Math.round(Math.random() * 100), + country: countryData.country, + continent: countryData.continent, + language: countryData.language, + mobile: this.createRandomPhoneNumber(), + landline: this.createRandomPhoneNumber() + }); + } + + return rowData; + } + + createRandomPhoneNumber() { + var result = '+'; + for (var i = 0; i < 12; i++) { + result += Math.round(Math.random() * 10); + if (i === 2 || i === 5 || i === 8) { + result += ' '; + } + } + return result; + } + +} \ No newline at end of file diff --git a/src/SkillsCellRenderer.jsx b/src/SkillsCellRenderer.jsx index 590f3e2..5fe5bce 100644 --- a/src/SkillsCellRenderer.jsx +++ b/src/SkillsCellRenderer.jsx @@ -5,8 +5,9 @@ export default class SkillsCellRenderer extends React.Component { render() { var skills = []; + var rowData = this.props.params.data; RefData.IT_SKILLS.forEach( (skill) => { - if (this.props.skills[skill]) { + if (rowData.skills[skill]) { skills.push(); } }); @@ -16,6 +17,10 @@ export default class SkillsCellRenderer extends React.Component { } +// the grid will always pass in one props called 'params', +// which is the grid passing you the params for the cellRenderer. +// this piece is optional. the grid will always pass the 'params' +// props, so little need for adding this validation meta-data. SkillsCellRenderer.propTypes = { - skills: React.PropTypes.object + params: React.PropTypes.object }; \ No newline at end of file diff --git a/src/SkillsFilter.jsx b/src/SkillsFilter.jsx new file mode 100644 index 0000000..3c4d223 --- /dev/null +++ b/src/SkillsFilter.jsx @@ -0,0 +1,98 @@ +import React from 'react'; +import RefData from './RefData'; + +// the skills filter component. this can be laid out much better in a 'React' +// way. there are design patterns you can apply to layout out your React classes. +// however, i'm not worried, as the intention here is to show you ag-Grid +// working with React, and that's all. i'm not looking for any awards for my +// React design skills. +export default class SkillsFilter extends React.Component { + + constructor() { + super(); + this.state = { + skills: RefData.IT_SKILLS, + skillNames: RefData.IT_SKILLS_NAMES, + android: false, + css: false, + html5: false, + mac: false, + windows: false + }; + } + + // called by agGrid + init(params) { + this.filterChangedCallback = params.filterChangedCallback; + } + + // called by agGrid + doesFilterPass(params) { + + var rowSkills = params.data.skills; + var passed = true; + + this.state.skills.forEach( (skill) => { + if (this.state[skill]) { + if (!rowSkills[skill]) { + passed = false; + } + } + }); + + return passed; + }; + + // called by agGrid + isFilterActive() { + var somethingSelected = this.state.android || this.state.css || + this.state.html5 || this.state.mac || this.state.windows; + return somethingSelected; + }; + + onSkillChanged(skill, event) { + var newValue = event.target.checked; + var newModel = {}; + newModel[skill] = newValue; + this.setState(newModel, ()=> {this.filterChangedCallback();} ); + } + + render() { + + var skillsTemplates = []; + this.state.skills.forEach( (skill, index) => { + + var skillName = this.state.skillNames[index]; + var template = ( + + ); + + skillsTemplates.push(template); + }); + + return ( +
+
+ Custom Skills Filter +
+ {skillsTemplates} +
+ ); + } + + // these are other method that agGrid calls that we + // could of implemented, but they are optional and + // we have no use for them in this particular filter. + //getApi() {} + //afterGuiAttached(params) {} + //onNewRowsLoaded() {} + //onAnyFilterChanged() {} +} diff --git a/src/myApp.jsx b/src/myApp.jsx index e3ee7c9..bd0848c 100644 --- a/src/myApp.jsx +++ b/src/myApp.jsx @@ -1,355 +1,183 @@ import ReactDOM from 'react-dom'; import React from 'react'; -import {AgGridReact} from './AgGridReact.jsx'; -import {RefData} from './RefData'; -import './myApp.css'; +import {AgGridReact} from 'ag-grid-react-component'; +import RefData from './RefData'; +import RowDataFactory from './RowDataFactory'; +import ColDefFactory from './ColDefFactory.jsx'; +import './MyApp.css'; -export class MyApp extends React.Component { +export default class MyApp extends React.Component { constructor() { super(); + this.state = { - quickFilterText: null + quickFilterText: null, + showGrid: true, + showToolPanel: false, + columnDefs: new ColDefFactory().createColDefs(), + rowData: new RowDataFactory().createRowData(), + 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 = { + // this is how you listen for events using gridOptions + onModelUpdated: function() { + console.log('event onModelUpdated received'); + }, + // this is a simple property + rowBuffer: 10 // no need to set this, the default is fine for almost all scenarios }; - this.onQuickFilterChange = this.onQuickFilterChange.bind(this) } - onQuickFilterChange() { - console.log('it changed: ' + this.state.quickFilterText); + onShowGrid(show) { + this.setState({ + showGrid: show + }); + } + + onToggleToolPanel(event) { + this.setState({showToolPanel: event.target.checked}); + } + + onReady(params) { + this.api = params.api; + this.columnApi = params.columnApi; + } + + 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 + }); } render() { - var gridOptions = { - columnDefs: this.createColDefs(), - rowData: this.createRowData(), - rowSelection: 'multiple', - enableColResize: true, - enableSorting: true, - enableFilter: true, - groupHeaders: true, - rowHeight: 22, - //modelUpdated: modelUpdated, - debug: true - }; + var gridTemplate; + var bottomHeaderTemplate; + var topHeaderTemplate; - return
-
+ 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: + + + +
+
+
+ + +
+
+
+ ); + gridTemplate = ( +
+ +
+ ); + } + + return
+
+ {topHeaderTemplate} + {bottomHeaderTemplate} + {gridTemplate} +
; } - - createRowData() { - var rowData = []; - - for (var i = 0; i < 1000; i++) { - var countryData = RefData.COUNTRIES[i % RefData.COUNTRIES.length]; - rowData.push({ - name: RefData.FIRST_NAMES[i % RefData.FIRST_NAMES.length] + ' ' + RefData.LAST_NAMES[i % RefData.LAST_NAMES.length], - skills: { - android: Math.random() < 0.4, - html5: Math.random() < 0.4, - mac: Math.random() < 0.4, - windows: Math.random() < 0.4, - css: Math.random() < 0.4 - }, - address: RefData.ADDRESSES[i % RefData.ADDRESSES.length], - years: Math.round(Math.random() * 100), - proficiency: Math.round(Math.random() * 100), - country: countryData.country, - continent: countryData.continent, - language: countryData.language, - mobile: createRandomPhoneNumber(), - landline: createRandomPhoneNumber() - }); - } - - return rowData; - } - - createColDefs() { - var columnDefs = [ - {headerName: '', width: 30, checkboxSelection: true, suppressSorting: true, - suppressMenu: true, pinned: true}, - { - headerName: 'Employee', - children: [ - {headerName: "Name", field: "name", - width: 150, pinned: true}, - {headerName: "Country", field: "country", width: 150, - cellRenderer: countryCellRenderer, pinned: true, - filterParams: {cellRenderer: countryCellRenderer, cellHeight: 20}}, - ] - }, - { - headerName: 'IT Skills', - children: [ - {headerName: "Skills", width: 125, suppressSorting: true, cellRenderer: skillsCellRenderer, filter: SkillFilter}, - {headerName: "Proficiency", field: "proficiency", filter: 'number', width: 120, cellRenderer: percentCellRenderer, filter: ProficiencyFilter}, - ] - }, - { - headerName: 'Contact', - children: [ - {headerName: "Mobile", field: "mobile", width: 150, filter: 'text'}, - {headerName: "Land-line", field: "landline", width: 150, filter: 'text'}, - {headerName: "Address", field: "address", width: 500, filter: 'text'} - ] - } - ]; - return columnDefs; - } } - - var btBringGridBack; - var btDestroyGrid; - // - //btBringGridBack = document.querySelector('#btBringGridBack'); - //btDestroyGrid = document.querySelector('#btDestroyGrid'); - // - //btBringGridBack.addEventListener('click', onBtBringGridBack); - //btDestroyGrid.addEventListener('click', onBtDestroyGrid); - // - //addQuickFilterListener(); - //onBtBringGridBack(); - - function onBtBringGridBack() { - var eGridDiv = document.querySelector('#myGrid'); - new ag.grid.Grid(eGridDiv, gridOptions); - btBringGridBack.disabled = true; - btDestroyGrid.disabled = false; - } - - function onBtDestroyGrid() { - btBringGridBack.disabled = false; - btDestroyGrid.disabled = true; - gridOptions.api.destroy(); - } - - function addQuickFilterListener() { - var eInput = document.querySelector('#quickFilterInput'); - eInput.addEventListener("input", function () { - var text = eInput.value; - gridOptions.api.setQuickFilter(text); - }); - } - - function modelUpdated() { - var model = gridOptions.api.getModel(); - var totalRows = gridOptions.rowData.length; - var processedRows = model.getVirtualRowCount(); - var eSpan = document.querySelector('#rowCount'); - eSpan.innerHTML = processedRows.toLocaleString() + ' / ' + totalRows.toLocaleString(); - } - - function skillsCellRenderer(params) { - var data = params.data; - var skills = []; - RefData.IT_SKILLS.forEach(function (skill) { - if (data.skills[skill]) { - skills.push(''); - } - }); - return skills.join(' '); - } - - function countryCellRenderer(params) { - var flag = ""; - return flag + " " + params.value; - } - - function createRandomPhoneNumber() { - var result = '+'; - for (var i = 0; i < 12; i++) { - result += Math.round(Math.random() * 10); - if (i === 2 || i === 5 || i === 8) { - result += ' '; - } - } - return result; - } - - function percentCellRenderer(params) { - var value = params.value; - - var eDivPercentBar = document.createElement('div'); - eDivPercentBar.className = 'div-percent-bar'; - eDivPercentBar.style.width = value + '%'; - if (value < 20) { - eDivPercentBar.style.backgroundColor = 'red'; - } else if (value < 60) { - eDivPercentBar.style.backgroundColor = '#ff9900'; - } else { - eDivPercentBar.style.backgroundColor = '#00A000'; - } - - var eValue = document.createElement('div'); - eValue.className = 'div-percent-value'; - eValue.innerHTML = value + '%'; - - var eOuterDiv = document.createElement('div'); - eOuterDiv.className = 'div-outer-div'; - eOuterDiv.appendChild(eValue); - eOuterDiv.appendChild(eDivPercentBar); - - return eOuterDiv; - } - - var SKILL_TEMPLATE = - ''; - - var FILTER_TITLE = - '
' + - 'TITLE_NAME' + - '
'; - - function SkillFilter() { - } - - SkillFilter.prototype.init = function (params) { - this.filterChangedCallback = params.filterChangedCallback; - this.model = { - android: false, - css: false, - html5: false, - mac: false, - windows: false - }; - }; - - SkillFilter.prototype.getGui = function () { - var eGui = document.createElement('div'); - eGui.style.width = '380px'; - var eInstructions = document.createElement('div'); - eInstructions.innerHTML = FILTER_TITLE.replace('TITLE_NAME', 'Custom Skills Filter'); - eGui.appendChild(eInstructions); - - var that = this; - - RefData.IT_SKILLS.forEach(function (skill, index) { - var skillName = RefData.IT_SKILLS_NAMES[index]; - var eSpan = document.createElement('span'); - var html = SKILL_TEMPLATE.replace("SKILL_NAME", skillName).replace("SKILL", skill); - eSpan.innerHTML = html; - - var eCheckbox = eSpan.querySelector('input'); - eCheckbox.addEventListener('click', function () { - that.model[skill] = eCheckbox.checked; - that.filterChangedCallback(); - }); - - eGui.appendChild(eSpan); - }); - - return eGui; - }; - - SkillFilter.prototype.doesFilterPass = function (params) { - - var rowSkills = params.data.skills; - var model = this.model; - var passed = true; - - IT_SKILLS.forEach(function (skill) { - if (model[skill]) { - if (!rowSkills[skill]) { - passed = false; - } - } - }); - - return passed; - }; - - SkillFilter.prototype.isFilterActive = function () { - var model = this.model; - var somethingSelected = model.android || model.css || model.html5 || model.mac || model.windows; - return somethingSelected; - }; - - var PROFICIENCY_TEMPLATE = - ''; - - var PROFICIENCY_NONE = 'none'; - var PROFICIENCY_ABOVE40 = 'above40'; - var PROFICIENCY_ABOVE60 = 'above60'; - var PROFICIENCY_ABOVE80 = 'above80'; - - var PROFICIENCY_NAMES = ['No Filter', 'Above 40%', 'Above 60%', 'Above 80%']; - var PROFICIENCY_VALUES = [PROFICIENCY_NONE, PROFICIENCY_ABOVE40, PROFICIENCY_ABOVE60, PROFICIENCY_ABOVE80]; - - function ProficiencyFilter() { - } - - ProficiencyFilter.prototype.init = function (params) { - this.filterChangedCallback = params.filterChangedCallback; - this.selected = PROFICIENCY_NONE; - this.valueGetter = params.valueGetter; - }; - - ProficiencyFilter.prototype.getGui = function () { - var eGui = document.createElement('div'); - var eInstructions = document.createElement('div'); - eInstructions.innerHTML = FILTER_TITLE.replace('TITLE_NAME', 'Custom Proficiency Filter'); - eGui.appendChild(eInstructions); - - var random = '' + Math.random(); - - var that = this; - PROFICIENCY_NAMES.forEach( function (name, index) { - var eFilter = document.createElement('div'); - var html = PROFICIENCY_TEMPLATE.replace('PROFICIENCY_NAME', name).replace('RANDOM', random); - eFilter.innerHTML = html; - var eRadio = eFilter.querySelector('input'); - if (index === 0) { - eRadio.checked = true; - } - eGui.appendChild(eFilter); - - eRadio.addEventListener('click', function () { - that.selected = PROFICIENCY_VALUES[index]; - that.filterChangedCallback(); - }); - }); - - return eGui; - }; - - ProficiencyFilter.prototype.doesFilterPass = function (params) { - - var value = this.valueGetter(params); - var valueAsNumber = parseFloat(value); - - switch (this.selected) { - case PROFICIENCY_ABOVE40 : return valueAsNumber >= 40; - case PROFICIENCY_ABOVE60 : return valueAsNumber >= 60; - case PROFICIENCY_ABOVE80 : return valueAsNumber >= 80; - default : return true; - } - - }; - - ProficiencyFilter.prototype.isFilterActive = function () { - return this.selected !== PROFICIENCY_NONE; - }; diff --git a/webpack.config.js b/webpack.config.js index 0214f8f..5671587 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -21,6 +21,7 @@ module.exports = { }, resolve: { alias: { + "ag-grid-react-component" : __dirname + "/node_modules/ag-grid-react-component/dist/ag-grid-react-component.js", "ag-grid" : __dirname + "/node_modules/ag-grid/dist/ag-grid.js", "ag-grid-root" : __dirname + "/node_modules/ag-grid/dist" }