<!DOCTYPE html>
<html>
<head>
<script>
console.clear()
</script>
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.3.0/semantic.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.3.0/semantic.min.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/3.7.2/redux.min.js"></script>
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/5.0.7/react-redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux-thunk/2.2.0/redux-thunk.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Faker/3.1.0/faker.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/json-schema-faker/0.5.0-rc13/json-schema-faker.js"></script>
<script src="./schema.js"></script>
<link rel="stylesheet" href="./style.css">
<script src="./store.js"></script>
</head>
<body>
<div class="ui container">
<div class="ui raised segment">
<div id="main"></div>
</div>
</div>
<script src="script.js"></script>
</body>
</html>
// Code goes here
const {Provider, connect} = ReactRedux;
const {createStore, applyMiddleware} = Redux;
const thunk = ReduxThunk.default;
const store = Redux.createStore(rootReducer, initialState, applyMiddleware(thunk));
const sortDirs = {
'asc': 'desc',
'desc': 'asc'
}
const sortFactors = {
'asc': -1,
'desc': 1
}
const ColumnHeader = ({column, sort, onclick}) => {
const sortClass = sort.by === column.name ? 'sorted-' + sort.dir : '';
return <th
className={'sortable ' + sortClass}
onClick={onclick}>
{column.display}
</th>;
}
const ItemRow = ({columns, item}) => {
return (
<tr>{columns.map((c,i) => {
const className = c.name === 'distance' ? (item[c.name] > 250 ? 'positive': 'negative') : '';
return <td key={c.name} className={className}>{item[c.name]}</td>
})}
</tr>
);
}
const ColumnFilter = ({columns, onchange}) => {
return (
<tr>{columns.map(c => {
return <td key={c.name}><div className="ui mini input">
<input type="search" onChange={onchange}/>
</div>
</td>
})}</tr>
);
}
class Items extends React.Component {
componentDidMount() {
this.props.fetchData();
}
sortByColumn(col) {
const {items, sort} = this.props;
this.props.sortItems(items, sort, col);
}
onFilterInput() {
console.log('todo');
}
render() {
const {items, columns, sort} = this.props;
const headers = columns.map(c =>
<ColumnHeader
key={c.name}
column={c}
sort={sort}
onclick={() => this.sortByColumn(c)}
/>
);
const rows = items.map(i => <ItemRow
item={i}
columns={columns}
key={i.address}
/>
);
const filters = <ColumnFilter columns={columns} onchange={this.onFilterInput}/>
return (
<table className="ui compact selectable table">
<thead>
<tr>{headers}</tr>
</thead>
<tbody className="filters">
{filters}
</tbody>
<tbody>{rows}</tbody>
</table>
)
}
}
const mapStateToProps = (state) => {
return {
items: state.items,
columns: state.columns,
sort: state.sort
};
};
const mapDispatchToProps = (dispatch) => {
return {
fetchData: (url) => dispatch(itemsFetchData(url)),
sortItems: (items, sortBy, col) => dispatch(itemsSort(items, sortBy, col))
};
};
const ItemsTable = connect(mapStateToProps, mapDispatchToProps)(Items);
const App = () => <Provider store={store}><ItemsTable/></Provider>
ReactDOM.render(<App/>, $("#main")[0]);
/* Styles go here */
.ui.table > thead > tr > th {
cursor: pointer;
&:hover {
background-color: #e6e6e6;
}
}
th.sortable.sorted-asc:before {
content: '\f0dd';
}
th.sortable.sorted-desc:before {
content: '\f0de';
}
th.sortable:before {
font-family: "Icons";
margin-right: .3em;
content: '\f0dc';
}
th:not(.sorted-asc):not(.sorted-desc):before {
opacity: .2;
}
#main {height: calc(100vh - 100px);}
table {
display: flex;
flex-flow: column;
height: 100%;
width: 100%;
}
table thead {
/* head takes the height it requires,
and it's not scaled when table is resized */
flex: 0 0 auto;
width: calc(100% - 0.9em);
}
table tbody.filters {
flex: 0 0 auto;
overflow-y: auto;
}
table tbody {
/* body takes all the remaining available space */
flex: 1 1 auto;
display: block;
overflow-y: auto;
}
table tbody tr {
width: 100%;
}
table thead, table tbody tr {
display: table;
table-layout: fixed;
}
tbody.filters .ui.input > input {
padding: .3em;
}
const schema = {
"id": "User",
"type": "array",
"minItems": 100,
"maxItems": 500,
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"faker": "name.firstName",
"minLength": 3,
"unique": true
},
"address": {
"type": "string",
"faker": "address.streetAddress"
},
"distance": {
"type": "integer",
"minimum": 1,
"maximum": 500
},
"group": {
"type": "string",
"enum": ["A", "B", "C"]
}
},
"required": [
"name",
"address",
"distance",
"group"
]
}
}
JSONSchemaFaker.extend('faker', () => faker);
const {combineReducers} = Redux;
const initialState = {
items: [],
columns: [{name: "name", display: "Name"}, {name: "address", display: "Address"}, {name: "distance", display: "Distance"}, {name:"group", display: "Group"}],
sort: {by: '', dir: 'asc'}
}
function items(state=[], action) {
switch (action.type) {
case 'ITEMS_FETCH_DATA_SUCCESS':
return action.items;
default:
return state;
}
}
function columns(state=initialState.columns, action) {
return state;
}
function sort(state=initialState.sort, action) {
switch (action.type) {
case 'SET_SORT':
return action.sort;
default:
return state;
}
}
const rootReducer = combineReducers({items, columns, sort});
function itemsFetchDataSuccess(items) {
return {
type: 'ITEMS_FETCH_DATA_SUCCESS',
items
};
}
function itemsFetchData(url) {
return (dispatch) => {
setTimeout(() => {
dispatch(itemsFetchDataSuccess(JSONSchemaFaker(schema)))
}, 1000)
};
}
function setSort(sort) {
return {
type: 'SET_SORT',
sort
}
}
function itemsSort(items, sort, col) {
return dispatch => {
const newItems = [...items];
const sortDir = sort.by === col.name ? sortDirs[sort.dir] : sort.dir;
const sortDirFactor = sortFactors[sortDir];
newItems.sort((i1, i2) => {
return i1[col.name] > i2[col.name] ? sortDirFactor : -sortDirFactor;
})
dispatch(itemsFetchDataSuccess(newItems));
dispatch(setSort({by: col.name, dir: sortDir}));
}
}