Stateless table component
parent
832e01fe27
commit
272a37fd4c
|
@ -1,50 +0,0 @@
|
|||
import React from 'react';
|
||||
import Checkbox from '../../checkbox';
|
||||
import style from './style';
|
||||
|
||||
class Head extends React.Component {
|
||||
|
||||
static propTypes = {
|
||||
className: React.PropTypes.string,
|
||||
model: React.PropTypes.object,
|
||||
onSelect: React.PropTypes.func,
|
||||
selected: React.PropTypes.bool
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
className: '',
|
||||
model: {},
|
||||
selected: false
|
||||
};
|
||||
|
||||
handleSelectChange = (event) => {
|
||||
this.props.onSelect(event);
|
||||
};
|
||||
|
||||
renderCellSelectable () {
|
||||
if (this.props.onSelect) {
|
||||
return (
|
||||
<th className={style.selectable}>
|
||||
<Checkbox onChange={this.handleSelectChange} checked={this.props.selected}/>
|
||||
</th>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
return (
|
||||
<thead data-react-toolbox-table='head' className={this.props.className}>
|
||||
<tr>
|
||||
{ this.renderCellSelectable() }
|
||||
{
|
||||
Object.keys(this.props.model).map((key) => {
|
||||
return (<th key={key}>{key}</th>);
|
||||
})
|
||||
}
|
||||
</tr>
|
||||
</thead>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Head;
|
|
@ -1,97 +0,0 @@
|
|||
import React from 'react';
|
||||
import Checkbox from '../../checkbox';
|
||||
import style from './style';
|
||||
|
||||
// Private
|
||||
const _castType = (type) => {
|
||||
let input_type = 'text';
|
||||
if (type === Date) {
|
||||
input_type = 'date';
|
||||
} else if (type === Number) {
|
||||
input_type = 'number';
|
||||
} else if (type === Boolean) {
|
||||
input_type = 'checkbox';
|
||||
}
|
||||
return input_type;
|
||||
};
|
||||
|
||||
const _castValue = (value, type) => {
|
||||
let cast = value;
|
||||
if (value && type === Date) {
|
||||
cast = new Date(value).toISOString().slice(0, 10);
|
||||
}
|
||||
return cast;
|
||||
};
|
||||
|
||||
class Row extends React.Component {
|
||||
|
||||
static propTypes = {
|
||||
changed: React.PropTypes.bool,
|
||||
className: React.PropTypes.string,
|
||||
data: React.PropTypes.object,
|
||||
index: React.PropTypes.number,
|
||||
onChange: React.PropTypes.func,
|
||||
onSelect: React.PropTypes.func,
|
||||
selected: React.PropTypes.bool
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
className: ''
|
||||
};
|
||||
|
||||
handleInputChange = (key, event) => {
|
||||
this.props.onChange(event, this, key, event.target.value);
|
||||
};
|
||||
|
||||
handleSelectChange = (event) => {
|
||||
this.props.onSelect(event, this);
|
||||
};
|
||||
|
||||
renderCell (key) {
|
||||
let value = this.props.data[key];
|
||||
|
||||
if (this.props.onChange) {
|
||||
const attr = this.props.model[key];
|
||||
value = _castValue(value, attr.type);
|
||||
return (
|
||||
<input
|
||||
type={_castType(attr.type)}
|
||||
value={value}
|
||||
onChange={this.handleInputChange.bind(null, key)}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
renderCellSelectable () {
|
||||
if (this.props.onSelect) {
|
||||
return (
|
||||
<td className={style.selectable}>
|
||||
<Checkbox onChange={this.handleSelectChange} checked={this.props.selected}/>
|
||||
</td>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
let className = `${this.props.className} ${style.row}`;
|
||||
if (this.props.changed) className += ` ${style.changed}`;
|
||||
if (this.props.onChange) className += ` ${style.editable}`;
|
||||
if (this.props.selected) className += ` ${style.selected}`;
|
||||
|
||||
return (
|
||||
<tr data-react-toolbox-table='row' className={className}>
|
||||
{ this.renderCellSelectable() }
|
||||
{
|
||||
Object.keys(this.props.model).map((key) => {
|
||||
return (<td key={key}>{this.renderCell(key)}</td>);
|
||||
})
|
||||
}
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Row;
|
|
@ -1,37 +0,0 @@
|
|||
@import "../../base";
|
||||
@import "../config";
|
||||
|
||||
.row {
|
||||
transition: background-color $animation-duration $animation-curve-default;
|
||||
&:last-child {
|
||||
border-color: transparent;
|
||||
}
|
||||
> td {
|
||||
> input {
|
||||
display: block;
|
||||
width: 100%;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.selected, .row:hover {
|
||||
background-color: $table-row-highlight;
|
||||
}
|
||||
|
||||
.editable > * {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.changed {
|
||||
// @TODO: We've to create a style for changed rows.
|
||||
}
|
||||
|
||||
.selectable {
|
||||
padding-right: 0 !important;
|
||||
width: 1.8 * $unit;
|
||||
> * {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
import React from 'react';
|
||||
import Checkbox from '../checkbox';
|
||||
import style from './style';
|
||||
|
||||
const TableHead = ({model, onSelect, selected}) => {
|
||||
let selectCell;
|
||||
const contentCells = Object.keys(model).map((key) => {
|
||||
return <th key={key}>{key}</th>;
|
||||
});
|
||||
|
||||
if (onSelect) {
|
||||
selectCell = (
|
||||
<th key='select' className={style.selectable}>
|
||||
<Checkbox onChange={onSelect} checked={selected} />
|
||||
</th>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<thead>
|
||||
<tr>{[selectCell, ...contentCells]}</tr>
|
||||
</thead>
|
||||
);
|
||||
};
|
||||
|
||||
TableHead.propTypes = {
|
||||
className: React.PropTypes.string,
|
||||
model: React.PropTypes.object,
|
||||
onSelect: React.PropTypes.func,
|
||||
selected: React.PropTypes.bool
|
||||
};
|
||||
|
||||
TableHead.defaultProps = {
|
||||
className: '',
|
||||
model: {},
|
||||
selected: false
|
||||
};
|
||||
|
||||
export default TableHead;
|
|
@ -1,121 +1,78 @@
|
|||
import React from 'react';
|
||||
import Head from './components/head';
|
||||
import Row from './components/row';
|
||||
import TableHead from './head';
|
||||
import TableRow from './row';
|
||||
import style from './style';
|
||||
import utils from '../utils';
|
||||
|
||||
class Table extends React.Component {
|
||||
|
||||
static propTypes = {
|
||||
className: React.PropTypes.string,
|
||||
dataSource: React.PropTypes.array,
|
||||
model: React.PropTypes.object,
|
||||
heading: React.PropTypes.bool,
|
||||
model: React.PropTypes.object,
|
||||
onChange: React.PropTypes.func,
|
||||
onSelect: React.PropTypes.func
|
||||
onSelect: React.PropTypes.func,
|
||||
selected: React.PropTypes.array,
|
||||
source: React.PropTypes.array
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
className: '',
|
||||
dataSource: [],
|
||||
heading: true
|
||||
heading: true,
|
||||
selected: [],
|
||||
source: []
|
||||
};
|
||||
|
||||
state = {
|
||||
all: false,
|
||||
dataSource: utils.cloneObject(this.props.dataSource),
|
||||
selected_index: []
|
||||
};
|
||||
|
||||
componentWillReceiveProps = (next_props) => {
|
||||
if (next_props.dataSource) {
|
||||
this.setState({dataSource: utils.cloneObject(next_props.datasSource)});
|
||||
handleFullSelect = () => {
|
||||
if (this.props.onSelect) {
|
||||
const {source, selected} = this.props;
|
||||
const newSelected = source.length === selected.length ? [] : source.map((i, idx) => idx);
|
||||
this.props.onSelect(newSelected);
|
||||
}
|
||||
};
|
||||
|
||||
handleRowChange = (event, row, key, value) => {
|
||||
const dataSource = this.state.dataSource;
|
||||
dataSource[row.props.index][key] = value;
|
||||
this.setState({ dataSource });
|
||||
handleRowSelect = (index) => {
|
||||
if (this.props.onSelect) {
|
||||
const position = this.props.selected.indexOf(index);
|
||||
const newSelected = [...this.props.selected];
|
||||
if (position !== -1) newSelected.splice(position, 1); else newSelected.push(index);
|
||||
this.props.onSelect(newSelected);
|
||||
}
|
||||
};
|
||||
|
||||
handleRowChange = (index, key, value) => {
|
||||
if (this.props.onChange) {
|
||||
this.props.onChange(event, dataSource[row.props.index], dataSource);
|
||||
this.props.onChange(index, key, value);
|
||||
}
|
||||
};
|
||||
|
||||
handleRowSelect = (event, instance) => {
|
||||
const index = instance.props.index;
|
||||
const selected_index = this.state.selected_index;
|
||||
const selected = selected_index.indexOf(index) === -1;
|
||||
if (selected) {
|
||||
selected_index.push(index);
|
||||
} else {
|
||||
delete selected_index[selected_index.indexOf(index)];
|
||||
}
|
||||
this.setState({ selected_index: selected_index });
|
||||
this.props.onSelect(event, this.getSelected());
|
||||
};
|
||||
|
||||
handleRowsSelect = () => {
|
||||
const all = !this.state.all;
|
||||
this.setState({ all });
|
||||
this.props.onSelect(event, this.getSelected(all));
|
||||
};
|
||||
|
||||
isChanged = (data, base) => {
|
||||
let changed = false;
|
||||
Object.keys(data).map((key) => {
|
||||
if (data[key] !== base[key]) {
|
||||
changed = true;
|
||||
}
|
||||
});
|
||||
return changed;
|
||||
};
|
||||
|
||||
getSelected = (all = false) => {
|
||||
const rows = [];
|
||||
this.state.dataSource.map((row, index) => {
|
||||
if (all || this.state.selected_index.indexOf(index) !== -1) rows.push(row);
|
||||
});
|
||||
return rows;
|
||||
}
|
||||
|
||||
renderHead () {
|
||||
if (this.props.heading) {
|
||||
return (
|
||||
<Head
|
||||
model={this.props.model}
|
||||
onSelect={this.props.onSelect ? this.handleRowsSelect : null}
|
||||
selected={this.state.all}
|
||||
/>
|
||||
);
|
||||
const {model, selected, source} = this.props;
|
||||
const isSelected = selected.length === source.length;
|
||||
return <TableHead model={model} onSelect={this.handleFullSelect} selected={isSelected} />;
|
||||
}
|
||||
}
|
||||
|
||||
renderBody () {
|
||||
return (
|
||||
<tbody>
|
||||
{
|
||||
this.state.dataSource.map((data, index) => {
|
||||
return (
|
||||
<Row
|
||||
changed={this.isChanged(data, this.props.dataSource[index])}
|
||||
data={data}
|
||||
index={index}
|
||||
key={index}
|
||||
model={this.props.model}
|
||||
onChange={this.props.onChange ? this.handleRowChange : null}
|
||||
onSelect={this.props.onSelect ? this.handleRowSelect : null}
|
||||
selected={this.state.all || this.state.selected_index.indexOf(index) !== -1}
|
||||
/>
|
||||
);
|
||||
})
|
||||
}
|
||||
</tbody>
|
||||
);
|
||||
const rows = this.props.source.map((data, idx) => {
|
||||
return (
|
||||
<TableRow
|
||||
data={data}
|
||||
index={idx}
|
||||
key={idx}
|
||||
model={this.props.model}
|
||||
onChange={this.props.onChange ? this.handleRowChange.bind(this, idx) : null}
|
||||
onSelect={this.handleRowSelect.bind(this, idx)}
|
||||
selected={this.props.selected.indexOf(idx) !== -1}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
return <tbody>{rows}</tbody>;
|
||||
}
|
||||
|
||||
render () {
|
||||
const className = `${this.props.className} ${style.root}`;
|
||||
let className = style.root;
|
||||
if (this.props.className) className += ` ${this.props.className}`;
|
||||
return (
|
||||
<table data-react-toolbox='table' className={className}>
|
||||
{ this.renderHead() }
|
||||
|
|
|
@ -1,43 +1,60 @@
|
|||
# Table
|
||||
|
||||
The Table component is an enhanced version of the standard HTML `<table>`. A data-table consists of rows and columns of well-formatted data, presented with appropriate user interaction capabilities. This component uses a solid typed model, helping you to create formatted formated cells. These cells can be editable if you subscribe to `onChange` method who dispatch then new dataSource with each change.
|
||||
The Table component is an enhanced version of the standard HTML `<table>`. A data-table consists of rows and columns of well-formatted data, presented with appropriate user interaction capabilities. This component uses a solid typed model, helping you to create formatted formated cells. These cells can be editable if you subscribe to `onChange` method who dispatch then new source with each change.
|
||||
|
||||
<!-- example -->
|
||||
```jsx
|
||||
import Table from 'react-toolbox/lib/table';
|
||||
|
||||
const UserModel = {
|
||||
name: {type: String}
|
||||
,
|
||||
twitter: {type: String}
|
||||
,
|
||||
birthdate: {type: Date}
|
||||
,
|
||||
cats: {type: Number}
|
||||
,
|
||||
dogs: {type: Number}
|
||||
,
|
||||
name: {type: String},
|
||||
twitter: {type: String},
|
||||
birthdate: {type: Date},
|
||||
cats: {type: Number},
|
||||
dogs: {type: Number},
|
||||
active: {type: Boolean}
|
||||
};
|
||||
|
||||
const users = [
|
||||
{name: 'Javi Jimenez', twitter: '@soyjavi', birthdate: new Date(1980, 3, 11), cats: 1}
|
||||
,
|
||||
{name: 'Javi Jimenez', twitter: '@soyjavi', birthdate: new Date(1980, 3, 11), cats: 1},
|
||||
{name: 'Javi Velasco', twitter: '@javivelasco', birthdate: new Date(1987, 1, 1), dogs: 1, active: true}
|
||||
];
|
||||
|
||||
const TableTest = () => (
|
||||
<Table model={UserModel} dataSource={users} />
|
||||
)
|
||||
class TableTest extends React.Component {
|
||||
state = { selected: [], source: users };
|
||||
|
||||
handleChange = (row, key, value) => {
|
||||
const source = this.state.source;
|
||||
source[row][key] = value;
|
||||
this.setState({source});
|
||||
};
|
||||
|
||||
handleSelect = (selected) => {
|
||||
this.setState({selected});
|
||||
};
|
||||
|
||||
render () {
|
||||
return (
|
||||
<Table
|
||||
model={UserModel}
|
||||
onChange={this.handleChange}
|
||||
onSelect={this.handleSelect}
|
||||
selected={this.state.selected}
|
||||
source={this.state.source}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Properties
|
||||
|
||||
| Name | Type | Default | Description|
|
||||
|:-----|:-----|:-----|:-----|
|
||||
| `className` | `String` | `''` | Sets a class to style of the Component.|
|
||||
| `dataSource` | `Array` | | array representing all items for show.|
|
||||
| `model` | `Object` | | If true, component will be disabled.|
|
||||
| `className` | `String` | `''` | Sets a custom class to style the Component.|
|
||||
| `heading` | `Bool` | `true` | If true, component will show a heading using model field names.|
|
||||
| `onChange` | `Function` | | Callback function that is fired when the components's value changes.|
|
||||
| `onSelect` | `Function` | | Callback function when selects a determinate row.|
|
||||
| `model` | `Object` | | Object describing the data model that represents each object in the `source`.|
|
||||
| `onChange` | `Function` | | Callback function that is fired when an item in a row changes. If set, rows are editable. |
|
||||
| `onSelect` | `Function` | | Callback function invoked when the row selection changes.|
|
||||
| `selected` | `Array` | | Array of indexes of the items in the source that should appear as selected.|
|
||||
| `source` | `Array` | | Array of objects representing each item to show.|
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
import React from 'react';
|
||||
import Checkbox from '../checkbox';
|
||||
import utils from '../utils';
|
||||
import style from './style';
|
||||
|
||||
class TableRow extends React.Component {
|
||||
static propTypes = {
|
||||
data: React.PropTypes.object,
|
||||
onChange: React.PropTypes.func,
|
||||
onSelect: React.PropTypes.func,
|
||||
selected: React.PropTypes.bool
|
||||
};
|
||||
|
||||
handleInputChange = (key, type, event) => {
|
||||
const value = type === 'checkbox' ? event.target.checked : event.target.value;
|
||||
this.props.onChange(key, value);
|
||||
};
|
||||
|
||||
renderSelectCell () {
|
||||
if (this.props.onSelect) {
|
||||
return (
|
||||
<td className={style.selectable}>
|
||||
<Checkbox checked={this.props.selected} onChange={this.props.onSelect} />
|
||||
</td>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
renderCells () {
|
||||
return Object.keys(this.props.model).map((key) => {
|
||||
return <td key={key}>{this.renderCell(key)}</td>;
|
||||
});
|
||||
}
|
||||
|
||||
renderCell (key) {
|
||||
const value = this.props.data[key];
|
||||
if (this.props.onChange) {
|
||||
return this.renderInput(key, value);
|
||||
} else if (value) {
|
||||
return value.toString();
|
||||
}
|
||||
}
|
||||
|
||||
renderInput (key, value) {
|
||||
const inputType = utils.inputTypeForPrototype(this.props.model[key].type);
|
||||
const inputValue = utils.prepareValueForInput(value, inputType);
|
||||
const checked = inputType === 'checkbox' && value ? true : null;
|
||||
return (
|
||||
<input
|
||||
checked={checked}
|
||||
onChange={this.handleInputChange.bind(null, key, inputType)}
|
||||
type={inputType}
|
||||
value={inputValue}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
render () {
|
||||
let className = style.row;
|
||||
if (this.props.onChange) className += ` ${style.editable}`;
|
||||
if (this.props.selected) className += ` ${style.selected}`;
|
||||
|
||||
return (
|
||||
<tr data-react-toolbox-table='row' className={className}>
|
||||
{ this.renderSelectCell() }
|
||||
{ this.renderCells() }
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default TableRow;
|
|
@ -10,7 +10,6 @@
|
|||
height: $table-row-height;
|
||||
line-height: $table-row-height;
|
||||
border-bottom: $table-row-divider;
|
||||
|
||||
}
|
||||
th {
|
||||
font-weight: $font-weight-bold;
|
||||
|
@ -21,5 +20,35 @@
|
|||
th, td {
|
||||
position: relative;
|
||||
padding: 0 $table-row-offset;
|
||||
&.selectable {
|
||||
width: 1.8 * $unit;
|
||||
padding-right: 0;
|
||||
> * {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.row {
|
||||
transition: background-color $animation-duration $animation-curve-default;
|
||||
&:last-child {
|
||||
border-color: transparent;
|
||||
}
|
||||
> td {
|
||||
> input {
|
||||
display: block;
|
||||
width: 100%;
|
||||
background-color: transparent;
|
||||
border: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.selected, .row:hover {
|
||||
background-color: $table-row-highlight;
|
||||
}
|
||||
|
||||
.editable > * {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
|
|
@ -44,6 +44,21 @@ module.exports = {
|
|||
return JSON.parse(JSON.stringify(object));
|
||||
},
|
||||
|
||||
inputTypeForPrototype (prototype) {
|
||||
if (prototype === Date) return 'date';
|
||||
if (prototype === Number) return 'number';
|
||||
if (prototype === Boolean) return 'checkbox';
|
||||
return 'text';
|
||||
},
|
||||
|
||||
prepareValueForInput (value, type) {
|
||||
if (type === 'date') return new Date(value).toISOString().slice(0, 10);
|
||||
if (type === 'checkbox') {
|
||||
return value ? 'on' : null;
|
||||
}
|
||||
return value;
|
||||
},
|
||||
|
||||
events: require('./events'),
|
||||
prefixer: require('./prefixer'),
|
||||
time: require('./time'),
|
||||
|
|
|
@ -12,17 +12,23 @@ const UserModel = {
|
|||
|
||||
const users = [
|
||||
{name: 'Javi Jimenez', twitter: '@soyjavi', birthdate: new Date(1980, 3, 11), cats: 1},
|
||||
{name: 'Javi Velasco', twitter: '@javivelasco', birthdate: new Date(1987, 1, 1), dogs: 1, active: true}
|
||||
{name: 'Javi Velasco', twitter: '@javivelasco', birthdate: new Date(1987, 1, 1), dogs: 1, owner: true}
|
||||
];
|
||||
|
||||
class TableTest extends React.Component {
|
||||
|
||||
handleTableChange = (event, row, dataSource) => {
|
||||
console.log('handleTableChange', row, dataSource);
|
||||
state = {
|
||||
selected: [],
|
||||
source: users
|
||||
};
|
||||
|
||||
handleTableRowSelect = (event, selected) => {
|
||||
console.log('handleTableRowSelect', selected);
|
||||
handleChange = (row, key, value) => {
|
||||
const source = this.state.source;
|
||||
source[row][key] = value;
|
||||
this.setState({source});
|
||||
};
|
||||
|
||||
handleSelect = (selected) => {
|
||||
this.setState({selected});
|
||||
};
|
||||
|
||||
render () {
|
||||
|
@ -32,9 +38,10 @@ class TableTest extends React.Component {
|
|||
<p style={{marginBottom: '10px'}}>Organized data.</p>
|
||||
<Table
|
||||
model={UserModel}
|
||||
dataSource={users}
|
||||
onChange={this.handleTableChange}
|
||||
onSelect={this.handleTableRowSelect}
|
||||
onChange={this.handleChange}
|
||||
onSelect={this.handleSelect}
|
||||
selected={this.state.selected}
|
||||
source={this.state.source}
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
|
|
Loading…
Reference in New Issue