It resolves #3
This commit is contained in:
parent
298ca64675
commit
7918cadcd9
12 changed files with 8667 additions and 173 deletions
38
README.md
38
README.md
|
|
@ -33,6 +33,12 @@ const csvData =[
|
|||
npm install react-csv --save;
|
||||
```
|
||||
|
||||
or , for non-node developers , you can use CDN directly :
|
||||
|
||||
```html
|
||||
<script src="" type="text/javascript"></script>
|
||||
```
|
||||
|
||||
# Components:
|
||||
|
||||
This package includes actually two components: `CSVLink` and `CSVDownload`.
|
||||
|
|
@ -91,6 +97,25 @@ data = json2csv(arrayOfLiteralObjects);
|
|||
> - The meaning of headers with data of type `String` data is only prepending those headers as the first line of CSV content.
|
||||
|
||||
|
||||
### - **separator** Props:
|
||||
|
||||
Following a request to add [this feature](https://github.com/abdennour/react-csv/issues/3) , from `1.0.1` release, `react-csv` supports `separator` props which is equals by default a comma `,` .
|
||||
|
||||
|
||||
```js
|
||||
import {CSVLink} from 'react-csv';
|
||||
|
||||
<CSVLink data={array} separator={";"}>
|
||||
Download me
|
||||
</CSVLink>
|
||||
/*
|
||||
This will generate CSV with ";" as delimiter (separator)
|
||||
|
||||
*/
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
## 1. CSVLink Component:
|
||||
|
|
@ -136,15 +161,14 @@ import {CSVDownload} from 'react-csv';
|
|||
# Contribution :
|
||||
|
||||
|
||||
### Run the sample :
|
||||
- Unit-tests must cover at least 90 % of code .
|
||||
|
||||
- Write documentation of the new class, function , method , attribute ..so on.. following JSDoc syntax.
|
||||
|
||||
- `cd react-csv/`
|
||||
- Add an example for the new feature to `sample-site`.
|
||||
|
||||
then :
|
||||
- `npm start` runs the [`sample-site`](sample-site/)
|
||||
|
||||
- `npm install`.
|
||||
- `npm docgen` generates documentation in HTML output.
|
||||
|
||||
- `npm start`
|
||||
|
||||
- browse : `http://localhost:8008`.
|
||||
- `npm cdn` generate a bundle to be used as CDN
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ const rt = process.cwd();
|
|||
const {version, name} = require(rt+'/package.json');
|
||||
const shell = require('child_process').execSync;
|
||||
const cmds ={
|
||||
browserify:(suffix =>`node ${rt}/node_modules/browserify/bin/cmd ${rt}/index.js -o ${rt}/cdn/${name}-${suffix}.js`) ,
|
||||
minify : (suffix => `node node_modules/uglify-js/bin/uglifyjs cdn/${name}-${suffix}.js -o cdn/${name}-${suffix}.min.js`)
|
||||
browserify:(suffix =>`node ${rt}/node_modules/browserify/bin/cmd ${rt}/cdn/window.js -o ${rt}/cdn/${name}-${suffix}.js`) ,
|
||||
// minify : (suffix => `node node_modules/uglify-js/bin/uglifyjs ${rt}/cdn/${name}-${suffix}.js -o ${rt}/cdn/${name}-${suffix}.min.js`)
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
4219
cdn/react-csv-1.0.1.js
Normal file
4219
cdn/react-csv-1.0.1.js
Normal file
File diff suppressed because it is too large
Load diff
4219
cdn/react-csv-latest.js
vendored
Normal file
4219
cdn/react-csv-latest.js
vendored
Normal file
File diff suppressed because it is too large
Load diff
3
cdn/window.js
Normal file
3
cdn/window.js
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
const {CSVLink, CSVDownload} = require('../lib/index');
|
||||
|
||||
window.ReactCSV = {CSVLink, CSVDownload};
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "react-csv",
|
||||
"version": "1.0.0-RC1",
|
||||
"version": "1.0.1",
|
||||
"description": "Build CSV files on the fly basing on Array/literal object of data ",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
|
|
|
|||
|
|
@ -14,10 +14,17 @@ class App extends React.Component {
|
|||
return (
|
||||
<div style={{padding: 10}}>
|
||||
<h1>react-csv</h1>
|
||||
<hr />
|
||||
<div>
|
||||
|
||||
Download CSV <CSVLink data={csvData}> here </CSVLink>.
|
||||
</div>
|
||||
<hr />
|
||||
|
||||
<div>
|
||||
|
||||
Download CSV with <code>;</code> as separator : <CSVLink data={csvData} separator=";"> here </CSVLink>.
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,10 @@ class CSVDownload extends React.Component {
|
|||
}
|
||||
|
||||
componentDidMount(){
|
||||
this.state.page = window.open(this.buildURI(this.props.data, this.props.headers), this.props.target, this.props.specs, this.props.replace);
|
||||
const {data, headers, separator, target, specs, replace} = this.props;
|
||||
this.state.page = window.open(
|
||||
this.buildURI(data, headers, separator), target, specs, replace
|
||||
);
|
||||
}
|
||||
|
||||
getWindow() {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
import {buildURI} from '../core';
|
||||
import {defaultProps, PropTypes, PropsNotForwarded} from '../metaProps';
|
||||
import {defaultProps, PropTypes} from '../metaProps';
|
||||
|
||||
class CSVLink extends React.Component {
|
||||
constructor(props) {
|
||||
|
|
@ -13,10 +13,10 @@ class CSVLink extends React.Component {
|
|||
}
|
||||
|
||||
render(){
|
||||
const {data, headers, children , ...rest} = this.props;
|
||||
const {data, headers, separator, children , ...rest} = this.props;
|
||||
return (
|
||||
<a {...rest}
|
||||
href={this.buildURI(data, headers)}>
|
||||
href={this.buildURI(data, headers, separator)}>
|
||||
{children}
|
||||
</a>
|
||||
)
|
||||
|
|
|
|||
28
src/core.js
28
src/core.js
|
|
@ -18,30 +18,30 @@ export const jsons2arrays = (jsons, headers) => {
|
|||
object[header] ? object[header] : ''))]
|
||||
};
|
||||
|
||||
export const joiner = ((data) =>
|
||||
data.map((row, index) => row.join(',')).join(`\n`)
|
||||
export const joiner = ((data,separator = ',') =>
|
||||
data.map((row, index) => row.join(separator)).join(`\n`)
|
||||
);
|
||||
|
||||
export const arrays2csv = ((data, headers) =>
|
||||
joiner(headers ? [headers, ...data] : data)
|
||||
export const arrays2csv = ((data, headers, separator) =>
|
||||
joiner(headers ? [headers, ...data] : data, separator)
|
||||
);
|
||||
|
||||
export const jsons2csv = ((data, headers) =>
|
||||
joiner(jsons2arrays(data, headers))
|
||||
export const jsons2csv = ((data, headers, separator) =>
|
||||
joiner(jsons2arrays(data, headers), separator)
|
||||
);
|
||||
|
||||
export const string2csv = ((data, headers) =>
|
||||
(headers) ? `${headers.join(`,`)}\n${data}`: data
|
||||
export const string2csv = ((data, headers, separator) =>
|
||||
(headers) ? `${headers.join(separator)}\n${data}`: data
|
||||
)
|
||||
|
||||
export const toCSV = (data, headers) => {
|
||||
if (isJsons(data)) return jsons2csv(data, headers);
|
||||
if (isArrays(data)) return arrays2csv(data, headers);
|
||||
if (typeof data ==='string') return string2csv(data, headers);
|
||||
export const toCSV = (data, headers, separator) => {
|
||||
if (isJsons(data)) return jsons2csv(data, headers, separator);
|
||||
if (isArrays(data)) return arrays2csv(data, headers, separator);
|
||||
if (typeof data ==='string') return string2csv(data, headers, separator);
|
||||
throw new TypeError(`Data should be a "String", "Array of arrays" OR "Array of objects" `);
|
||||
};
|
||||
|
||||
export const buildURI = ((data, headers) => encodeURI(
|
||||
`data:text/csv;charset=utf-8,${toCSV(data, headers)}`
|
||||
export const buildURI = ((data, headers, separator) => encodeURI(
|
||||
`data:text/csv;charset=utf-8,${toCSV(data, headers, separator)}`
|
||||
)
|
||||
);
|
||||
|
|
|
|||
|
|
@ -7,11 +7,12 @@ export const PropTypes = {
|
|||
React.PropTypes.array
|
||||
]).isRequired,
|
||||
headers: React.PropTypes.array,
|
||||
target: React.PropTypes.string
|
||||
target: React.PropTypes.string,
|
||||
separator: React.PropTypes.string
|
||||
};
|
||||
|
||||
export const defaultProps = {
|
||||
|
||||
separator: ','
|
||||
};
|
||||
|
||||
export const PropsNotForwarded = [
|
||||
|
|
|
|||
304
test/coreSpec.js
304
test/coreSpec.js
|
|
@ -1,179 +1,179 @@
|
|||
import expect from 'expect';
|
||||
import sinon from 'sinon';
|
||||
import {
|
||||
isJsons,
|
||||
isArrays,
|
||||
jsonsHeaders,
|
||||
jsons2arrays,
|
||||
arrays2csv,
|
||||
jsons2csv,
|
||||
string2csv,
|
||||
toCSV,
|
||||
buildURI
|
||||
isJsons,
|
||||
isArrays,
|
||||
jsonsHeaders,
|
||||
jsons2arrays,
|
||||
arrays2csv,
|
||||
jsons2csv,
|
||||
string2csv,
|
||||
toCSV,
|
||||
buildURI
|
||||
} from '../src/core';
|
||||
|
||||
describe(`core::isJsons`, () => {
|
||||
it(`returns true if all items of array are literal objects`, () => {
|
||||
const target = [{}, {}, {}, {}];
|
||||
expect(isJsons(target)).toBeTruthy();
|
||||
});
|
||||
it(`returns false if one of array items is not literal object`, () => {
|
||||
let target = ["", {}, {}, {}];
|
||||
expect(isJsons(target)).toBeFalsy();
|
||||
target = [{},
|
||||
[], {}, {}
|
||||
];
|
||||
expect(isJsons(target)).toBeFalsy();
|
||||
});
|
||||
it(`returns true if all items of array are literal objects`, () => {
|
||||
const target = [{}, {}, {}, {}];
|
||||
expect(isJsons(target)).toBeTruthy();
|
||||
});
|
||||
it(`returns false if one of array items is not literal object`, () => {
|
||||
let target = ["", {}, {}, {}];
|
||||
expect(isJsons(target)).toBeFalsy();
|
||||
target = [{},
|
||||
[], {}, {}
|
||||
];
|
||||
expect(isJsons(target)).toBeFalsy();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe(`core::isArrays`, () => {
|
||||
it(`retruns true if all array items are arrays too`, () => {
|
||||
const target = [
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[]
|
||||
];
|
||||
expect(isArrays(target)).toBeTruthy();
|
||||
});
|
||||
it(`retruns false if one of array items is not array`, () => {
|
||||
let target = [{},
|
||||
[],
|
||||
[],
|
||||
[]
|
||||
];
|
||||
expect(isArrays(target)).toBeFalsy();
|
||||
target = [
|
||||
[], new Set([]), [],
|
||||
[]
|
||||
];
|
||||
expect(isArrays(target)).toBeFalsy();
|
||||
target = [
|
||||
[], '[]', [],
|
||||
[]
|
||||
];
|
||||
expect(isArrays(target)).toBeFalsy();
|
||||
});
|
||||
it(`retruns true if all array items are arrays too`, () => {
|
||||
const target = [
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[]
|
||||
];
|
||||
expect(isArrays(target)).toBeTruthy();
|
||||
});
|
||||
it(`retruns false if one of array items is not array`, () => {
|
||||
let target = [{},
|
||||
[],
|
||||
[],
|
||||
[]
|
||||
];
|
||||
expect(isArrays(target)).toBeFalsy();
|
||||
target = [
|
||||
[], new Set([]), [],
|
||||
[]
|
||||
];
|
||||
expect(isArrays(target)).toBeFalsy();
|
||||
target = [
|
||||
[], '[]', [],
|
||||
[]
|
||||
];
|
||||
expect(isArrays(target)).toBeFalsy();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
describe(`core::jsonsHeaders`, () => {
|
||||
let fixtures;
|
||||
beforeEach(() => {
|
||||
fixtures = [{
|
||||
maths: 90,
|
||||
phy: 80
|
||||
}, {
|
||||
maths: 50,
|
||||
sport: 97,
|
||||
ch: 66
|
||||
}, {
|
||||
ch: 77,
|
||||
sport: 99
|
||||
}]
|
||||
});
|
||||
let fixtures;
|
||||
beforeEach(() => {
|
||||
fixtures = [{
|
||||
maths: 90,
|
||||
phy: 80
|
||||
}, {
|
||||
maths: 50,
|
||||
sport: 97,
|
||||
ch: 66
|
||||
}, {
|
||||
ch: 77,
|
||||
sport: 99
|
||||
}]
|
||||
});
|
||||
|
||||
it(`returns union of keys of all array items `, () => {
|
||||
let actual = jsonsHeaders(fixtures);
|
||||
expect(actual).toEqual([`maths`, `phy`, 'sport', 'ch'])
|
||||
}),
|
||||
it(`returns union of keys of all array items `, () => {
|
||||
let actual = jsonsHeaders(fixtures);
|
||||
expect(actual).toEqual([`maths`, `phy`, 'sport', 'ch'])
|
||||
}),
|
||||
|
||||
it(`does not duplicate keys on the array`, () => {
|
||||
let actual = jsonsHeaders(fixtures);
|
||||
let keysOfAllItems = fixtures.map((object) => Object.keys(object))
|
||||
.reduce((a, b) => [...a, ...b], []);
|
||||
expect(actual.length).toBeLessThanOrEqualTo(keysOfAllItems.length);
|
||||
expect(actual.length).toEqual(new Set(keysOfAllItems).size);
|
||||
it(`does not duplicate keys on the array`, () => {
|
||||
let actual = jsonsHeaders(fixtures);
|
||||
let keysOfAllItems = fixtures.map((object) => Object.keys(object))
|
||||
.reduce((a, b) => [...a, ...b], []);
|
||||
expect(actual.length).toBeLessThanOrEqualTo(keysOfAllItems.length);
|
||||
expect(actual.length).toEqual(new Set(keysOfAllItems).size);
|
||||
|
||||
})
|
||||
})
|
||||
|
||||
});
|
||||
describe(`core::jsons2arrays`, () => {
|
||||
let fixtures;
|
||||
beforeEach(() => {
|
||||
fixtures = [{
|
||||
maths: '90'
|
||||
}, {
|
||||
sport: '97'
|
||||
}, {
|
||||
maths: '77',
|
||||
sport: '99'
|
||||
}]
|
||||
});
|
||||
it(`converts an Array of literal objects to Array of arrays`, () => {
|
||||
const actual = jsons2arrays(fixtures);
|
||||
const expected = [
|
||||
['maths', 'sport'],
|
||||
['90', ''],
|
||||
['', '97'],
|
||||
['77', '99']
|
||||
];
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
let fixtures;
|
||||
beforeEach(() => {
|
||||
fixtures = [{
|
||||
maths: '90'
|
||||
}, {
|
||||
sport: '97'
|
||||
}, {
|
||||
maths: '77',
|
||||
sport: '99'
|
||||
}]
|
||||
});
|
||||
it(`converts an Array of literal objects to Array of arrays`, () => {
|
||||
const actual = jsons2arrays(fixtures);
|
||||
const expected = [
|
||||
['maths', 'sport'],
|
||||
['90', ''],
|
||||
['', '97'],
|
||||
['77', '99']
|
||||
];
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
|
||||
it(`converts to Array of arrays following the order of headers`, () => {
|
||||
const actual = jsons2arrays(fixtures, ['sport', 'maths']);
|
||||
const expected = [
|
||||
['maths', 'sport'].reverse(), ['90', ''].reverse(), ['', '97'].reverse(), ['77', '99'].reverse()
|
||||
];
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
it(`accepts any size of headers list`, () => {
|
||||
const headers = ['maths', 'sport', 'phy', 'ch'];
|
||||
const actual = jsons2arrays(fixtures, headers);
|
||||
const expected = [
|
||||
headers, ['90', '', '', ''],
|
||||
['', '97', '', ''],
|
||||
['77', '99', '', '']
|
||||
];
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
it(`converts to Array of arrays following the order of headers`, () => {
|
||||
const actual = jsons2arrays(fixtures, ['sport', 'maths']);
|
||||
const expected = [
|
||||
['maths', 'sport'].reverse(), ['90', ''].reverse(), ['', '97'].reverse(), ['77', '99'].reverse()
|
||||
];
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
it(`accepts any size of headers list`, () => {
|
||||
const headers = ['maths', 'sport', 'phy', 'ch'];
|
||||
const actual = jsons2arrays(fixtures, headers);
|
||||
const expected = [
|
||||
headers, ['90', '', '', ''],
|
||||
['', '97', '', ''],
|
||||
['77', '99', '', '']
|
||||
];
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe(`core::arrays2csv`, () => {
|
||||
let fixtures;
|
||||
beforeEach(() => {
|
||||
fixtures = [
|
||||
[`a`, `b`],
|
||||
[`c`, `d`]
|
||||
];
|
||||
let fixtures;
|
||||
beforeEach(() => {
|
||||
fixtures = [
|
||||
[`a`, `b`],
|
||||
[`c`, `d`]
|
||||
];
|
||||
|
||||
});
|
||||
it(`converts Array of arrays to string in CSV format`, () => {
|
||||
const actual = arrays2csv(fixtures);
|
||||
expect(actual).toBeA('string');
|
||||
expect(actual.split(`\n`).join(`|`)).toEqual(`a,b|c,d`);
|
||||
});
|
||||
});
|
||||
it(`converts Array of arrays to string in CSV format`, () => {
|
||||
const actual = arrays2csv(fixtures);
|
||||
expect(actual).toBeA('string');
|
||||
expect(actual.split(`\n`).join(`|`)).toEqual(`a,b|c,d`);
|
||||
});
|
||||
|
||||
it(`renders CSV headers whenever it was given `, () => {
|
||||
const headers = [`X`, `Y`];
|
||||
const firstLineOfCSV = arrays2csv(fixtures, headers).split(`\n`)[0];
|
||||
expect(firstLineOfCSV).toEqual(headers.join(`,`));
|
||||
});
|
||||
it(`renders CSV headers whenever it was given `, () => {
|
||||
const headers = [`X`, `Y`];
|
||||
const firstLineOfCSV = arrays2csv(fixtures, headers).split(`\n`)[0];
|
||||
expect(firstLineOfCSV).toEqual(headers.join(`,`));
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe(`core::jsons2csv`, () => {
|
||||
let fixtures;
|
||||
beforeEach(() => {
|
||||
fixtures = [{
|
||||
X: '88',
|
||||
Y: '97'
|
||||
}, {
|
||||
X: '77',
|
||||
Y: '99'
|
||||
}]
|
||||
});
|
||||
let fixtures;
|
||||
beforeEach(() => {
|
||||
fixtures = [{
|
||||
X: '88',
|
||||
Y: '97'
|
||||
}, {
|
||||
X: '77',
|
||||
Y: '99'
|
||||
}]
|
||||
});
|
||||
|
||||
it(`converts Array of literal objects to string in CSV format including headers`, () => {
|
||||
it(`converts Array of literal objects to string in CSV format including headers`, () => {
|
||||
|
||||
const actual = jsons2csv(fixtures);
|
||||
const expectedHeaders = ['X', 'Y'];
|
||||
const expected =`${expectedHeaders.join(`,`)}|88,97|77,99`;
|
||||
const actual = jsons2csv(fixtures);
|
||||
const expectedHeaders = ['X', 'Y'];
|
||||
const expected = `${expectedHeaders.join(`,`)}|88,97|77,99`;
|
||||
expect(actual).toBeA('string');
|
||||
expect(actual.split(`\n`).join(`|`)).toEqual(expected);
|
||||
});
|
||||
|
|
@ -229,7 +229,7 @@ describe(`core::toCSV`, () =>{
|
|||
describe(`core::buildURI`, () =>{
|
||||
let fixtures;
|
||||
beforeEach(() => {
|
||||
fixtures = {string:'Xy', arrays:[[],[]],jsons:[{}, {}]};
|
||||
fixtures = {string:'Xy', arrays:[['a', 'b'],['c', 'd']],jsons:[{}, {}]};
|
||||
});
|
||||
|
||||
it(`generates URI to download data in CSV format`,() => {
|
||||
|
|
@ -239,4 +239,22 @@ describe(`core::buildURI`, () =>{
|
|||
expect(buildURI(fixtures.string).startsWith(prefixCsvURI)).toBeTruthy();
|
||||
|
||||
});
|
||||
|
||||
it(`generates CSV string according to "separator"`, () => {
|
||||
const prefixCsvURI= `data:text/csv;charset=utf-8,`;
|
||||
const expectedSepartorCount = fixtures.arrays.map(row => row.length -1).reduce((total, next) =>total + next, 0);
|
||||
let separator = ';';
|
||||
let fullURI = buildURI(fixtures.arrays,null , separator);
|
||||
|
||||
expect(
|
||||
fullURI.slice(prefixCsvURI.length).match(/;/g).length
|
||||
).toEqual(expectedSepartorCount);
|
||||
|
||||
separator = ',';
|
||||
fullURI = buildURI(fixtures.arrays,null , separator);
|
||||
expect(
|
||||
fullURI.slice(prefixCsvURI.length).match(/,/g).length
|
||||
).toEqual(expectedSepartorCount);
|
||||
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue