Display a string of comma-separated values as a columnar table with a user-selectable number of columns.
The app responds to input dynamically, validating input and generating the columnar table on the fly.
To install dependencies locally, issue yarn install
.
To start a server locally, issue yarn start
.
<CSVForm />
: The CSV string submission form<ResultsTable />
: The table generated upon changes to the input<CSVTransformer />
: The "controller" mediating interactions between the child components and the modules below.
// src/CSVTransformer.js L21-L38 (4ddf0c9f)
render () {
const table = ColumnarTable.fromValues({
valuesList: this.state.inputValues,
numberOfColumns: this.state.selectedNumberOfColumns
})
return (
<div className='App'>
<CSVForm
minColumns={this.MIN_COLS}
maxColumns={this.MAX_COLS}
maxEntries={this.MAX_CSV_ENTRIES}
updateSharedState={this.setState.bind(this)} />
<ResultsTable tableBody={table} />
</div>
)
}
ColumnarTable
// src/ColumnarTable.js L6-L27 (d36ac344)
//
// Generate a columnar table (as an Array of Arrays) of width
// `numberOfColumns` (an Integer) from `valuesList`, expected to be a
// 1-dimensional Array.
//
fromValues: ({ valuesList, numberOfColumns }) => {
const sliceLength = Math.ceil(valuesList.length / numberOfColumns)
if (sliceLength < 1 || isNaN(sliceLength)) { return [] }
// partition values list into slices of length sliceLength
const slices = Enum.eachSlice(valuesList, sliceLength)
// transpose to make each slice into a column
const tableBody = _.zip(...slices)
// right-pad each row to the specified number of columns
// (so a <td> will be rendered for empty cells, for valid HTML)
tableBody.forEach(row => { row.length = numberOfColumns })
return tableBody
}
Enum
// src/Enum.js L1-L35 (d36ac344)
// Slice an enumerable `collection` into a list of lists, each sub-list of
// length at least `sliceLength`.
//
// Signature:
//
// Enum.eachSlice(obj, sliceLength)
//
// Example:
//
// >>> Enum.eachSlice([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 4)
// [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10]]
//
const Enum = {
eachSlice: (collection, sliceLength) => {
if (sliceLength < 1) {
throw new InvalidSliceSizeException(sliceLength)
}
const slicedList = []
const list = collection.map(e => e)
if (typeof list.slice === 'undefined') { return slicedList }
const numberOfSlices = Math.ceil(list.length / sliceLength)
for (let i = 0; i < numberOfSlices; i++) {
const startIdx = i * sliceLength
const endIdx = startIdx + sliceLength
const slice = list.slice(startIdx, endIdx)
slicedList.push(slice)
}
return slicedList
}
}
Tests are written using Jest and co-located with their implementation files.
To run tests locally, issue yarn test
from the project root.
// src/Enum.test.js L5-L15 (4ddf0c9f)
describe('Enum.eachSlice', () => {
describe('given a slice size that divides into the collection', () => {
it('slices into evenly sized sub-arrays', () => {
const originalArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
const expectedArray = [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10]]
const slicedArray = Enum.eachSlice(originalArray, 2)
assert.deepEqual(slicedArray, expectedArray)
})
})