├── .babelrc ├── .eslintrc.js ├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── custom.md │ └── feature_request.md ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── demo ├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .postcssrc.js ├── README.md ├── build │ ├── build.js │ ├── check-versions.js │ ├── logo.png │ ├── utils.js │ ├── vue-loader.conf.js │ ├── webpack.base.conf.js │ ├── webpack.dev.conf.js │ └── webpack.prod.conf.js ├── config │ ├── dev.env.js │ ├── index.js │ └── prod.env.js ├── index.html ├── package.json ├── src │ ├── App.vue │ └── main.js └── static │ └── .gitkeep ├── package.json ├── src ├── JsonToCsv.vue ├── plugin.js └── utils │ └── helpers.js ├── test ├── JsonToVue.spec.js └── helpers.spec.js └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-2"], 3 | "plugins": ["transform-runtime"], 4 | "comments": false, 5 | "env": { 6 | "test": { 7 | "presets": [ 8 | ["env", { "targets": { "node": "current" }}] 9 | ] 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: 'babel-eslint', 4 | parserOptions: { 5 | sourceType: 'module' 6 | }, 7 | env: { 8 | browser: true, 9 | }, 10 | plugins: [ 11 | "html" 12 | ], 13 | rules: { 14 | 'arrow-spacing': [2, { 'before': true, 'after': true }], 15 | 'block-spacing': [2, 'always'], 16 | 'brace-style': [2, '1tbs', { 'allowSingleLine': true }], 17 | 'comma-dangle': [2, 'never'], 18 | 'comma-spacing': [2, { 'before': false, 'after': true }], 19 | 'comma-style': [2, 'last'], 20 | 'curly': [2, 'multi-line'], 21 | 'dot-location': [2, 'property'], 22 | 'eol-last': 2, 23 | 'eqeqeq': [2, 'allow-null'], 24 | 'generator-star-spacing': [2, { 'before': true, 'after': true }], 25 | 'handle-callback-err': [2, '^(err|error)$' ], 26 | 'indent': [2, 2, { 'SwitchCase': 1 }], 27 | 'key-spacing': [2, { 'beforeColon': false, 'afterColon': true }], 28 | 'keyword-spacing': [2, { 'before': true, 'after': true }], 29 | 'new-cap': [2, { 'newIsCap': true, 'capIsNew': false }], 30 | 'no-dupe-keys': 2, 31 | 'no-empty-pattern': 2, 32 | 'no-trailing-spaces': 2, 33 | 'no-unexpected-multiline': 2, 34 | 'no-whitespace-before-property': 2, 35 | 'quotes': [2, 'single', { 'avoidEscape': true, 'allowTemplateLiterals': true }], 36 | 'semi': [2, 'never'], 37 | 'semi-spacing': [2, { 'before': false, 'after': true }], 38 | 'space-before-blocks': [2, 'always'], 39 | 'space-before-function-paren': [2, 'always'], 40 | 'space-in-parens': [2, 'never'], 41 | 'spaced-comment': [2, 'always', { 'markers': ['global', 'globals', 'eslint', 'eslint-disable', '*package', '!', ','] }], 42 | 'template-curly-spacing': [2, 'never'], 43 | 'object-curly-spacing': [2, 'always', { objectsInObjects: false }], 44 | 'array-bracket-spacing': [2, 'never'] 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | 5 | --- 6 | 7 | **Describe the bug** 8 | A clear and concise description of what the bug is. 9 | 10 | **To Reproduce** 11 | Steps to reproduce the behavior: 12 | 1. Go to '...' 13 | 2. Click on '....' 14 | 3. Scroll down to '....' 15 | 4. See error 16 | 17 | **Expected behavior** 18 | A clear and concise description of what you expected to happen. 19 | 20 | **Screenshots** 21 | If applicable, add screenshots to help explain your problem. 22 | 23 | **Desktop (please complete the following information):** 24 | - OS: [e.g. iOS] 25 | - Browser [e.g. chrome, safari] 26 | - Version [e.g. 22] 27 | 28 | **Smartphone (please complete the following information):** 29 | - Device: [e.g. iPhone6] 30 | - OS: [e.g. iOS8.1] 31 | - Browser [e.g. stock browser, safari] 32 | - Version [e.g. 22] 33 | 34 | **Additional context** 35 | Add any other context about the problem here. 36 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/custom.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Custom issue template 3 | about: Describe this issue template's purpose here. 4 | 5 | --- 6 | 7 | 8 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | 5 | --- 6 | 7 | **Is your feature request related to a problem? Please describe.** 8 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 9 | 10 | **Describe the solution you'd like** 11 | A clear and concise description of what you want to happen. 12 | 13 | **Describe alternatives you've considered** 14 | A clear and concise description of any alternative solutions or features you've considered. 15 | 16 | **Additional context** 17 | Add any other context or screenshots about the feature request here. 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .idea 3 | coverage 4 | .DS_Store 5 | dist 6 | package-lock.json 7 | .github 8 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .idea 3 | coverage 4 | .DS_Store 5 | dist 6 | package-lock.json 7 | demo 8 | test 9 | .github 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Angeliki Komianou 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue-json-to-csv 2 | 3 | A Vue.js 2 component to transform and download a json in csv format [https://angeliquekom.github.io/vue-json-to-csv.github.io/](https://angeliquekom.github.io/vue-json-to-csv.github.io/) 4 | 5 | ## Installation 6 | 7 | `npm install --save vue-json-to-csv` or use `dist/vue-json-to-csv.min.js` 8 | 9 | ## Demo 10 | 11 | [Demo](https://angeliquekom.github.io/vue-json-to-csv.github.io/) 12 | 13 | ## Usage 14 | 15 | For vue-cli user: 16 | 17 | ```javascript 18 | import VueJsonToCsv from 'vue-json-to-csv' 19 | ``` 20 | 21 | For standalone usage: 22 | 23 | ```html 24 | 25 | 26 | 27 | 30 | ``` 31 | 32 | ## Sample 1 (simple use) 33 | 34 | Simple usage: will generate a default button. The csv will include all the labels (name, surname) and the data 35 | 36 | ```html 37 | 41 | 42 | ``` 43 | #### Result csv 44 | 45 | csv.csv 46 | 47 | | name | surname | 48 | |----------|------| 49 | | Joe | Roe | 50 | | John | Doe | 51 | 52 | ## Sample 2 (filter labels) 53 | 54 | Selected labels with custom csv title: will generate a custom button as defined at the slot. The csv will include only the "name" label with the "First name" title and the relevant data. 55 | 56 | ```html 57 | 64 | 67 | 68 | ``` 69 | 70 | #### Result csv 71 | 72 | My_CSV.csv 73 | 74 | | First name | 75 | |----------| 76 | | Joe | 77 | | John | 78 | 79 | ## Sample 3 (handle success/error, custom button, configure labels) 80 | 81 | Handle success/error with custom button, returns specific labels with custom title: use of custom methods on success or error. 82 | 83 | ```html 84 | 96 | 99 | 100 | ``` 101 | ## Configuration 102 | 103 | | Prop | Details | 104 | |----------|------| 105 | | json-data | Array of the objects which contain the data to display (required). Each key will be a different column at the csv. All the objects should contain the same keys. If empty array an error will be returned. Example: [ { name: 'Joe', surname: 'Roe' }, { name: 'Joe', surname: 'Doe' }]| 106 | | show-labels | Boolean. If false the first row of the csv will not contain the labels names. | 107 | | labels | An object of the keys of the labels will be displayed. Use to filter the keys to display and modify their label title. For each key we provide the title of the key to displayed. If not defined all the keys will be parsed. Example: { name: { title: 'First name' } }| 108 | | csv-title | String. The title of the generated csv. Default: 'csv' | 109 | | separator | String. The separator of the columns. Default: ',' | 110 | | @update:error | Will be triggered in case of an empty json array, if the labels object has not children or any parsing issue | 111 | | @update:success| Will be triggered in case of a successful csv creation | 112 | -------------------------------------------------------------------------------- /demo/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "modules": false, 5 | "targets": { 6 | "browsers": ["> 1%", "last 2 versions", "not ie <= 8"] 7 | } 8 | }], 9 | "stage-2" 10 | ], 11 | "plugins": ["transform-vue-jsx", "transform-runtime"] 12 | } 13 | -------------------------------------------------------------------------------- /demo/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /demo/.eslintignore: -------------------------------------------------------------------------------- 1 | /build/ 2 | /config/ 3 | /dist/ 4 | /*.js 5 | -------------------------------------------------------------------------------- /demo/.eslintrc.js: -------------------------------------------------------------------------------- 1 | // https://eslint.org/docs/user-guide/configuring 2 | 3 | module.exports = { 4 | root: true, 5 | parserOptions: { 6 | parser: 'babel-eslint' 7 | }, 8 | env: { 9 | browser: true, 10 | }, 11 | extends: [ 12 | // https://github.com/vuejs/eslint-plugin-vue#priority-a-essential-error-prevention 13 | // consider switching to `plugin:vue/strongly-recommended` or `plugin:vue/recommended` for stricter rules. 14 | 'plugin:vue/essential', 15 | // https://github.com/standard/standard/blob/master/docs/RULES-en.md 16 | 'standard' 17 | ], 18 | // required to lint *.vue files 19 | plugins: [ 20 | 'vue' 21 | ], 22 | // add your custom rules here 23 | rules: { 24 | // allow async-await 25 | 'generator-star-spacing': 'off', 26 | // allow debugger during development 27 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off' 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /demo/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | /dist/ 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Editor directories and files 9 | .idea 10 | .vscode 11 | *.suo 12 | *.ntvs* 13 | *.njsproj 14 | *.sln 15 | -------------------------------------------------------------------------------- /demo/.postcssrc.js: -------------------------------------------------------------------------------- 1 | // https://github.com/michael-ciniawsky/postcss-load-config 2 | 3 | module.exports = { 4 | "plugins": { 5 | "postcss-import": {}, 6 | "postcss-url": {}, 7 | // to edit target browsers: use "browserslist" field in package.json 8 | "autoprefixer": {} 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /demo/README.md: -------------------------------------------------------------------------------- 1 | # examples 2 | 3 | > A Vue.js project 4 | 5 | ## Build Setup 6 | 7 | ``` bash 8 | # install dependencies 9 | npm install 10 | 11 | # serve with hot reload at localhost:8080 12 | npm run dev 13 | 14 | # build for production with minification 15 | npm run build 16 | 17 | # build for production and view the bundle analyzer report 18 | npm run build --report 19 | ``` 20 | 21 | For a detailed explanation on how things work, check out the [guide](http://vuejs-templates.github.io/webpack/) and [docs for vue-loader](http://vuejs.github.io/vue-loader). 22 | -------------------------------------------------------------------------------- /demo/build/build.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | require('./check-versions')() 3 | 4 | process.env.NODE_ENV = 'production' 5 | 6 | const ora = require('ora') 7 | const rm = require('rimraf') 8 | const path = require('path') 9 | const chalk = require('chalk') 10 | const webpack = require('webpack') 11 | const config = require('../config') 12 | const webpackConfig = require('./webpack.prod.conf') 13 | 14 | const spinner = ora('building for production...') 15 | spinner.start() 16 | 17 | rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => { 18 | if (err) throw err 19 | webpack(webpackConfig, (err, stats) => { 20 | spinner.stop() 21 | if (err) throw err 22 | process.stdout.write(stats.toString({ 23 | colors: true, 24 | modules: false, 25 | children: false, // If you are using ts-loader, setting this to true will make TypeScript errors show up during build. 26 | chunks: false, 27 | chunkModules: false 28 | }) + '\n\n') 29 | 30 | if (stats.hasErrors()) { 31 | console.log(chalk.red(' Build failed with errors.\n')) 32 | process.exit(1) 33 | } 34 | 35 | console.log(chalk.cyan(' Build complete.\n')) 36 | console.log(chalk.yellow( 37 | ' Tip: built files are meant to be served over an HTTP server.\n' + 38 | ' Opening index.html over file:// won\'t work.\n' 39 | )) 40 | }) 41 | }) 42 | -------------------------------------------------------------------------------- /demo/build/check-versions.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const chalk = require('chalk') 3 | const semver = require('semver') 4 | const packageConfig = require('../package.json') 5 | const shell = require('shelljs') 6 | 7 | function exec (cmd) { 8 | return require('child_process').execSync(cmd).toString().trim() 9 | } 10 | 11 | const versionRequirements = [ 12 | { 13 | name: 'node', 14 | currentVersion: semver.clean(process.version), 15 | versionRequirement: packageConfig.engines.node 16 | } 17 | ] 18 | 19 | if (shell.which('npm')) { 20 | versionRequirements.push({ 21 | name: 'npm', 22 | currentVersion: exec('npm --version'), 23 | versionRequirement: packageConfig.engines.npm 24 | }) 25 | } 26 | 27 | module.exports = function () { 28 | const warnings = [] 29 | 30 | for (let i = 0; i < versionRequirements.length; i++) { 31 | const mod = versionRequirements[i] 32 | 33 | if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) { 34 | warnings.push(mod.name + ': ' + 35 | chalk.red(mod.currentVersion) + ' should be ' + 36 | chalk.green(mod.versionRequirement) 37 | ) 38 | } 39 | } 40 | 41 | if (warnings.length) { 42 | console.log('') 43 | console.log(chalk.yellow('To use this template, you must update following to modules:')) 44 | console.log() 45 | 46 | for (let i = 0; i < warnings.length; i++) { 47 | const warning = warnings[i] 48 | console.log(' ' + warning) 49 | } 50 | 51 | console.log() 52 | process.exit(1) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /demo/build/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angeliquekom/vue-json-to-csv/ac077a8b9a463be3e99bc1d87bae7b5501f0c2e5/demo/build/logo.png -------------------------------------------------------------------------------- /demo/build/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const config = require('../config') 4 | const ExtractTextPlugin = require('extract-text-webpack-plugin') 5 | const packageConfig = require('../package.json') 6 | 7 | exports.assetsPath = function (_path) { 8 | const assetsSubDirectory = process.env.NODE_ENV === 'production' 9 | ? config.build.assetsSubDirectory 10 | : config.dev.assetsSubDirectory 11 | 12 | return path.posix.join(assetsSubDirectory, _path) 13 | } 14 | 15 | exports.cssLoaders = function (options) { 16 | options = options || {} 17 | 18 | const cssLoader = { 19 | loader: 'css-loader', 20 | options: { 21 | sourceMap: options.sourceMap 22 | } 23 | } 24 | 25 | const postcssLoader = { 26 | loader: 'postcss-loader', 27 | options: { 28 | sourceMap: options.sourceMap 29 | } 30 | } 31 | 32 | // generate loader string to be used with extract text plugin 33 | function generateLoaders (loader, loaderOptions) { 34 | const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader] 35 | 36 | if (loader) { 37 | loaders.push({ 38 | loader: loader + '-loader', 39 | options: Object.assign({}, loaderOptions, { 40 | sourceMap: options.sourceMap 41 | }) 42 | }) 43 | } 44 | 45 | // Extract CSS when that option is specified 46 | // (which is the case during production build) 47 | if (options.extract) { 48 | return ExtractTextPlugin.extract({ 49 | use: loaders, 50 | fallback: 'vue-style-loader' 51 | }) 52 | } else { 53 | return ['vue-style-loader'].concat(loaders) 54 | } 55 | } 56 | 57 | // https://vue-loader.vuejs.org/en/configurations/extract-css.html 58 | return { 59 | css: generateLoaders(), 60 | postcss: generateLoaders(), 61 | less: generateLoaders('less'), 62 | sass: generateLoaders('sass', { indentedSyntax: true }), 63 | scss: generateLoaders('sass'), 64 | stylus: generateLoaders('stylus'), 65 | styl: generateLoaders('stylus') 66 | } 67 | } 68 | 69 | // Generate loaders for standalone style files (outside of .vue) 70 | exports.styleLoaders = function (options) { 71 | const output = [] 72 | const loaders = exports.cssLoaders(options) 73 | 74 | for (const extension in loaders) { 75 | const loader = loaders[extension] 76 | output.push({ 77 | test: new RegExp('\\.' + extension + '$'), 78 | use: loader 79 | }) 80 | } 81 | 82 | return output 83 | } 84 | 85 | exports.createNotifierCallback = () => { 86 | const notifier = require('node-notifier') 87 | 88 | return (severity, errors) => { 89 | if (severity !== 'error') return 90 | 91 | const error = errors[0] 92 | const filename = error.file && error.file.split('!').pop() 93 | 94 | notifier.notify({ 95 | title: packageConfig.name, 96 | message: severity + ': ' + error.name, 97 | subtitle: filename || '', 98 | icon: path.join(__dirname, 'logo.png') 99 | }) 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /demo/build/vue-loader.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const utils = require('./utils') 3 | const config = require('../config') 4 | const isProduction = process.env.NODE_ENV === 'production' 5 | const sourceMapEnabled = isProduction 6 | ? config.build.productionSourceMap 7 | : config.dev.cssSourceMap 8 | 9 | module.exports = { 10 | loaders: utils.cssLoaders({ 11 | sourceMap: sourceMapEnabled, 12 | extract: isProduction 13 | }), 14 | cssSourceMap: sourceMapEnabled, 15 | cacheBusting: config.dev.cacheBusting, 16 | transformToRequire: { 17 | video: ['src', 'poster'], 18 | source: 'src', 19 | img: 'src', 20 | image: 'xlink:href' 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /demo/build/webpack.base.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const utils = require('./utils') 4 | const config = require('../config') 5 | const vueLoaderConfig = require('./vue-loader.conf') 6 | 7 | function resolve (dir) { 8 | return path.join(__dirname, '..', dir) 9 | } 10 | 11 | const createLintingRule = () => ({ 12 | test: /\.(js|vue)$/, 13 | loader: 'eslint-loader', 14 | enforce: 'pre', 15 | include: [resolve('src'), resolve('test')], 16 | options: { 17 | formatter: require('eslint-friendly-formatter'), 18 | emitWarning: !config.dev.showEslintErrorsInOverlay 19 | } 20 | }) 21 | 22 | module.exports = { 23 | context: path.resolve(__dirname, '../'), 24 | entry: { 25 | app: './src/main.js' 26 | }, 27 | output: { 28 | path: config.build.assetsRoot, 29 | filename: '[name].js', 30 | publicPath: process.env.NODE_ENV === 'production' 31 | ? config.build.assetsPublicPath 32 | : config.dev.assetsPublicPath 33 | }, 34 | resolve: { 35 | extensions: ['.js', '.vue', '.json'], 36 | alias: { 37 | 'vue$': 'vue/dist/vue.esm.js', 38 | '@': resolve('src'), 39 | } 40 | }, 41 | module: { 42 | rules: [ 43 | ...(config.dev.useEslint ? [createLintingRule()] : []), 44 | { 45 | test: /\.vue$/, 46 | loader: 'vue-loader', 47 | options: vueLoaderConfig 48 | }, 49 | { 50 | test: /\.js$/, 51 | loader: 'babel-loader', 52 | include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')] 53 | }, 54 | { 55 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 56 | loader: 'url-loader', 57 | options: { 58 | limit: 10000, 59 | name: utils.assetsPath('img/[name].[hash:7].[ext]') 60 | } 61 | }, 62 | { 63 | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, 64 | loader: 'url-loader', 65 | options: { 66 | limit: 10000, 67 | name: utils.assetsPath('media/[name].[hash:7].[ext]') 68 | } 69 | }, 70 | { 71 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 72 | loader: 'url-loader', 73 | options: { 74 | limit: 10000, 75 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]') 76 | } 77 | } 78 | ] 79 | }, 80 | node: { 81 | // prevent webpack from injecting useless setImmediate polyfill because Vue 82 | // source contains it (although only uses it if it's native). 83 | setImmediate: false, 84 | // prevent webpack from injecting mocks to Node native modules 85 | // that does not make sense for the client 86 | dgram: 'empty', 87 | fs: 'empty', 88 | net: 'empty', 89 | tls: 'empty', 90 | child_process: 'empty' 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /demo/build/webpack.dev.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const utils = require('./utils') 3 | const webpack = require('webpack') 4 | const config = require('../config') 5 | const merge = require('webpack-merge') 6 | const path = require('path') 7 | const baseWebpackConfig = require('./webpack.base.conf') 8 | const CopyWebpackPlugin = require('copy-webpack-plugin') 9 | const HtmlWebpackPlugin = require('html-webpack-plugin') 10 | const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin') 11 | const portfinder = require('portfinder') 12 | 13 | const HOST = process.env.HOST 14 | const PORT = process.env.PORT && Number(process.env.PORT) 15 | 16 | const devWebpackConfig = merge(baseWebpackConfig, { 17 | module: { 18 | rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true }) 19 | }, 20 | // cheap-module-eval-source-map is faster for development 21 | devtool: config.dev.devtool, 22 | 23 | // these devServer options should be customized in /config/index.js 24 | devServer: { 25 | clientLogLevel: 'warning', 26 | historyApiFallback: { 27 | rewrites: [ 28 | { from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') }, 29 | ], 30 | }, 31 | hot: true, 32 | contentBase: false, // since we use CopyWebpackPlugin. 33 | compress: true, 34 | host: HOST || config.dev.host, 35 | port: PORT || config.dev.port, 36 | open: config.dev.autoOpenBrowser, 37 | overlay: config.dev.errorOverlay 38 | ? { warnings: false, errors: true } 39 | : false, 40 | publicPath: config.dev.assetsPublicPath, 41 | proxy: config.dev.proxyTable, 42 | quiet: true, // necessary for FriendlyErrorsPlugin 43 | watchOptions: { 44 | poll: config.dev.poll, 45 | } 46 | }, 47 | plugins: [ 48 | new webpack.DefinePlugin({ 49 | 'process.env': require('../config/dev.env') 50 | }), 51 | new webpack.HotModuleReplacementPlugin(), 52 | new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update. 53 | new webpack.NoEmitOnErrorsPlugin(), 54 | // https://github.com/ampedandwired/html-webpack-plugin 55 | new HtmlWebpackPlugin({ 56 | filename: 'index.html', 57 | template: 'index.html', 58 | inject: true 59 | }), 60 | // copy custom static assets 61 | new CopyWebpackPlugin([ 62 | { 63 | from: path.resolve(__dirname, '../static'), 64 | to: config.dev.assetsSubDirectory, 65 | ignore: ['.*'] 66 | } 67 | ]) 68 | ] 69 | }) 70 | 71 | module.exports = new Promise((resolve, reject) => { 72 | portfinder.basePort = process.env.PORT || config.dev.port 73 | portfinder.getPort((err, port) => { 74 | if (err) { 75 | reject(err) 76 | } else { 77 | // publish the new Port, necessary for e2e tests 78 | process.env.PORT = port 79 | // add port to devServer config 80 | devWebpackConfig.devServer.port = port 81 | 82 | // Add FriendlyErrorsPlugin 83 | devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({ 84 | compilationSuccessInfo: { 85 | messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`], 86 | }, 87 | onErrors: config.dev.notifyOnErrors 88 | ? utils.createNotifierCallback() 89 | : undefined 90 | })) 91 | 92 | resolve(devWebpackConfig) 93 | } 94 | }) 95 | }) 96 | -------------------------------------------------------------------------------- /demo/build/webpack.prod.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const utils = require('./utils') 4 | const webpack = require('webpack') 5 | const config = require('../config') 6 | const merge = require('webpack-merge') 7 | const baseWebpackConfig = require('./webpack.base.conf') 8 | const CopyWebpackPlugin = require('copy-webpack-plugin') 9 | const HtmlWebpackPlugin = require('html-webpack-plugin') 10 | const ExtractTextPlugin = require('extract-text-webpack-plugin') 11 | const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin') 12 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin') 13 | 14 | const env = require('../config/prod.env') 15 | 16 | const webpackConfig = merge(baseWebpackConfig, { 17 | module: { 18 | rules: utils.styleLoaders({ 19 | sourceMap: config.build.productionSourceMap, 20 | extract: true, 21 | usePostCSS: true 22 | }) 23 | }, 24 | devtool: config.build.productionSourceMap ? config.build.devtool : false, 25 | output: { 26 | path: config.build.assetsRoot, 27 | filename: utils.assetsPath('js/[name].[chunkhash].js'), 28 | chunkFilename: utils.assetsPath('js/[id].[chunkhash].js') 29 | }, 30 | plugins: [ 31 | // http://vuejs.github.io/vue-loader/en/workflow/production.html 32 | new webpack.DefinePlugin({ 33 | 'process.env': env 34 | }), 35 | new UglifyJsPlugin({ 36 | uglifyOptions: { 37 | compress: { 38 | warnings: false 39 | } 40 | }, 41 | sourceMap: config.build.productionSourceMap, 42 | parallel: true 43 | }), 44 | // extract css into its own file 45 | new ExtractTextPlugin({ 46 | filename: utils.assetsPath('css/[name].[contenthash].css'), 47 | // Setting the following option to `false` will not extract CSS from codesplit chunks. 48 | // Their CSS will instead be inserted dynamically with style-loader when the codesplit chunk has been loaded by webpack. 49 | // It's currently set to `true` because we are seeing that sourcemaps are included in the codesplit bundle as well when it's `false`, 50 | // increasing file size: https://github.com/vuejs-templates/webpack/issues/1110 51 | allChunks: true, 52 | }), 53 | // Compress extracted CSS. We are using this plugin so that possible 54 | // duplicated CSS from different components can be deduped. 55 | new OptimizeCSSPlugin({ 56 | cssProcessorOptions: config.build.productionSourceMap 57 | ? { safe: true, map: { inline: false } } 58 | : { safe: true } 59 | }), 60 | // generate dist index.html with correct asset hash for caching. 61 | // you can customize output by editing /index.html 62 | // see https://github.com/ampedandwired/html-webpack-plugin 63 | new HtmlWebpackPlugin({ 64 | filename: config.build.index, 65 | template: 'index.html', 66 | inject: true, 67 | minify: { 68 | removeComments: true, 69 | collapseWhitespace: true, 70 | removeAttributeQuotes: true 71 | // more options: 72 | // https://github.com/kangax/html-minifier#options-quick-reference 73 | }, 74 | // necessary to consistently work with multiple chunks via CommonsChunkPlugin 75 | chunksSortMode: 'dependency' 76 | }), 77 | // keep module.id stable when vendor modules does not change 78 | new webpack.HashedModuleIdsPlugin(), 79 | // enable scope hoisting 80 | new webpack.optimize.ModuleConcatenationPlugin(), 81 | // split vendor js into its own file 82 | new webpack.optimize.CommonsChunkPlugin({ 83 | name: 'vendor', 84 | minChunks (module) { 85 | // any required modules inside node_modules are extracted to vendor 86 | return ( 87 | module.resource && 88 | /\.js$/.test(module.resource) && 89 | module.resource.indexOf( 90 | path.join(__dirname, '../node_modules') 91 | ) === 0 92 | ) 93 | } 94 | }), 95 | // extract webpack runtime and module manifest to its own file in order to 96 | // prevent vendor hash from being updated whenever app bundle is updated 97 | new webpack.optimize.CommonsChunkPlugin({ 98 | name: 'manifest', 99 | minChunks: Infinity 100 | }), 101 | // This instance extracts shared chunks from code splitted chunks and bundles them 102 | // in a separate chunk, similar to the vendor chunk 103 | // see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk 104 | new webpack.optimize.CommonsChunkPlugin({ 105 | name: 'app', 106 | async: 'vendor-async', 107 | children: true, 108 | minChunks: 3 109 | }), 110 | 111 | // copy custom static assets 112 | new CopyWebpackPlugin([ 113 | { 114 | from: path.resolve(__dirname, '../static'), 115 | to: config.build.assetsSubDirectory, 116 | ignore: ['.*'] 117 | } 118 | ]) 119 | ] 120 | }) 121 | 122 | if (config.build.productionGzip) { 123 | const CompressionWebpackPlugin = require('compression-webpack-plugin') 124 | 125 | webpackConfig.plugins.push( 126 | new CompressionWebpackPlugin({ 127 | asset: '[path].gz[query]', 128 | algorithm: 'gzip', 129 | test: new RegExp( 130 | '\\.(' + 131 | config.build.productionGzipExtensions.join('|') + 132 | ')$' 133 | ), 134 | threshold: 10240, 135 | minRatio: 0.8 136 | }) 137 | ) 138 | } 139 | 140 | if (config.build.bundleAnalyzerReport) { 141 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin 142 | webpackConfig.plugins.push(new BundleAnalyzerPlugin()) 143 | } 144 | 145 | module.exports = webpackConfig 146 | -------------------------------------------------------------------------------- /demo/config/dev.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const merge = require('webpack-merge') 3 | const prodEnv = require('./prod.env') 4 | 5 | module.exports = merge(prodEnv, { 6 | NODE_ENV: '"development"' 7 | }) 8 | -------------------------------------------------------------------------------- /demo/config/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // Template version: 1.3.1 3 | // see http://vuejs-templates.github.io/webpack for documentation. 4 | 5 | const path = require('path') 6 | 7 | module.exports = { 8 | dev: { 9 | 10 | // Paths 11 | assetsSubDirectory: 'static', 12 | assetsPublicPath: '/', 13 | proxyTable: {}, 14 | 15 | // Various Dev Server settings 16 | host: 'localhost', // can be overwritten by process.env.HOST 17 | port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined 18 | autoOpenBrowser: false, 19 | errorOverlay: true, 20 | notifyOnErrors: true, 21 | poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions- 22 | 23 | // Use Eslint Loader? 24 | // If true, your code will be linted during bundling and 25 | // linting errors and warnings will be shown in the console. 26 | useEslint: true, 27 | // If true, eslint errors and warnings will also be shown in the error overlay 28 | // in the browser. 29 | showEslintErrorsInOverlay: false, 30 | 31 | /** 32 | * Source Maps 33 | */ 34 | 35 | // https://webpack.js.org/configuration/devtool/#development 36 | devtool: 'cheap-module-eval-source-map', 37 | 38 | // If you have problems debugging vue-files in devtools, 39 | // set this to false - it *may* help 40 | // https://vue-loader.vuejs.org/en/options.html#cachebusting 41 | cacheBusting: true, 42 | 43 | cssSourceMap: true 44 | }, 45 | 46 | build: { 47 | // Template for index.html 48 | index: path.resolve(__dirname, '../dist/index.html'), 49 | 50 | // Paths 51 | assetsRoot: path.resolve(__dirname, '../dist'), 52 | assetsSubDirectory: 'static', 53 | assetsPublicPath: '/', 54 | 55 | /** 56 | * Source Maps 57 | */ 58 | 59 | productionSourceMap: true, 60 | // https://webpack.js.org/configuration/devtool/#production 61 | devtool: '#source-map', 62 | 63 | // Gzip off by default as many popular static hosts such as 64 | // Surge or Netlify already gzip all static assets for you. 65 | // Before setting to `true`, make sure to: 66 | // npm install --save-dev compression-webpack-plugin 67 | productionGzip: false, 68 | productionGzipExtensions: ['js', 'css'], 69 | 70 | // Run the build command with an extra argument to 71 | // View the bundle analyzer report after build finishes: 72 | // `npm run build --report` 73 | // Set to `true` or `false` to always turn it on or off 74 | bundleAnalyzerReport: process.env.npm_config_report 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /demo/config/prod.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = { 3 | NODE_ENV: '"production"' 4 | } 5 | -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | vue-json-to-csv 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "examples", 3 | "version": "1.0.0", 4 | "description": "A Vue.js project", 5 | "author": "Angeliki Komianou", 6 | "private": true, 7 | "scripts": { 8 | "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js", 9 | "start": "npm run dev", 10 | "lint": "eslint --ext .js,.vue src", 11 | "build": "node build/build.js" 12 | }, 13 | "dependencies": { 14 | "vue": "^2.5.2", 15 | "vue-json-to-csv": "^1.1.7", 16 | "vuetify": "^1.1.4" 17 | }, 18 | "devDependencies": { 19 | "autoprefixer": "^7.1.2", 20 | "babel-core": "^6.22.1", 21 | "babel-eslint": "^8.2.1", 22 | "babel-helper-vue-jsx-merge-props": "^2.0.3", 23 | "babel-loader": "^7.1.1", 24 | "babel-plugin-syntax-jsx": "^6.18.0", 25 | "babel-plugin-transform-runtime": "^6.22.0", 26 | "babel-plugin-transform-vue-jsx": "^3.5.0", 27 | "babel-preset-env": "^1.3.2", 28 | "babel-preset-stage-2": "^6.22.0", 29 | "chalk": "^2.0.1", 30 | "copy-webpack-plugin": "^4.0.1", 31 | "css-loader": "^0.28.0", 32 | "eslint": "^4.15.0", 33 | "eslint-config-standard": "^10.2.1", 34 | "eslint-friendly-formatter": "^3.0.0", 35 | "eslint-loader": "^1.7.1", 36 | "eslint-plugin-import": "^2.7.0", 37 | "eslint-plugin-node": "^5.2.0", 38 | "eslint-plugin-promise": "^3.4.0", 39 | "eslint-plugin-standard": "^3.0.1", 40 | "eslint-plugin-vue": "^4.0.0", 41 | "extract-text-webpack-plugin": "^3.0.0", 42 | "file-loader": "^1.1.4", 43 | "friendly-errors-webpack-plugin": "^1.6.1", 44 | "html-webpack-plugin": "^2.30.1", 45 | "node-notifier": "^5.1.2", 46 | "optimize-css-assets-webpack-plugin": "^3.2.0", 47 | "ora": "^1.2.0", 48 | "portfinder": "^1.0.13", 49 | "postcss-import": "^11.0.0", 50 | "postcss-loader": "^2.0.8", 51 | "postcss-url": "^7.2.1", 52 | "rimraf": "^2.6.0", 53 | "semver": "^5.3.0", 54 | "shelljs": "^0.7.6", 55 | "uglifyjs-webpack-plugin": "^1.1.1", 56 | "url-loader": "^0.5.8", 57 | "vue-loader": "^13.3.0", 58 | "vue-style-loader": "^3.0.1", 59 | "vue-template-compiler": "^2.5.2", 60 | "webpack": "^3.6.0", 61 | "webpack-bundle-analyzer": "^2.9.0", 62 | "webpack-dev-server": "^2.9.1", 63 | "webpack-merge": "^4.1.0" 64 | }, 65 | "engines": { 66 | "node": ">= 6.0.0", 67 | "npm": ">= 3.0.0" 68 | }, 69 | "browserslist": [ 70 | "> 1%", 71 | "last 2 versions", 72 | "not ie <= 8" 73 | ] 74 | } 75 | -------------------------------------------------------------------------------- /demo/src/App.vue: -------------------------------------------------------------------------------- 1 | 147 | 148 | 241 | 242 | 271 | -------------------------------------------------------------------------------- /demo/src/main.js: -------------------------------------------------------------------------------- 1 | // The Vue build version to load with the `import` command 2 | // (runtime-only or standalone) has been set in webpack.base.conf with an alias. 3 | import Vue from 'vue' 4 | import Vuetify from 'vuetify' 5 | import 'vuetify/dist/vuetify.min.css' 6 | // import VueJsonToCsv from './../../dist/vue-json-to-csv' 7 | import App from './App' 8 | 9 | Vue.use(Vuetify) 10 | 11 | Vue.config.productionTip = false 12 | // Vue.component('VueJsonToCsv', VueJsonToCsv); 13 | /* eslint-disable no-new */ 14 | new Vue({ 15 | el: '#app', 16 | components: { App }, 17 | template: '' 18 | }) 19 | -------------------------------------------------------------------------------- /demo/static/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angeliquekom/vue-json-to-csv/ac077a8b9a463be3e99bc1d87bae7b5501f0c2e5/demo/static/.gitkeep -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-json-to-csv", 3 | "version": "1.1.8", 4 | "description": "A Vue.js 2 component to transform and download a json in csv format", 5 | "main": "dist/vue-json-to-csv.js", 6 | "scripts": { 7 | "build": "rimraf ./dist && webpack --config ./webpack.config.js", 8 | "test": "jest" 9 | }, 10 | "author": "Angeliki Komianou", 11 | "license": "MIT", 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/angeliquekom/vue-json-to-csv" 15 | }, 16 | "devDependencies": { 17 | "@vue/test-utils": "^1.0.0-beta.16", 18 | "babel-core": "^6.10.4", 19 | "babel-eslint": "^8.2.6", 20 | "babel-loader": "^6.2.4", 21 | "babel-plugin-transform-runtime": "^6.9.0", 22 | "babel-preset-es2015": "^6.9.0", 23 | "babel-preset-stage-2": "^6.11.0", 24 | "babel-runtime": "^6.9.2", 25 | "eslint": "^5.1.0", 26 | "eslint-config-standard": "^12.0.0-alpha.0", 27 | "eslint-friendly-formatter": "^4.0.1", 28 | "eslint-loader": "^2.0.0", 29 | "eslint-plugin-html": "^4.0.5", 30 | "eslint-plugin-import": "^2.13.0", 31 | "eslint-plugin-node": "^6.0.1", 32 | "eslint-plugin-promise": "^3.8.0", 33 | "eslint-plugin-standard": "^3.1.0", 34 | "jest": "^21.2.1", 35 | "jest-serializer-vue": "^0.2.0", 36 | "rimraf": "^2.6.1", 37 | "vue": "^2.2.1", 38 | "vue-html-loader": "^1.2.3", 39 | "vue-jest": "^1.0.0", 40 | "vue-loader": "^11.1.4", 41 | "vue-style-loader": "^2.0.3", 42 | "vue-template-compiler": "^2.2.1", 43 | "webpack": "1.13.1", 44 | "webpack-merge": "^4.1.0" 45 | }, 46 | "jest": { 47 | "collectCoverage": true, 48 | "moduleFileExtensions": [ 49 | "js", 50 | "vue" 51 | ], 52 | "moduleNameMapper": { 53 | "@/([^\\.]*).vue$": "/$1.vue", 54 | "@/([^\\.]*)$": "/$1.js" 55 | }, 56 | "transform": { 57 | "^.+\\.js$": "/node_modules/babel-jest", 58 | ".*\\.(vue)$": "/node_modules/vue-jest" 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/JsonToCsv.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 90 | -------------------------------------------------------------------------------- /src/plugin.js: -------------------------------------------------------------------------------- 1 | import JsonToCsv from './JsonToCsv.vue' 2 | 3 | module.exports = { 4 | install: function (Vue, options) { 5 | Vue.component('vue-json-to-csv', JsonToCsv) 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/utils/helpers.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | methods: { 3 | $_createCsvLabelsConf (arr) { 4 | let conf = {} 5 | 6 | arr.map(function (m, i) { 7 | conf[m] = { title: m } 8 | }) 9 | 10 | return conf 11 | }, 12 | $_createCsvLabels (labels, separator) { 13 | let row = '' 14 | let c = '' 15 | 16 | try { 17 | Object.keys(labels).map(function (m, i) { 18 | row += '"' + labels[m].title + '"' + separator 19 | }) 20 | 21 | row = row.slice(0, -1) 22 | 23 | c += row + '\r\n' 24 | } catch (err) { 25 | c = 'error' 26 | } finally { 27 | return c 28 | } 29 | }, 30 | $_createCsvContent (arr, labels, separator) { 31 | let row = '' 32 | let c = '' 33 | let type = '' 34 | 35 | try { 36 | arr.map(function (m, i) { 37 | row = '' 38 | 39 | Object.keys(labels).map(function (k, s) { 40 | type = typeof m[k] 41 | if (type === 'number' || type === 'float') { 42 | row += m[k] + separator 43 | } else { 44 | row += '"' + m[k] + '"' + separator 45 | } 46 | }) 47 | 48 | row = row.slice(0, -1) 49 | 50 | c += row + '\r\n' 51 | }) 52 | } catch (err) { 53 | c = 'error' 54 | } finally { 55 | return c 56 | } 57 | }, 58 | $_downloadCsv (uid, csv, title) { 59 | try { 60 | let uri = 'data:text/csv;charset=utf-8,' + '\uFEFF' + encodeURIComponent(csv) 61 | 62 | let link = document.createElement('a') 63 | 64 | link.id = 'csv-' + uid 65 | link.href = uri 66 | 67 | document.body.appendChild(link) 68 | 69 | document.getElementById(link.id).style.visibility = 'hidden' 70 | document.getElementById(link.id).download = title + '.csv' 71 | 72 | document.body.appendChild(link) 73 | document.getElementById(link.id).click() 74 | 75 | setTimeout(function () { 76 | document.body.removeChild(link) 77 | }) 78 | return true 79 | } catch (err) { 80 | return false 81 | } 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /test/JsonToVue.spec.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import { shallowMount, mount, createLocalVue } from '@vue/test-utils' 3 | import JsonToCsv from '@/src/JsonToCsv.vue' 4 | import helpers from '@/src/utils/helpers' 5 | 6 | describe('JsonToCsv.vue', () => { 7 | it('renders the default button', () => { 8 | 9 | const jsonData = [ 10 | { name: 'John', surname: 'Doe' }, 11 | { name: 'John', surname: 'Roe' } 12 | ] 13 | 14 | const wrapper = shallowMount(JsonToCsv, { 15 | propsData: { jsonData } 16 | }) 17 | 18 | let button = wrapper.find('button') 19 | 20 | expect(button.text()).toBe('Download') 21 | }) 22 | 23 | it('renders the custom button', () => { 24 | 25 | const jsonData = [ 26 | { name: 'John', surname: 'Doe' }, 27 | { name: 'John', surname: 'Roe' } 28 | ] 29 | 30 | const wrapper = shallowMount(JsonToCsv, { 31 | propsData: { jsonData }, 32 | slots: { 33 | default: '' 34 | } 35 | }) 36 | 37 | let button = wrapper.find('button') 38 | 39 | expect(wrapper.findAll('button').length).toBe(1) 40 | expect(button.text()).toBe('Custom button') 41 | }) 42 | 43 | it('renders does not trigger csv creation when data are empty', () => { 44 | 45 | const jsonData = [] 46 | 47 | const wrapper = shallowMount(JsonToCsv, { 48 | propsData: { jsonData } 49 | }) 50 | 51 | const mocked_createCsvContent = jest.spyOn(wrapper.vm, '$_createCsvContent'); 52 | const mocked_handleError = jest.spyOn(wrapper.vm, 'handleError'); 53 | 54 | wrapper.find('button').trigger('click') 55 | expect(mocked_handleError).toHaveBeenCalled() 56 | expect(mocked_createCsvContent).not.toHaveBeenCalled() 57 | }) 58 | 59 | it('renders does not trigger csv creation when data is function', () => { 60 | 61 | const jsonData = [{func: hi()}] 62 | 63 | function hi () { 64 | return 'hi' 65 | } 66 | 67 | const wrapper = shallowMount(JsonToCsv, { 68 | propsData: { jsonData } 69 | }) 70 | 71 | const mocked_createCsvContent = jest.spyOn(wrapper.vm, '$_createCsvContent'); 72 | 73 | wrapper.find('button').trigger('click') 74 | expect(mocked_createCsvContent).toHaveBeenCalled() 75 | }) 76 | 77 | it('renders does not trigger csv creation when labels are empty', () => { 78 | 79 | const labels = {} 80 | const jsonData = [{name: 'Joe', surname: 'Doe'}] 81 | 82 | const wrapper = shallowMount(JsonToCsv, { 83 | propsData: { jsonData, labels } 84 | }) 85 | 86 | const mocked_createCsvContent = jest.spyOn(wrapper.vm, '$_createCsvContent') 87 | const mocked_handleError = jest.spyOn(wrapper.vm, 'handleError'); 88 | 89 | wrapper.find('button').trigger('click') 90 | 91 | expect(mocked_handleError).toHaveBeenCalled() 92 | expect(mocked_createCsvContent).not.toHaveBeenCalled() 93 | }) 94 | 95 | it('renders does not trigger csv creation when data are empty', () => { 96 | 97 | const jsonData = [ 98 | { name: 'John', surname: 'Doe' }, 99 | { name: 'John', surname: 'Roe' } 100 | ] 101 | 102 | const wrapper = shallowMount(JsonToCsv, { 103 | propsData: { jsonData } 104 | }) 105 | 106 | const mocked_createCsvContent = jest.spyOn(wrapper.vm, '$_createCsvContent'); 107 | 108 | wrapper.find('button').trigger('click') 109 | expect(mocked_createCsvContent).toHaveBeenCalled() 110 | }) 111 | 112 | it('renders does triggers csv creation when data are not empty but showLabels not defined', () => { 113 | 114 | const jsonData = [ 115 | { name: 'John', surname: 'Doe' }, 116 | { name: 'John', surname: 'Roe' } 117 | ] 118 | 119 | const wrapper = shallowMount(JsonToCsv, { 120 | propsData: { jsonData } 121 | }) 122 | 123 | const mocked_downloadCsv = jest.spyOn(wrapper.vm, '$_downloadCsv'); 124 | wrapper.find('button').trigger('click') 125 | expect(mocked_downloadCsv).toHaveBeenCalled() 126 | }) 127 | }) 128 | -------------------------------------------------------------------------------- /test/helpers.spec.js: -------------------------------------------------------------------------------- 1 | import helpers from '@/src/utils/helpers' 2 | 3 | describe('Helpers.js', () => { 4 | it('creates the labels conf', () => { 5 | 6 | const labelsKeys = ["name", "surname"] 7 | 8 | let expected = { 9 | name: { 10 | title: 'name' 11 | }, 12 | surname: { 13 | title: 'surname' 14 | } 15 | } 16 | 17 | expect(helpers.methods.$_createCsvLabelsConf(labelsKeys)).toEqual(expected) 18 | }) 19 | 20 | it('renders the total labels', () => { 21 | 22 | const labels = { 23 | name: { 24 | title: 'name' 25 | }, 26 | surname: { 27 | title: 'surname' 28 | } 29 | } 30 | 31 | const separator = ',' 32 | 33 | let expected = '"name","surname"' + '\r\n' 34 | 35 | expect(helpers.methods.$_createCsvLabels(labels, separator)).toEqual(expected) 36 | }) 37 | 38 | it('renders the total custom labels', () => { 39 | 40 | const labels = { 41 | name: { 42 | title: 'My first name' 43 | }, 44 | surname: { 45 | title: 'My last name' 46 | } 47 | } 48 | 49 | const separator = ',' 50 | 51 | let expected = '"My first name","My last name"' + '\r\n' 52 | 53 | expect(helpers.methods.$_createCsvLabels(labels, separator)).toEqual(expected) 54 | }) 55 | 56 | it('renders the total rows of the content with custom labels', () => { 57 | 58 | const jsonData = [ 59 | { name: 'John', surname: 'Doe' }, 60 | { name: 'John', surname: 'Roe' }, 61 | { name: 'Jane', surname: 'Woe' } 62 | ] 63 | 64 | const labels = { 65 | name: { 66 | title: 'My first name' 67 | }, 68 | surname: { 69 | title: 'My last name' 70 | } 71 | } 72 | 73 | const separator = ',' 74 | 75 | let expected = '"John","Doe"' + '\r\n' + '"John","Roe"' + '\r\n' + '"Jane","Woe"' + '\r\n' 76 | 77 | expect(helpers.methods.$_createCsvContent(jsonData, labels, separator)).toEqual(expected) 78 | }) 79 | 80 | it('renders the link', (done) => { 81 | 82 | let content = '"Doe"' + '\r\n' + '"Roe"' + '\r\n' + '"Woe"' + '\r\n' 83 | 84 | helpers.methods.$_downloadCsv(100, content, '_mytest') 85 | expect(document.getElementById('csv-100')).not.toBeNull() 86 | 87 | setTimeout(function () { 88 | expect(document.getElementById('csv-100')).toBeNull() 89 | done(); 90 | }) 91 | }) 92 | 93 | it('renders the right format at numbers and floats and special characters', () => { 94 | 95 | const jsonData = [ 96 | { name: 'John', surname: 'Doe', age: 20, hours: 10.1 }, 97 | { name: 'John#', surname: 'Roe', age: 30, hours: 20.1 }, 98 | { name: 'Jane', surname: 'Woe', age: 40, hours: 30.1 } 99 | ] 100 | 101 | const labels = { 102 | name: { 103 | title: 'First name' 104 | }, 105 | age: { 106 | title: 'Age' 107 | }, 108 | hours: { 109 | title: 'Total hours' 110 | } 111 | } 112 | 113 | const separator = ',' 114 | 115 | let expected = '"John"' + ',' + 20 + ',' + 10.1 + '\r\n' + 116 | '"John#"' + ',' + 30 + ',' + 20.1 + '\r\n' + 117 | '"Jane"' + ',' + 40 + ',' + 30.1 + '\r\n' 118 | 119 | expect(helpers.methods.$_createCsvContent(jsonData, labels, separator)).toEqual(expected) 120 | }) 121 | }) 122 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack') 2 | const merge = require('webpack-merge') 3 | const path = require('path') 4 | 5 | var config = { 6 | output: { 7 | path: path.resolve(__dirname + '/dist/'), 8 | }, 9 | module: { 10 | loaders: [ 11 | { 12 | test: /\.js$/, 13 | loader: 'babel', 14 | include: __dirname, 15 | exclude: /node_modules/ 16 | }, 17 | { 18 | test: /\.vue$/, 19 | loader: 'vue' 20 | }, 21 | { 22 | test: /\.css$/, 23 | loader: 'style!less!css' 24 | }, 25 | { 26 | enforce: "pre", 27 | test: /\.(js|vue)$/, 28 | exclude: /node_modules/, 29 | loader: "eslint-loader", 30 | options: { 31 | formatter: require('eslint-friendly-formatter') 32 | } 33 | } 34 | ] 35 | }, 36 | plugins: [ 37 | new webpack.optimize.UglifyJsPlugin( { 38 | minimize : true, 39 | sourceMap : false, 40 | mangle: true, 41 | compress: { 42 | warnings: false 43 | } 44 | } ) 45 | ] 46 | } 47 | 48 | module.exports = [ 49 | merge(config, { 50 | entry: path.resolve(__dirname + '/src/plugin.js'), 51 | output: { 52 | filename: 'vue-json-to-csv.min.js', 53 | libraryTarget: 'window', 54 | library: 'VueJsonToCsv', 55 | } 56 | }), 57 | merge(config, { 58 | entry: path.resolve(__dirname + '/src/JsonToCsv.vue'), 59 | output: { 60 | filename: 'vue-json-to-csv.js', 61 | libraryTarget: 'umd', 62 | library: 'vue-json-to-csv', 63 | umdNamedDefine: true 64 | } 65 | }) 66 | ] 67 | --------------------------------------------------------------------------------