Sorting and searching are working
This commit is contained in:
+1
-1
@@ -7,6 +7,6 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id = "root"></div>
|
<div id = "root"></div>
|
||||||
<script src="/app.bundle.js"></script>
|
<script src="/app.bundle.js?v=3"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
+2
-1
@@ -1,6 +1,6 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { BrowserRouter, Switch, Route, Link } from "react-router-dom";
|
import { BrowserRouter, Switch, Route, Link } from "react-router-dom";
|
||||||
import { Header, Container } from "semantic-ui-react";
|
import { Header, Container, Divider } from "semantic-ui-react";
|
||||||
|
|
||||||
//include styles
|
//include styles
|
||||||
import "./styles/shared.css";
|
import "./styles/shared.css";
|
||||||
@@ -34,6 +34,7 @@ class App extends React.Component {
|
|||||||
<LinkButton to="/concepts">Concepts</LinkButton>
|
<LinkButton to="/concepts">Concepts</LinkButton>
|
||||||
<LinkButton to="/about">About</LinkButton>
|
<LinkButton to="/about">About</LinkButton>
|
||||||
</LinkButton.Group>
|
</LinkButton.Group>
|
||||||
|
<Divider hidden />
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route exact path="/" component={ Landing } />
|
<Route exact path="/" component={ Landing } />
|
||||||
<Route exact path="/rules" component={ Rules } />
|
<Route exact path="/rules" component={ Rules } />
|
||||||
|
|||||||
+50
-12
@@ -1,13 +1,17 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { Table } from "semantic-ui-react";
|
import { Table, Input, Icon } from "semantic-ui-react";
|
||||||
import Papa from "papaparse";
|
import Papa from "papaparse";
|
||||||
|
import tsorter from "../utilities/tsorter.js";
|
||||||
|
|
||||||
class CardList extends React.Component {
|
class CardList extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
body: "loading..."
|
blob: null, //response from the server
|
||||||
|
body: "loading...", //parsed table
|
||||||
|
tsorter: null, //sorting tool
|
||||||
|
query: "" //search query
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -15,27 +19,26 @@ class CardList extends React.Component {
|
|||||||
fetch("/content/card_list.csv")
|
fetch("/content/card_list.csv")
|
||||||
.then(result => result.text())
|
.then(result => result.text())
|
||||||
.then(result => Papa.parse(result, { delimiter: ";" }).data)
|
.then(result => Papa.parse(result, { delimiter: ";" }).data)
|
||||||
.then(result => {
|
.then(result => { this.setState({ blob: result }); return result; })
|
||||||
let headers = result[0];
|
.then(result => this.generateTableJSX(result[0], result.slice(1)))
|
||||||
let content = result.slice(1);
|
|
||||||
|
|
||||||
return this.generateTableJSX(headers, content);
|
|
||||||
})
|
|
||||||
.then(result => this.setState({ body: result }))
|
.then(result => this.setState({ body: result }))
|
||||||
|
.then(() => this.setState({ tsorter: tsorter.create("cardtable") })); //NOTE: tsorter operates on raw HTML, so the body must be set first
|
||||||
}
|
}
|
||||||
|
|
||||||
generateTableJSX(headers, content) {
|
generateTableJSX(headers, content) {
|
||||||
headers = headers.map((h, i) => <Table.HeaderCell key={i}>{h}</Table.HeaderCell>);
|
headers = headers.map((h, i) => <Table.HeaderCell key={i}>{h}</Table.HeaderCell>);
|
||||||
|
|
||||||
content = content.map((r, i) => {
|
content = content.filter((r) => !r.hidden); //filter out hidden content
|
||||||
|
content = content.map((r, i) => { //convert the content to rows and cells
|
||||||
r = r.map((c, j) => {
|
r = r.map((c, j) => {
|
||||||
return <Table.Cell key={j}>{c}</Table.Cell>;
|
return <Table.Cell key={j}>{c || " "}</Table.Cell>; //NOTE: c || " " because tsorter doesn't like empty strings
|
||||||
})
|
})
|
||||||
return <tr key={i}>{r}</tr>;
|
return <tr key={i} display={r.hidden ? "none" : "inline"}>{r}</tr>;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
//NOTE: id must match tsorter's argument above
|
||||||
return (
|
return (
|
||||||
<Table unstackable>
|
<Table unstackable id={"cardtable"}>
|
||||||
<Table.Header>
|
<Table.Header>
|
||||||
<Table.Row>
|
<Table.Row>
|
||||||
{headers}
|
{headers}
|
||||||
@@ -48,9 +51,44 @@ class CardList extends React.Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateQuery(evt) {
|
||||||
|
this.setState({ query: evt.target.value });
|
||||||
|
|
||||||
|
//search the table (hide all rows in the blob, then reveal the appropriate ones)
|
||||||
|
let blob = JSON.parse(JSON.stringify(this.state.blob));
|
||||||
|
|
||||||
|
blob.map((r, i) => {
|
||||||
|
r.hidden = true;
|
||||||
|
|
||||||
|
r.map((c, j) => {
|
||||||
|
if (c.toLowerCase().indexOf(evt.target.value.toLowerCase()) > -1) {
|
||||||
|
r.hidden = false;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
//don't hide the title row
|
||||||
|
blob[0].hidden = false;
|
||||||
|
|
||||||
|
//find a change
|
||||||
|
for (let i = 0; i < blob.length; i++) {
|
||||||
|
if (blob[i].hidden != this.state.blob[i].hidden) {
|
||||||
|
//update the table on a change
|
||||||
|
this.setState({ blob: blob, body: this.generateTableJSX(blob[0], blob.slice(1)) });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
<div className="rightContent">
|
||||||
|
<Input icon placeholder="Search...">
|
||||||
|
<input value={this.state.query} onChange={this.updateQuery.bind(this)} />
|
||||||
|
<Icon name="search" />
|
||||||
|
</Input>
|
||||||
|
</div>
|
||||||
{this.state.body}
|
{this.state.body}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -47,3 +47,27 @@ footer {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* centering */
|
||||||
|
.centeredContent {
|
||||||
|
flex: 0 1 auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rightContent {
|
||||||
|
flex: 0 1 auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Up and Down Arrows */
|
||||||
|
.descend:after{
|
||||||
|
content: "\25B2";
|
||||||
|
}
|
||||||
|
|
||||||
|
.ascend:after{
|
||||||
|
content: "\25BC";
|
||||||
|
}
|
||||||
@@ -0,0 +1,252 @@
|
|||||||
|
/*!
|
||||||
|
* tsorter 2.0.0 - Copyright 2015 Terrill Dent, http://terrill.ca
|
||||||
|
* JavaScript HTML Table Sorter
|
||||||
|
* Released under MIT license, http://terrill.ca/sorting/tsorter/LICENSE
|
||||||
|
*/
|
||||||
|
var tsorter = (function()
|
||||||
|
{
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var sorterPrototype,
|
||||||
|
addEvent,
|
||||||
|
removeEvent,
|
||||||
|
hasEventListener = !!document.addEventListener;
|
||||||
|
|
||||||
|
if( !Object.create ){
|
||||||
|
// Define Missing Function
|
||||||
|
Object.create = function( prototype ) {
|
||||||
|
var Obj = function(){return undefined;};
|
||||||
|
Obj.prototype = prototype;
|
||||||
|
return new Obj();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cross Browser event binding
|
||||||
|
addEvent = function( element, eventName, callback ) {
|
||||||
|
if( hasEventListener ) {
|
||||||
|
element.addEventListener(eventName, callback, false );
|
||||||
|
} else {
|
||||||
|
element.attachEvent( 'on' + eventName, callback);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Cross Browser event removal
|
||||||
|
removeEvent = function( element, eventName, callback ) {
|
||||||
|
if( hasEventListener ) {
|
||||||
|
element.removeEventListener(eventName, callback, false );
|
||||||
|
} else {
|
||||||
|
element.detachEvent( 'on' + eventName, callback);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
sorterPrototype = {
|
||||||
|
|
||||||
|
getCell: function(row)
|
||||||
|
{
|
||||||
|
var that = this;
|
||||||
|
return that.trs[row].cells[that.column];
|
||||||
|
},
|
||||||
|
|
||||||
|
/* SORT
|
||||||
|
* Sorts a particular column. If it has been sorted then call reverse
|
||||||
|
* if not, then use quicksort to get it sorted.
|
||||||
|
* Sets the arrow direction in the headers.
|
||||||
|
* @param oTH - the table header cell (<th>) object that is clicked
|
||||||
|
*/
|
||||||
|
sort: function( e )
|
||||||
|
{
|
||||||
|
var that = this,
|
||||||
|
th = e.target;
|
||||||
|
|
||||||
|
// TODO: make sure target 'th' is not a child element of a <th>
|
||||||
|
// We can't use currentTarget because of backwards browser support
|
||||||
|
// IE6,7,8 don't have it.
|
||||||
|
|
||||||
|
// set the data retrieval function for this column
|
||||||
|
that.column = th.cellIndex;
|
||||||
|
that.get = that.getAccessor( th.getAttribute('data-tsorter') );
|
||||||
|
|
||||||
|
if( that.prevCol === that.column )
|
||||||
|
{
|
||||||
|
// if already sorted, reverse
|
||||||
|
th.className = th.className !== 'descend' ? 'descend' : 'ascend';
|
||||||
|
that.reverseTable();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// not sorted - call quicksort
|
||||||
|
th.className = 'descend';
|
||||||
|
if( that.prevCol !== -1 && that.ths[that.prevCol].className !== 'exc_cell'){
|
||||||
|
that.ths[that.prevCol].className = '';
|
||||||
|
}
|
||||||
|
that.quicksort(0, that.trs.length);
|
||||||
|
}
|
||||||
|
that.prevCol = that.column;
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Choose Data Accessor Function
|
||||||
|
* @param: the html structure type (from the data-type attribute)
|
||||||
|
*/
|
||||||
|
getAccessor: function(sortType)
|
||||||
|
{
|
||||||
|
var that = this,
|
||||||
|
accessors = that.accessors;
|
||||||
|
|
||||||
|
if( accessors && accessors[ sortType ] ){
|
||||||
|
return accessors[ sortType ];
|
||||||
|
}
|
||||||
|
|
||||||
|
switch( sortType )
|
||||||
|
{
|
||||||
|
case "link":
|
||||||
|
return function(row){
|
||||||
|
return that.getCell(row).firstChild.firstChild.nodeValue;
|
||||||
|
};
|
||||||
|
case "input":
|
||||||
|
return function(row){
|
||||||
|
return that.getCell(row).firstChild.value;
|
||||||
|
};
|
||||||
|
case "numeric":
|
||||||
|
return function(row){
|
||||||
|
return parseFloat( that.getCell(row).firstChild.nodeValue.replace(/\D/g,''), 10 );
|
||||||
|
};
|
||||||
|
default: /* Plain Text */
|
||||||
|
return function(row){
|
||||||
|
return that.getCell(row).firstChild.nodeValue.toLowerCase();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Exchange
|
||||||
|
* A complicated way of exchanging two rows in a table.
|
||||||
|
* Exchanges rows at index i and j
|
||||||
|
*/
|
||||||
|
exchange: function(i, j)
|
||||||
|
{
|
||||||
|
var that = this,
|
||||||
|
tbody = that.tbody,
|
||||||
|
trs = that.trs,
|
||||||
|
tmpNode;
|
||||||
|
|
||||||
|
if( i === j+1 ) {
|
||||||
|
tbody.insertBefore(trs[i], trs[j]);
|
||||||
|
} else if( j === i+1 ) {
|
||||||
|
tbody.insertBefore(trs[j], trs[i]);
|
||||||
|
} else {
|
||||||
|
tmpNode = tbody.replaceChild(trs[i], trs[j]);
|
||||||
|
if( !trs[i] ) {
|
||||||
|
tbody.appendChild(tmpNode);
|
||||||
|
} else {
|
||||||
|
tbody.insertBefore(tmpNode, trs[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
* REVERSE TABLE
|
||||||
|
* Reverses a table ordering
|
||||||
|
*/
|
||||||
|
reverseTable: function()
|
||||||
|
{
|
||||||
|
var that = this,
|
||||||
|
i;
|
||||||
|
|
||||||
|
for( i = 1; i < that.trs.length; i++ ) {
|
||||||
|
that.tbody.insertBefore( that.trs[i], that.trs[0] );
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
* QUICKSORT
|
||||||
|
* @param: lo - the low index of the array to sort
|
||||||
|
* @param: hi - the high index of the array to sort
|
||||||
|
*/
|
||||||
|
quicksort: function(lo, hi)
|
||||||
|
{
|
||||||
|
var i, j, pivot,
|
||||||
|
that = this;
|
||||||
|
|
||||||
|
if( hi <= lo+1 ){ return; }
|
||||||
|
|
||||||
|
if( (hi - lo) === 2 ) {
|
||||||
|
if(that.get(hi-1) > that.get(lo)) {
|
||||||
|
that.exchange(hi-1, lo);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
i = lo + 1;
|
||||||
|
j = hi - 1;
|
||||||
|
|
||||||
|
if( that.get(lo) > that.get( i) ){ that.exchange( i, lo); }
|
||||||
|
if( that.get( j) > that.get(lo) ){ that.exchange(lo, j); }
|
||||||
|
if( that.get(lo) > that.get( i) ){ that.exchange( i, lo); }
|
||||||
|
|
||||||
|
pivot = that.get(lo);
|
||||||
|
|
||||||
|
while(true) {
|
||||||
|
j--;
|
||||||
|
while(pivot > that.get(j)){ j--; }
|
||||||
|
i++;
|
||||||
|
while(that.get(i) > pivot){ i++; }
|
||||||
|
if(j <= i){ break; }
|
||||||
|
that.exchange(i, j);
|
||||||
|
}
|
||||||
|
that.exchange(lo, j);
|
||||||
|
|
||||||
|
if((j-lo) < (hi-j)) {
|
||||||
|
that.quicksort(lo, j);
|
||||||
|
that.quicksort(j+1, hi);
|
||||||
|
} else {
|
||||||
|
that.quicksort(j+1, hi);
|
||||||
|
that.quicksort(lo, j);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
init: function( table, initialSortedColumn, customDataAccessors ){
|
||||||
|
var that = this,
|
||||||
|
i;
|
||||||
|
|
||||||
|
if( typeof table === 'string' ){
|
||||||
|
table = document.getElementById(table);
|
||||||
|
}
|
||||||
|
|
||||||
|
that.table = table;
|
||||||
|
that.ths = table.getElementsByTagName("th");
|
||||||
|
that.tbody = table.tBodies[0];
|
||||||
|
that.trs = that.tbody.getElementsByTagName("tr");
|
||||||
|
that.prevCol = ( initialSortedColumn && initialSortedColumn > 0 ) ? initialSortedColumn : -1;
|
||||||
|
that.accessors = customDataAccessors;
|
||||||
|
that.boundSort = that.sort.bind( that );
|
||||||
|
|
||||||
|
for( i = 0; i < that.ths.length; i++ ) {
|
||||||
|
addEvent( that.ths[i], 'click', that.boundSort );
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
destroy: function(){
|
||||||
|
var that = this,
|
||||||
|
i;
|
||||||
|
|
||||||
|
if( that.ths ){
|
||||||
|
for( i = 0; i < that.ths.length; i++ ) {
|
||||||
|
removeEvent( that.ths[i], 'click', that.boundSort );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create a new sorter given a table element
|
||||||
|
return {
|
||||||
|
create: function( table, initialSortedColumn, customDataAccessors )
|
||||||
|
{
|
||||||
|
var sorter = Object.create( sorterPrototype );
|
||||||
|
sorter.init( table, initialSortedColumn, customDataAccessors );
|
||||||
|
return sorter;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}());
|
||||||
|
|
||||||
|
export default tsorter;
|
||||||
Reference in New Issue
Block a user