├── .gitignore ├── .travis.yml ├── README.md ├── example ├── big.csv ├── example.csv ├── example.gif ├── index.css ├── index.js └── transactions.csv ├── index.html ├── index.js ├── lib ├── tbody.js └── update.js ├── package.json └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '4' 4 | before_script: 5 | - export DISPLAY=:99.0; sh -e /etc/init.d/xvfb start 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # csv-viewer [![Build Status](http://img.shields.io/travis/shama/csv-viewer.svg)](https://travis-ci.org/shama/csv-viewer) 2 | 3 | A WIP CSV viewer element. 4 | 5 | ![example](https://raw.githubusercontent.com/shama/csv-viewer/master/example/example.gif) 6 | 7 | ## Installing/Running 8 | 9 | ```shell 10 | git clone git://github.com/shama/csv-viewer && cd csv-viewer 11 | npm i 12 | npm start 13 | ``` 14 | 15 | Visit `http://localhost:9966` 16 | 17 | ## Example 18 | 19 | [https://shama.github.io/csv-viewer](https://shama.github.io/csv-viewer) 20 | 21 | ## Usage 22 | 23 | ```js 24 | var viewer = require('csv-viewer') 25 | 26 | // Get some CSV data 27 | var csv = [ 28 | ['Name', 'Address', 'Phone'], 29 | ['Grizzly', '123 Fake St', '707-123-4567'], 30 | ] 31 | 32 | // Build the element and attach to page 33 | var element = viewer(csv) 34 | document.body.appendChild(element) 35 | ``` 36 | -------------------------------------------------------------------------------- /example/example.csv: -------------------------------------------------------------------------------- 1 | "Name","Address","Phone" 2 | "Max","123 Real St","415-123-4567" 3 | "Karissa","123 Some St","000-123-4567" 4 | "Mathias","123 Dat St","800-123-4567" 5 | "Kyle","123 Fake St","707-123-4567" 6 | -------------------------------------------------------------------------------- /example/example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shama/csv-viewer/ebec1d61bf8df3172770cda1b267c20ccf47d23a/example/example.gif -------------------------------------------------------------------------------- /example/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-style: normal; 3 | font-weight: 400; 4 | font-size: 14px; 5 | background-color: #ecf0f1; 6 | color: #2c3e50; 7 | font-family: 'Lucida Grande', Helvetica, Arial; 8 | margin: 0; 9 | box-sizing: border-box; 10 | } 11 | button { 12 | background: transparent; 13 | border: none; 14 | cursor: pointer; 15 | } 16 | button:hover { 17 | color: #CFD8DC; 18 | } 19 | button:focus { 20 | outline: none; 21 | } 22 | 23 | nav { 24 | background-color: #2980b9; 25 | width: 100%; 26 | padding: 1em; 27 | margin-bottom: 1em; 28 | color: #ecf0f1; 29 | } 30 | nav h3 { 31 | width: auto; 32 | float: left; 33 | margin: 0; 34 | padding-right: 1em; 35 | line-height: 1em; 36 | } 37 | nav button { 38 | text-decoration: underline; 39 | padding: 0 1em; 40 | color: #ecf0f1; 41 | } 42 | 43 | .contents { 44 | width: 90%; 45 | margin: 0 auto; 46 | } 47 | -------------------------------------------------------------------------------- /example/index.js: -------------------------------------------------------------------------------- 1 | var csvViewer = require('../index.js') 2 | var yo = require('yo-yo') 3 | var createRouter = require('base-router') 4 | var parseCSV = require('babyparse').parse 5 | var nets = require('nets') 6 | var update = require('../lib/update.js') 7 | 8 | // Create a route that loads our model 9 | var router = createRouter({ 10 | '/:file': function (params, done) { 11 | nets({ 12 | url: 'example/' + (params.file || 'example.csv') 13 | }, function (err, res, csv) { 14 | csv = parseCSV(csv.toString()) 15 | // TODO: Check for errors in csv 16 | done(err, csv) 17 | }) 18 | } 19 | }, { location: 'hash' }) 20 | 21 | // Create a loading state 22 | var loading = yo`
Loading files....
` 23 | router.on('loading', function () { 24 | update('.app', render(loading)) 25 | }) 26 | 27 | // On successful transitions, render the app 28 | router.on('transition', function (router, csv) { 29 | var rows = csv.data 30 | update('.app', render(csvViewer(rows), rows.length)) 31 | }) 32 | 33 | // Main application 34 | function render (contents, total) { 35 | var nav = [ 36 | 'example.csv', 37 | 'big.csv', 38 | 'transactions.csv' 39 | ] 40 | return yo`
41 | 49 |
50 | ${contents} 51 |

Total Rows: ${total || '?'} 52 |

53 |
` 54 | } 55 | 56 | // Initial render 57 | var app = render(loading) 58 | document.body.appendChild(app) 59 | 60 | // Start by going to example.csv 61 | router.transitionTo('/') 62 | -------------------------------------------------------------------------------- /example/transactions.csv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shama/csv-viewer/ebec1d61bf8df3172770cda1b267c20ccf47d23a/example/transactions.csv -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var yo = require('yo-yo') 2 | var csjs = require('csjs') 3 | var update = require('./lib/update.js') 4 | var tbody = require('./lib/tbody.js') 5 | 6 | module.exports = function csvViewer (data, opts) { 7 | console.time('csvViewer') 8 | var headerRow = data.splice(0, 1)[0] 9 | var asc = true 10 | var sortByIndex = 0 11 | var element = render(data) 12 | console.timeEnd('csvViewer') 13 | return element 14 | 15 | function render (data) { 16 | return yo`
17 | ${thead(headerRow)} 18 | ${tbody(data)} 19 |
` 20 | } 21 | 22 | function thead (row) { 23 | return yo`
24 |
25 | ${row.map(function (col, idx) { 26 | var icon = '' 27 | if (idx === sortByIndex) { 28 | icon = (asc) ? 'fa-caret-down' : 'fa-caret-up' 29 | icon = yo`` 30 | } 31 | return yo`
32 | 35 |
` 36 | })} 37 |
38 |
` 39 | } 40 | 41 | function sort (idx) { 42 | asc = !asc 43 | sortByIndex = idx 44 | data = data.sort(function (a, b) { 45 | var x = a[sortByIndex] || '' 46 | var y = b[sortByIndex] || '' 47 | return (asc) ? x.localeCompare(y) : y.localeCompare(x) 48 | }) 49 | update('.' + className, render(data)) 50 | } 51 | } 52 | 53 | var styles = module.exports.styles = csjs` 54 | .csv-viewer { 55 | table-layout: fixed; 56 | border-collapse: collapse; 57 | width: 100%; 58 | } 59 | .row { 60 | display: flex; 61 | flex-wrap: wrap; 62 | } 63 | .th { 64 | flex: 1; 65 | background-color: #27ae60; 66 | border: 1px solid #27ae60; 67 | padding: .5em; 68 | text-overflow: ellipsis; 69 | overflow: hidden; 70 | } 71 | .csv-viewer .tr:nth-child(even) { 72 | background-color: #F5F5F5; 73 | } 74 | ` 75 | var className = styles['csv-viewer'] 76 | -------------------------------------------------------------------------------- /lib/tbody.js: -------------------------------------------------------------------------------- 1 | var objectAssign = require('object-assign') 2 | var yo = require('yo-yo') 3 | var csjs = require('csjs') 4 | var domcss = require('dom-css') 5 | var update = require('./update.js') 6 | 7 | module.exports = function tbody (rows, opts) { 8 | console.time('tbody') 9 | opts = objectAssign({ 10 | height: 500, 11 | rowHeight: 30 12 | }, opts) 13 | var scrollTop = 0 14 | var visibleStart = 0 15 | var visibleEnd = 0 16 | var displayStart = 0 17 | var displayEnd = 0 18 | var scrollTop = 0 19 | var element = render(partialRows(rows, scrollTop)) 20 | console.timeEnd('tbody') 21 | return element 22 | 23 | function render (rows) { 24 | var tbody = yo`
25 | ${toprow()} 26 | ${rows.map(function (row) { 27 | if (!row || row.length < 1 || row[0].length <= 1) return '' 28 | return yo`
29 | ${row.map(function (col) { 30 | return yo`
${col}
` 31 | })} 32 |
` 33 | })} 34 | ${bottomrow()} 35 |
` 36 | return tbody 37 | } 38 | 39 | function onscroll () { 40 | var section = partialRows(rows, this.scrollTop) 41 | update('.' + styles.tbody, render(section)) 42 | } 43 | 44 | function toprow () { 45 | var row = yo`
` 46 | domcss(row, 'height', displayStart * opts.rowHeight) 47 | return row 48 | } 49 | 50 | function bottomrow () { 51 | var row = yo`
` 52 | domcss(row, 'height', (rows.length - displayEnd) * opts.rowHeight) 53 | return row 54 | } 55 | 56 | function partialRows (rows, scrollTop) { 57 | var total = rows.length 58 | var rowsPerBody = Math.floor((opts.height - 2) / opts.rowHeight) 59 | visibleStart = Math.round(Math.floor(scrollTop / opts.rowHeight)) 60 | visibleEnd = Math.round(Math.min(visibleStart + rowsPerBody)) 61 | displayStart = Math.round(Math.max(0, Math.floor(scrollTop / opts.rowHeight) - rowsPerBody * 1.5)) 62 | displayEnd = Math.round(Math.min(displayStart + 4 * rowsPerBody, total)) 63 | return rows.slice(displayStart, displayEnd) 64 | } 65 | } 66 | 67 | var styles = module.exports.styles = csjs` 68 | .tbody { 69 | overflow: auto; 70 | height: 500px; 71 | } 72 | .row { 73 | display: flex; 74 | flex-wrap: wrap; 75 | } 76 | .td { 77 | flex: 1; 78 | border: 1px solid #27ae60; 79 | padding: .5em; 80 | text-overflow: ellipsis; 81 | overflow: hidden; 82 | } 83 | .row:nth-child(even) { 84 | background-color: #F5F5F5; 85 | } 86 | ` 87 | -------------------------------------------------------------------------------- /lib/update.js: -------------------------------------------------------------------------------- 1 | var document = require('global/document') 2 | var yo = require('yo-yo') 3 | 4 | module.exports = function update (f, t) { 5 | if (typeof f === 'string') { 6 | f = document.querySelector(f) 7 | } 8 | yo.update(f, t) 9 | } 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "csv-viewer", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "wzrd example/index.js:bundle.js -- -t csjs-injectify", 8 | "test": "standard && browserify test.js | testron", 9 | "build": "browserify example/index.js -o bundle.js", 10 | "deploy": "gh-pages-deploy" 11 | }, 12 | "author": "Kyle Robinson Young (http://dontkry.com)", 13 | "license": "MIT", 14 | "dependencies": { 15 | "babyparse": "^0.4.3", 16 | "dom-css": "^2.0.0", 17 | "global": "^4.3.0", 18 | "normalize.css": "^3.0.3", 19 | "object-assign": "^4.0.1", 20 | "yo-yo": "^1.1.1" 21 | }, 22 | "devDependencies": { 23 | "base-router": "^1.1.0", 24 | "browserify": "^13.0.0", 25 | "csjs": "^1.0.0", 26 | "csjs-injectify": "^1.0.0", 27 | "dat-browserify": "git+https://github.com/karissa/dat-browserify.git", 28 | "electron-prebuilt": "^0.36.9", 29 | "font-awesome": "^4.5.0", 30 | "gh-pages-deploy": "^0.4.0", 31 | "nets": "^3.2.0", 32 | "normalize.css": "^3.0.3", 33 | "standard": "^6.0.7", 34 | "tape": "^4.4.0", 35 | "testron": "^1.2.0", 36 | "wzrd": "^1.3.1", 37 | "yo-yoify": "^1.0.2" 38 | }, 39 | "browserify": { 40 | "transform": ["yo-yoify"] 41 | }, 42 | "gh-pages-deploy": { 43 | "prep": [ 44 | "build" 45 | ], 46 | "noprompt": false 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | var test = require('tape') 2 | var explorer = require('./index.js') 3 | 4 | test('basically works', function (t) { 5 | t.plan(2) 6 | var files = [ 7 | { 8 | path: 'bears', 9 | type: 'folder', 10 | mtime: new Date(), 11 | children: [ 12 | { 13 | path: 'bears/grizzly.js', 14 | type: 'file', 15 | mtime: new Date() 16 | } 17 | ] 18 | } 19 | ] 20 | var element = explorer(files) 21 | var treeButton = element.querySelector('.fs-explorer-tree button') 22 | t.equal(treeButton.textContent, 'bears', 'tree should display the folder name') 23 | t.equal(treeButton.className, 'folder', 'tree should have the class folder on folders') 24 | t.end() 25 | }) 26 | --------------------------------------------------------------------------------