├── .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 | [](https://npmjs.com/package/vue-csv-import)
4 | [](LICENSE.md)
5 | 
6 | [](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 |
51 |
55 |
56 |
57 |
58 |
59 |
60 |
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 |
81 |
94 |
95 |
96 |
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 |
140 |
141 | ...
142 |
143 | ...
144 |
145 |
146 | ```
147 |
148 | Or with custom markup:
149 |
150 | ```vue
151 |
152 |
153 |
154 | ...
155 |
156 |
157 |
158 | ...
159 |
160 |
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 |
186 |
187 | ...
188 |
189 | ...
190 |
191 |
192 | ```
193 |
194 | Or with custom markup:
195 |
196 | ```vue
197 |
198 |
199 |
200 | ...
201 |
202 | ...
203 |
204 | ...
205 |
206 |
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 |
235 |
236 | ...
237 |
238 | ...
239 |
240 |
241 | ```
242 |
243 | Or use slot for custom markup:
244 |
245 | ```vue
246 |
247 |
248 |
249 | ...
250 |
251 | ...
252 |
253 | ...
254 |
255 |
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 |
284 |
285 | ...
286 |
287 | ...
288 |
289 |
290 | ```
291 |
292 | Or use slot for custom markup:
293 |
294 | ```vue
295 |
296 |
297 |
298 |
299 |
300 |
301 |
302 |
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 |
328 |
329 | ...
330 |
331 | ...
332 |
333 |
334 | ```
335 |
336 | Or use slot for custom markup:
337 |
338 | ```vue
339 |
340 |
341 |
342 | ...
343 |
344 | ...
345 |
346 | ...
347 |
348 |
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 |
2 |
3 |

4 |
5 |
10 |
11 |
12 |
13 |
Example:
14 |
<vue-csv-import
15 | v-model="csv"
16 | :fields="{name: {required: false, label: 'Name'}, age: {required: true, label: 'Age'}}"
17 | >
18 | <vue-csv-toggle-headers></vue-csv-toggle-headers>
19 | <vue-csv-errors></vue-csv-errors>
20 | <vue-csv-input></vue-csv-input>
21 | <vue-csv-table-map :auto-match="false"></vue-csv-table-map>
22 | </vue-csv-import>
23 |
24 |
25 |
26 |
27 |
Result:
28 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
{{ csv }}
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
113 |
114 |
139 |
--------------------------------------------------------------------------------
/src/components/VueCsvErrors.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 | {{ error }}
8 |
9 |
10 |
11 |
12 |
26 |
--------------------------------------------------------------------------------
/src/components/VueCsvImport.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
108 |
--------------------------------------------------------------------------------
/src/components/VueCsvInput.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
112 |
--------------------------------------------------------------------------------
/src/components/VueCsvMap.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
11 | {{ labels.fieldColumn }} |
12 | {{ labels.csvColumn }} |
13 |
14 |
15 |
16 |
17 | {{ field.label }} |
18 |
19 |
32 | |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
99 |
--------------------------------------------------------------------------------
/src/components/VueCsvSubmit.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
9 |
51 |
--------------------------------------------------------------------------------
/src/components/VueCsvTableMap.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
11 |
12 | {{ field }}
13 |
34 | |
35 |
36 |
37 |
38 |
39 | {{item}} |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
132 |
--------------------------------------------------------------------------------
/src/components/VueCsvToggleHeaders.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
12 |
13 |
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 |
--------------------------------------------------------------------------------