├── .browserslistrc ├── .editorconfig ├── .github └── workflows │ └── nodejs.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── babel.config.js ├── demo ├── assets │ ├── csv-sample.csv │ └── logo.png ├── favicon.ico ├── index.html └── js │ ├── app.js │ └── chunk-vendors.js ├── jest.config.js ├── package-lock.json ├── package.json ├── public ├── assets │ ├── csv-sample.csv │ └── logo.png ├── favicon.ico └── index.html ├── rollup.config.js ├── src ├── App.vue ├── components │ ├── VueCsvErrors.vue │ ├── VueCsvImport.vue │ ├── VueCsvInput.vue │ ├── VueCsvMap.vue │ ├── VueCsvSubmit.vue │ ├── VueCsvTableMap.vue │ └── VueCsvToggleHeaders.vue ├── index.css ├── index.js ├── main.js └── util │ └── mimeDictionary.js ├── tests ├── MOCK_DATA.csv └── unit │ ├── .eslintrc.js │ └── vueCsvImport.spec.js └── vue.config.js /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not dead 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.{js,jsx,ts,tsx,vue}] 2 | indent_style = space 3 | indent_size = 4 4 | end_of_line = lf 5 | trim_trailing_whitespace = true 6 | insert_final_newline = true 7 | max_line_length = 200 8 | -------------------------------------------------------------------------------- /.github/workflows/nodejs.yml: -------------------------------------------------------------------------------- 1 | name: Node CI 2 | 3 | on: [ push ] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | strategy: 9 | matrix: 10 | node-version: [ 16.x ] 11 | steps: 12 | - uses: actions/checkout@v1 13 | - name: Use Node.js ${{ matrix.node-version }} 14 | uses: actions/setup-node@v1 15 | with: 16 | node-version: ${{ matrix.node-version }} 17 | - name: npm install, build, and test 18 | run: | 19 | npm ci 20 | npm run build --if-present 21 | npm run test 22 | env: 23 | CI: true 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | 6 | # local env files 7 | .env.local 8 | .env.*.local 9 | 10 | # Log files 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | pnpm-debug.log* 15 | 16 | # Editor directories and files 17 | .idea 18 | .vscode 19 | *.suo 20 | *.ntvs* 21 | *.njsproj 22 | *.sln 23 | *.sw? 24 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `vue-csv-import` will be documented in this file 4 | 5 | ## 1.0.0 6 | 7 | - Initial release 8 | 9 | ## 1.5.0 10 | 11 | - Added ability to use custom labels for fields. 12 | 13 | ## 1.5.1 14 | 15 | - Change default file class. 16 | - Add options for the button values 17 | 18 | ## 1.6 19 | 20 | - Debug and added tests 21 | 22 | ## 2.1.0 23 | 24 | - Remove csv-parse dependency and replace with papaparse - smaller and more dependable. 25 | - Can now use with v-model. will return a parsed csv. 26 | 27 | ## 2.3.0 28 | 29 | - Added slots for file header checkbox and table thead. 30 | - default button text changed to "next" 31 | - added callback for usage without url. 32 | - Added 'headers' prop. Define whether csv has headers by default. Removes checkbox. 33 | 34 | ## 2.3.4 35 | 36 | - restructure app 37 | - make axios and lodash external dependencies. 38 | - papaparse is bundled in component. 39 | 40 | ## 2.3.5 41 | 42 | - Mime type validation when selecting file 43 | 44 | ## 2.3.6 45 | 46 | - added names to select fields 47 | - added other mime types to default accepted types array 48 | - added some more meaningful tests 49 | 50 | ## 2.3.7 51 | 52 | - added class to table select fields 53 | - added canIgnore to allow users to ignore fields if required 54 | 55 | ## 4.0.0 56 | 57 | - rebuild for Vue 3 58 | - completely modular and un-styled. 59 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vue.js component to handle CSV uploads with field mapping. 2 | 3 | [![Latest Version on NPM](https://img.shields.io/npm/v/vue-csv-import.svg?style=flat-square)](https://npmjs.com/package/vue-csv-import) 4 | [![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE.md) 5 | ![npm tests](https://github.com/jgile/vue-csv-import/actions/workflows/nodejs.yml/badge.svg) 6 | [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/jgile/vue-csv-import/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/jgile/vue-csv-import/?branch=master) 7 | 8 | This version is for Vue 3. [Click here for Vue 2](https://github.com/jgile/vue-csv-import/tree/vue2). 9 | 10 | VueCsvImport is completely un-styled and customizable. All markup can be replaced and all text can be customized. 11 | 12 | [Demo](https://jgile.github.io/vue-csv-import/) 13 | 14 | --- 15 | 16 | ## Installation 17 | 18 | You can install the package via npm or yarn: 19 | 20 | ```bash 21 | # npm 22 | npm install vue-csv-import 23 | 24 | # Yarn 25 | yarn add vue-csv-import 26 | ``` 27 | 28 | You can import components individually. 29 | 30 | ```js 31 | import {VueCsvToggleHeaders, VueCsvSubmit, VueCsvMap, VueCsvInput, VueCsvErrors, VueCsvImport} from 'vue-csv-import'; 32 | ``` 33 | 34 | Or import all as a plugin. 35 | 36 | ```js 37 | import {createApp} from "vue"; 38 | import App from "./App.vue"; 39 | import {VueCsvImportPlugin} from "vue-csv-import"; 40 | 41 | createApp(App) 42 | .use(VueCsvImportPlugin) 43 | .mount("#app"); 44 | ``` 45 | 46 | A minimal working example with all components will look something like this: 47 | 48 | ```vue 49 | 50 | 61 | ``` 62 | 63 | --- 64 | 65 | ## Components 66 | 67 | - [VueCsvImport](#VueCsvImport) - The primary component wrapper. All other components should be used within this component. 68 | - [VueCsvToggleHeaders](#VueCsvToggleHeaders) - Toggles whether CSV should be read as having headers or not. 69 | - [VueCsvInput](#VueCsvInput) - The file input field to upload your CSV 70 | - [VueCsvMap](#VueCsvMap) - Used to map CSV columns to your fields 71 | - [VueCsvSubmit](#VueCsvSubmit) - Used to POST the mapped CSV. 72 | - [VueCsvErrors](#VueCsvErrors) - Used to display errors. 73 | 74 | ### VueCsvImport 75 | 76 | Primary wrapper component. 77 | 78 | ```vue 79 | 80 | 97 | 98 | ``` 99 | 100 | #### Props: 101 | 102 | | Prop | Default | Description | 103 | | ------ | ------- | ----------- | 104 | | fields | null | (required) The field names used to map the CSV. | 105 | | text | see below | (optional) Override the default text used in the component. | 106 | | modelValue | N/A | (optional) Binds to the mapped CSV object. | 107 | 108 | #### Default text 109 | 110 | ```json 111 | { 112 | errors: { 113 | fileRequired: 'A file is required', 114 | invalidMimeType: "Invalid file type" 115 | }, 116 | toggleHeaders: 'File has headers', 117 | submitBtn: 'Submit', 118 | fieldColumn: 'Field', 119 | csvColumn: 'Column' 120 | } 121 | ``` 122 | 123 | #### Slot Props: 124 | 125 | | Prop | Description | 126 | | ------ | ----------- | 127 | | file | The selected file | 128 | | errors | Current errors | 129 | | fields | The fields object | 130 | 131 | --- 132 | 133 | ### VueCsvToggleHeaders 134 | 135 | Allows user to toggle whether the CSV has headers or not. 136 | 137 | ```vue 138 | 139 | 146 | ``` 147 | 148 | Or with custom markup: 149 | 150 | ```vue 151 | 152 | 161 | ``` 162 | 163 | #### Props: 164 | 165 | | Prop | Default | Description | 166 | | ------ | ------- | ----------- | 167 | | checkboxAttributes | {} | (optional) Attributes to bind to the checkbox. | 168 | | labelAttributes | {} | (optional) Attributes to bind to the label. | 169 | 170 | #### Slot Props: 171 | 172 | | Prop | Description | 173 | | ------ | ----------- | 174 | | hasHeaders | Whether CSV is marked as having headers. | 175 | | toggle | Toggle the 'hasHeaders' value. | 176 | 177 | --- 178 | 179 | ### VueCsvInput 180 | 181 | The file field for importing CSV. 182 | 183 | ```vue 184 | 185 | 192 | ``` 193 | 194 | Or with custom markup: 195 | 196 | ```vue 197 | 198 | 207 | ``` 208 | 209 | #### Props: 210 | 211 | | Prop | Default | Description | 212 | | ------ | ------- | ----------- | 213 | | name | N/A | (required) The field names used to map the CSV. 214 | | headers | true | (optional) Override the default text used in the component. | 215 | | parseConfig | N/A | (optional) Papaparse config object. | 216 | | validation | true | (optional) Use validation or not | 217 | | fileMimeTypes | ["text/csv", "text/x-csv", "application/vnd.ms-excel", "text/plain"] | (optional) Accepted CSV file mime types. | 218 | 219 | #### Slot Props: 220 | 221 | | Prop | Description | 222 | | ------ | ----------- | 223 | | file | The current file object | 224 | | change | Change the file | 225 | 226 | --- 227 | 228 | ### VueCsvMap 229 | 230 | Component to map the CSV to the specified fields. 231 | 232 | ```vue 233 | 234 | 241 | ``` 242 | 243 | Or use slot for custom markup: 244 | 245 | ```vue 246 | 247 | 256 | ``` 257 | 258 | #### Props: 259 | 260 | | Prop | Default | Description | 261 | | ------ | ------- | ----------- | 262 | | noThead | false | (optional) Attributes to bind to the checkbox. | 263 | | selectAttributes | {} | (optional) Attributes to bind to the select fields. | 264 | | autoMatch | true | (optional) Auto-match fields to columns when they share the same name | 265 | | autoMatchIgnoreCase | true | (optional) Ignore case when auto-matching | 266 | 267 | #### Slot Props: 268 | 269 | | Prop | Description | 270 | | ------ | ----------- | 271 | | sample | The first row of the CSV. | 272 | | map | The currently mapped fields. | 273 | | fields | The fields. | 274 | 275 | --- 276 | 277 | ### VueCsvSubmit 278 | 279 | Displays a button to post the CSV to specified URL. 280 | 281 | ```vue 282 | 283 | 290 | ``` 291 | 292 | Or use slot for custom markup: 293 | 294 | ```vue 295 | 296 | 303 | ``` 304 | 305 | #### Props: 306 | 307 | | Prop | Default | Description | 308 | | ------ | ------- | ----------- | 309 | | url | N/A | (required) Where to post the CSV. | 310 | | config | {} | (optional) Axios config object. | 311 | 312 | #### Slot Props: 313 | 314 | | Prop | Description | 315 | | ------ | ----------- | 316 | | submit | Submit the CSV (POST) | 317 | | mappedCsv | The mapped CSV object | 318 | 319 | --- 320 | 321 | ### VueCsvErrors 322 | 323 | Displays any error messages. 324 | 325 | ```vue 326 | 327 | 334 | ``` 335 | 336 | Or use slot for custom markup: 337 | 338 | ```vue 339 | 340 | 349 | ``` 350 | 351 | #### Slot Props: 352 | 353 | | Prop | Description | 354 | | ------ | ----------- | 355 | | errors | Object containing errors | 356 | 357 | --- 358 | 359 | ### Testing 360 | 361 | ```bash 362 | npm run test 363 | ``` 364 | 365 | ### Changelog 366 | 367 | Please see [CHANGELOG](CHANGELOG.md) for more information what has changed recently. 368 | 369 | ### Security 370 | 371 | If you discover any security related issues, please contact John Gile. 372 | 373 | ## License 374 | 375 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 376 | 377 | ## Credits 378 | 379 | - [John Gile](https://github.com/jgile) 380 | - [All Contributors](../../contributors) 381 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /demo/assets/csv-sample.csv: -------------------------------------------------------------------------------- 1 | columnA,columnB,columnC 2 | "Susan",41,a 3 | "Mike",5,b 4 | "Jake",33,c 5 | "Jill",30,d 6 | -------------------------------------------------------------------------------- /demo/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jgile/vue-csv-import/1dade3d7f0aff90d7f00e9870dcd41df709762ee/demo/assets/logo.png -------------------------------------------------------------------------------- /demo/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jgile/vue-csv-import/1dade3d7f0aff90d7f00e9870dcd41df709762ee/demo/favicon.ico -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | vue-csv-import 9 | 10 | 11 |
12 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: '@vue/cli-plugin-unit-jest', 3 | transform: { 4 | '^.+\\.vue$': 'vue-jest' 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-csv-import", 3 | "version": "4.1.2", 4 | "private": false, 5 | "description": "Vue.js component to handle CSV uploads with field mapping.", 6 | "author": "John Gile ", 7 | "main": "dist/vue-csv-import.esm.js", 8 | "unpkg": "dist/vue-csv-import.umd.js", 9 | "scripts": { 10 | "serve": "vue-cli-service serve", 11 | "build": "rollup -c", 12 | "test": "jest" 13 | }, 14 | "dependencies": { 15 | "axios": "^0.21.1", 16 | "lodash": "^4.17.20", 17 | "papaparse": "^5.0.0" 18 | }, 19 | "peerDependencies": { 20 | "vue": "^3.0.4" 21 | }, 22 | "devDependencies": { 23 | "@vue/cli-plugin-babel": "~4.5.0", 24 | "@vue/cli-plugin-unit-jest": "~4.5.0", 25 | "@vue/cli-service": "~4.5.0", 26 | "@vue/compiler-sfc": "^3.0.4", 27 | "@vue/test-utils": "^2.0.0-0", 28 | "core-js": "^3.6.5", 29 | "rollup": "^2.52.0", 30 | "rollup-plugin-terser": "^7.0.2", 31 | "rollup-plugin-vue": "^6.0.0", 32 | "typescript": "~3.9.3", 33 | "vue": "^3.0.4", 34 | "vue-jest": "^5.0.0-0" 35 | }, 36 | "files": [ 37 | "dist/*.js" 38 | ], 39 | "bugs": { 40 | "url": "https://github.com/jgile/vue-csv-import/issues" 41 | }, 42 | "keywords": [ 43 | "jgile", 44 | "csv", 45 | "map", 46 | "vue-csv", 47 | "vue-csv-import", 48 | "vue3", 49 | "import" 50 | ], 51 | "license": "MIT", 52 | "repository": { 53 | "type": "git", 54 | "url": "https://github.com/jgile/vue-csv-import.git" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /public/assets/csv-sample.csv: -------------------------------------------------------------------------------- 1 | columnA,columnB,columnC 2 | "Susan",41,a 3 | "Mike",5,b 4 | "Jake",33,c 5 | "Jill",30,d 6 | -------------------------------------------------------------------------------- /public/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jgile/vue-csv-import/1dade3d7f0aff90d7f00e9870dcd41df709762ee/public/assets/logo.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jgile/vue-csv-import/1dade3d7f0aff90d7f00e9870dcd41df709762ee/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= htmlWebpackPlugin.options.title %> 9 | 10 | 11 |
12 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import vue from 'rollup-plugin-vue'; 2 | import {terser} from 'rollup-plugin-terser'; 3 | 4 | export default [ 5 | { 6 | input: 'src/index.js', 7 | output: { 8 | format: 'cjs', 9 | file: 'dist/vue-csv-import.cjs.js' 10 | }, 11 | plugins: [ 12 | vue(), 13 | terser() 14 | ] 15 | },{ 16 | input: 'src/index.js', 17 | output: { 18 | format: 'esm', 19 | file: 'dist/vue-csv-import.esm.js' 20 | }, 21 | plugins: [ 22 | vue(), 23 | terser() 24 | ] 25 | },{ 26 | input: 'src/index.js', 27 | output: { 28 | format: 'umd', 29 | name: 'vue-csv-import', 30 | file: 'dist/vue-csv-import.umd.js' 31 | }, 32 | plugins: [ 33 | vue(), 34 | terser() 35 | ] 36 | } 37 | ] 38 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 46 | 47 | 113 | 114 | 139 | -------------------------------------------------------------------------------- /src/components/VueCsvErrors.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 26 | -------------------------------------------------------------------------------- /src/components/VueCsvImport.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 108 | -------------------------------------------------------------------------------- /src/components/VueCsvInput.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 112 | -------------------------------------------------------------------------------- /src/components/VueCsvMap.vue: -------------------------------------------------------------------------------- 1 | 39 | 40 | 99 | -------------------------------------------------------------------------------- /src/components/VueCsvSubmit.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 51 | -------------------------------------------------------------------------------- /src/components/VueCsvTableMap.vue: -------------------------------------------------------------------------------- 1 | 46 | 47 | 132 | -------------------------------------------------------------------------------- /src/components/VueCsvToggleHeaders.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 40 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | #app { 2 | font-family: Avenir, Helvetica, Arial, sans-serif; 3 | -webkit-font-smoothing: antialiased; 4 | -moz-osx-font-smoothing: grayscale; 5 | text-align: center; 6 | color: #2c3e50; 7 | margin-top: 60px; 8 | } 9 | #csv-table { 10 | font-family: Arial, Helvetica, sans-serif; 11 | border-collapse: collapse; 12 | width: 100%; 13 | } 14 | 15 | #csv-table td, #csv-table th { 16 | border: 1px solid #ddd; 17 | padding: 8px; 18 | } 19 | 20 | #csv-table tr:nth-child(even){background-color: #f2f2f2;} 21 | 22 | #csv-table tr:hover {background-color: #ddd;} 23 | 24 | #csv-table th { 25 | padding-top: 12px; 26 | padding-bottom: 12px; 27 | text-align: left; 28 | background-color: #4CAF50; 29 | color: white; 30 | } 31 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import merge from 'lodash/merge'; 2 | import VueCsvImport from "./components/VueCsvImport.vue"; 3 | import VueCsvErrors from "./components/VueCsvErrors.vue"; 4 | import VueCsvInput from "./components/VueCsvInput.vue"; 5 | import VueCsvMap from "./components/VueCsvMap.vue"; 6 | import VueCsvTableMap from "./components/VueCsvTableMap.vue"; 7 | import VueCsvSubmit from "./components/VueCsvSubmit.vue"; 8 | import VueCsvToggleHeaders from "./components/VueCsvToggleHeaders.vue"; 9 | 10 | const VueCsvImportPlugin = { 11 | install(app, options) { 12 | options = merge({ 13 | components: { 14 | 'vue-csv-import': 'vue-csv-import', 15 | 'vue-csv-errors': 'vue-csv-errors', 16 | 'vue-csv-input': 'vue-csv-input', 17 | 'vue-csv-map': 'vue-csv-map', 18 | 'vue-csv-table-map': 'vue-csv-table-map', 19 | 'vue-csv-submit': 'vue-csv-submit', 20 | 'vue-csv-toggle-headers': 'vue-csv-toggle-headers', 21 | } 22 | }, options); 23 | 24 | app.component(options.components['vue-csv-import'], VueCsvImport) 25 | app.component(options.components['vue-csv-errors'], VueCsvErrors) 26 | app.component(options.components['vue-csv-input'], VueCsvInput) 27 | app.component(options.components['vue-csv-map'], VueCsvMap) 28 | app.component(options.components['vue-csv-table-map'], VueCsvTableMap) 29 | app.component(options.components['vue-csv-submit'], VueCsvSubmit) 30 | app.component(options.components['vue-csv-toggle-headers'], VueCsvToggleHeaders) 31 | } 32 | } 33 | 34 | export {VueCsvToggleHeaders, VueCsvSubmit, VueCsvMap, VueCsvTableMap, VueCsvInput, VueCsvErrors, VueCsvImport, VueCsvImportPlugin}; 35 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import {createApp} from "vue"; 2 | import App from "./App.vue"; 3 | import "./index.css"; 4 | import {VueCsvImportPlugin} from "../dist/vue-csv-import.umd.js"; 5 | 6 | createApp(App) 7 | .use(VueCsvImportPlugin) 8 | .mount("#app"); 9 | -------------------------------------------------------------------------------- /src/util/mimeDictionary.js: -------------------------------------------------------------------------------- 1 | import findKey from 'lodash/findKey' 2 | 3 | const mimeTypes = { 4 | "text/csv": { 5 | "source": "iana", 6 | "compressible": true, 7 | "extensions": ["csv"] 8 | }, 9 | "application/vnd.ms-excel": { 10 | "source": "iana", 11 | "compressible": false, 12 | "extensions": ["xls", "xlm", "xla", "xlc", "xlt", "xlw"] 13 | }, 14 | "text/plain": { 15 | "source": "iana", 16 | "compressible": true, 17 | "extensions": ["txt", "text", "conf", "def", "list", "log", "in", "ini"] 18 | }, 19 | }; 20 | 21 | 22 | export default function guessMimeType(path) { 23 | if (!path || typeof path !== 'string') { 24 | return false 25 | } 26 | 27 | let extension = path.split(".").pop(); 28 | 29 | if (!extension) { 30 | return false 31 | } 32 | 33 | return findKey(mimeTypes, obj => obj.extensions.includes(extension)) || false; 34 | } 35 | -------------------------------------------------------------------------------- /tests/MOCK_DATA.csv: -------------------------------------------------------------------------------- 1 | name,date,home 2 | Home Ing,2/9/2018,false 3 | Y-find,8/4/2017,true 4 | Zamit,5/31/2017,false 5 | Duobam,4/13/2017,true 6 | Solarbreeze,8/1/2017,true 7 | Domainer,8/14/2017,true 8 | Zoolab,1/2/2018,true 9 | Toughjoyfax,2/9/2018,true 10 | Flowdesk,7/15/2017,false 11 | Lotlux,6/11/2017,false 12 | Holdlamis,11/18/2017,true 13 | Voyatouch,8/1/2017,false 14 | Tres-Zap,11/7/2017,false 15 | Toughjoyfax,5/4/2017,false 16 | Y-find,4/12/2017,true 17 | Redhold,9/1/2017,false 18 | Voyatouch,7/21/2017,false 19 | Wrapsafe,5/25/2017,false 20 | Tres-Zap,12/19/2017,false 21 | Konklab,9/2/2017,false 22 | Job,11/6/2017,true 23 | Stim,11/11/2017,true 24 | Vagram,1/2/2018,false 25 | Konklab,8/30/2017,true 26 | Tresom,1/20/2018,false 27 | Quo Lux,9/20/2017,false 28 | Tin,9/25/2017,false 29 | Veribet,1/21/2018,true 30 | Holdlamis,3/16/2018,false 31 | Redhold,3/2/2018,true 32 | Fintone,3/24/2018,false 33 | Viva,8/25/2017,false 34 | Voltsillam,7/20/2017,false 35 | Pannier,1/28/2018,true 36 | Cookley,12/7/2017,false 37 | Zaam-Dox,11/21/2017,false 38 | Regrant,11/28/2017,false 39 | Cookley,7/3/2017,false 40 | Otcom,5/29/2017,false 41 | Fintone,3/26/2018,false 42 | Prodder,11/3/2017,true 43 | Andalax,5/10/2017,true 44 | Duobam,5/19/2017,true 45 | Span,11/4/2017,true 46 | Bytecard,2/13/2018,false 47 | Otcom,1/5/2018,false 48 | Gembucket,11/22/2017,false 49 | Voyatouch,12/22/2017,false 50 | Zamit,8/18/2017,true 51 | Redhold,10/11/2017,true 52 | -------------------------------------------------------------------------------- /tests/unit/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | jest: true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /tests/unit/vueCsvImport.spec.js: -------------------------------------------------------------------------------- 1 | import {mount} from '@vue/test-utils' 2 | import get from 'lodash/get'; 3 | import {VueCsvErrors, VueCsvImport, VueCsvToggleHeaders, VueCsvSubmit, VueCsvInput, VueCsvMap} from '@/index'; 4 | 5 | const defaultTestComponent = { 6 | components: {VueCsvErrors, VueCsvImport, VueCsvToggleHeaders, VueCsvSubmit, VueCsvInput, VueCsvMap}, 7 | props: { 8 | mapFields: { 9 | default() { 10 | return {name: {required: false, label: 'Name'}, age: {required: true, label: 'Age'}} 11 | } 12 | } 13 | }, 14 | template: ` 15 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | `, 27 | data() { 28 | return {csv: null}; 29 | } 30 | }; 31 | 32 | describe('VueCsvImport', () => { 33 | let wrapper; 34 | const ref = function (ref) { 35 | return wrapper.vm.$refs[ref] 36 | } 37 | const dataObject = function (key = null) { 38 | if (key === null) { 39 | return wrapper.vm.$refs.vueCsvImport.VueCsvImportData; 40 | } 41 | 42 | return get(wrapper.vm.$refs.vueCsvImport.VueCsvImportData, key); 43 | } 44 | // const csv = [["name", "age", "grade"], ["Susan", "41", "a"], ["Mike", "5", "b"], ["Jake", "33", "c"], ["Jill", "30", "d"]]; 45 | // const sample = [["name", "age", "grade"], ["Susan", "41", "a"]]; 46 | // const map = {"name": 0, "age": 1}; 47 | // const testComponent = function (obj = {}, options = {}) { 48 | // return mount({ 49 | // components: {VueCsvErrors, VueCsvImport, VueCsvToggleHeaders, VueCsvSubmit, VueCsvInput, VueCsvMap}, 50 | // props: { 51 | // mapFields: { 52 | // default() { 53 | // return {name: {required: false, label: 'Name'}, age: {required: true, label: 'Age'}} 54 | // } 55 | // } 56 | // }, 57 | // template: ` 58 | // 63 | // 64 | // 65 | // 66 | // 67 | // 68 | // 69 | // `, 70 | // data() { 71 | // return {csv: null}; 72 | // } 73 | // }, options); 74 | // } 75 | 76 | it('has expected html', () => { 77 | wrapper = mount(defaultTestComponent, { 78 | propsData: { 79 | mapFields: {name: {required: false, label: 'Name'}, age: {required: true, label: 'Age'}} 80 | } 81 | }); 82 | 83 | // expect(wrapper.vm.$el).toMatchSnapshot(); 84 | }); 85 | 86 | // it('has expected map fields when array', async () => { 87 | // wrapper = mount(testComponent, { 88 | // props: { 89 | // mapFields: ['name_map', 'age_map', 'grade_map'] 90 | // } 91 | // }) 92 | // 93 | // expect(dataObject('fields')).toEqual([{"key": "name_map", "label": "name_map", "required": true}, {"key": "age_map", "label": "age_map", "required": true}, {"key": "grade_map", "label": "grade_map", "required": true}]); 94 | // }); 95 | 96 | // it('has expected map fields when object', async () => { 97 | // wrapper = mount(testComponent, { 98 | // props: { 99 | // mapFields: {name: {required: false, label: 'Name'}, age: {required: true, label: 'Age'}} 100 | // } 101 | // }) 102 | // 103 | // expect(dataObject('fields')).toEqual([{"key": "name", "label": "Name", "required": false}, {"key": "age", "label": "Age", "required": true}]); 104 | // }); 105 | 106 | // it('headers uses headers headers', async() => { 107 | // wrapper = testComponent({ 108 | // template: ` 109 | // 114 | // 115 | // 116 | // `, 117 | // }); 118 | // expect(dataObject('fileHasHeaders')).toEqual(true); 119 | // expect(wrapper.vm.$el).toMatchSnapshot(); 120 | // }); 121 | 122 | // it('headers toggle toggles headers', () => { 123 | // wrapper = testComponent(); 124 | // console.log(wrapper.text()); 125 | // // expect(dataObject('fileHasHeaders')).toEqual(false); 126 | // // await wrapper.find('[type=checkbox]').trigger('click'); 127 | // // expect(dataObject('fileHasHeaders')).toEqual(true); 128 | // }); 129 | 130 | 131 | // 132 | // it('can map when passed fields are an object', async () => { 133 | // objWrapper.vm.hasHeaders = true; 134 | // objWrapper.vm.sample = sample; 135 | // objWrapper.vm.csv = csv; 136 | // objWrapper.vm.map = map; 137 | // objWrapper.vm.submit(); 138 | // 139 | // let emitted = objWrapper.emitted(); 140 | // expect(emitted['update:modelValue'][0][0]).toEqual([{"name": "Susan", "age": "41"}, {"name": "Mike", "age": "5"}, {"name": "Jake", "age": "33"}, { 141 | // "name": "Jill", 142 | // "age": "30" 143 | // }]); 144 | // }); 145 | // 146 | // it('can map when passed fields are an array', async () => { 147 | // wrapper.vm.hasHeaders = true; 148 | // wrapper.vm.sample = sample; 149 | // wrapper.vm.csv = csv; 150 | // wrapper.vm.map = map; 151 | // wrapper.vm.submit(); 152 | // 153 | // let emitted = wrapper.emitted(); 154 | // expect(emitted['update:modelValue'][0][0]).toEqual([{"name": "Susan", "age": "41"}, {"name": "Mike", "age": "5"}, {"name": "Jake", "age": "33"}, { 155 | // "name": "Jill", 156 | // "age": "30" 157 | // }]); 158 | // }); 159 | // 160 | // it('validates mime types', async () => { 161 | // wrapper.vm.hasHeaders = true; 162 | // wrapper.vm.sample = sample; 163 | // wrapper.vm.csv = csv; 164 | // wrapper.vm.map = map; 165 | // expect(wrapper.vm.validateMimeType('peanut')).toEqual(false); 166 | // expect(wrapper.vm.validateMimeType('text/csv')).toEqual(true); 167 | // expect(wrapper.vm.validateMimeType('text/x-csv')).toEqual(true); 168 | // expect(wrapper.vm.validateMimeType('application/vnd.ms-excel')).toEqual(true); 169 | // expect(wrapper.vm.validateMimeType('text/plain')).toEqual(true); 170 | // }); 171 | 172 | // it('is a Vue instance', () => { 173 | // expect(wrapper.isVueInstance()).toBeTruthy(); 174 | // }); 175 | // it('installs as plugin', () => { 176 | // localVue.use(VueCsvImportPlugin); 177 | // expect(localVue.options.components["VueCsvImport"]).toBeDefined(); 178 | // }); 179 | // it('automatically maps fields when cases match', async () => { 180 | // objWrapper = mount(VueCsvImport, { 181 | // props: { 182 | // value: [], 183 | // autoMatchFields: true, 184 | // mapFields: {name: 'Name', age: 'Age'} 185 | // }, 186 | // data() { 187 | // return { 188 | // hasHeaders: true, 189 | // fieldsToMap: fields, 190 | // csv: csv, 191 | // } 192 | // } 193 | // }); 194 | // 195 | // objWrapper.vm.sample = [["Name", "Age", "Grade"], ["Susan", "41", "a"]]; 196 | // await objWrapper.find('button').trigger('click'); 197 | // expect(objWrapper.vm.map).toEqual({"age": 1, "name": 0}); 198 | // }); 199 | // 200 | // it('automatically maps fields when cases do not match', async () => { 201 | // objWrapper.setProps({autoMatchFields: true, autoMatchIgnoreCase: true}) 202 | // objWrapper.setData({hasHeaders: true, sample: sample, csv: csv, fieldsToMap: fields}); 203 | // expect(objWrapper.vm.map).toEqual({"age": 1, "name": 0}); 204 | // }); 205 | // it('automatically maps fields when cases match', async () => { 206 | // const objWrapper = mount(VueCsvImport, { 207 | // data() { 208 | // return { 209 | // map: map, 210 | // csv: csv, 211 | // fileSelected: false, 212 | // hasHeaders: true, 213 | // fieldsToMap: fields, 214 | // isValidFileMimeType: true, 215 | // sample: [["Name", "Age", "Grade"], ["Susan", "41", "a"]], 216 | // } 217 | // }, 218 | // props: { 219 | // value: [], 220 | // autoMatchFields: true, 221 | // mapFields: {name: 'Name', age: 'Age'} 222 | // } 223 | // }); 224 | // 225 | // // await objWrapper.find('button').trigger('click'); 226 | // 227 | // objWrapper.vm.sample = [["Name", "Age", "Grade"], ["Susan", "41", "a"]]; 228 | // 229 | // expect(objWrapper.vm.form.csv).not.toEqual(null); 230 | // }); 231 | // 232 | }); 233 | -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | publicPath: process.env.NODE_ENV === 'development' ? '/vue-csv-import/' : './' 3 | } 4 | --------------------------------------------------------------------------------