15 |
16 |
BS Custom File Input tests
17 |
18 |
19 |
20 | Destroy all
21 |
22 |
23 |
24 |
25 |
Examples
26 |
32 |
33 |
39 |
40 |
41 |
42 |
Example with form
43 |
62 |
63 |
64 |
65 |
Example with label containing a child
66 |
67 |
68 |
69 | Choose several files
70 |
71 |
72 |
73 |
74 |
75 |
76 |
85 |
86 |
87 |
--------------------------------------------------------------------------------
/tests/units/index.spec.js:
--------------------------------------------------------------------------------
1 | var customInputFile = [
2 | '
',
3 | ' ',
4 | ' Choose file ',
5 | '
',
6 | ].join('')
7 |
8 | describe('index.js', function () {
9 | var input
10 | var mochaFixtureDiv
11 |
12 | before(function () {
13 | mochaFixtureDiv = document.getElementById('mocha-fixture')
14 | })
15 |
16 | beforeEach(function() {
17 | mochaFixtureDiv.innerHTML = customInputFile
18 | input = document.querySelector('input')
19 | })
20 |
21 | afterEach(function () {
22 | mochaFixtureDiv.innerHTML = ''
23 | })
24 |
25 | describe('init', function () {
26 | it('should add bsCustomFileInput property', function () {
27 | bsCustomFileInput.init()
28 |
29 | expect(input.bsCustomFileInput).not.undefined
30 | })
31 |
32 | it('should store default text', function () {
33 | bsCustomFileInput.init()
34 |
35 | var label = document.querySelector('.custom-file-label')
36 |
37 | expect(input.bsCustomFileInput.defaultText).equal(label.innerHTML)
38 | })
39 |
40 | it('should add listener to the given input', function () {
41 | var spy = sinon.spy(input, 'addEventListener')
42 |
43 | bsCustomFileInput.init()
44 |
45 | expect(spy.called).equal(true)
46 | })
47 |
48 | it('should add an event listener on forms', function () {
49 | var form = document.createElement('form')
50 | form.innerHTML = customInputFile
51 |
52 | mochaFixtureDiv.appendChild(form)
53 |
54 | var spy = sinon.spy(form, 'addEventListener')
55 |
56 | bsCustomFileInput.init()
57 |
58 | expect(spy.called).to.be.true
59 | })
60 |
61 | it('should select only my custom input selector', function () {
62 | mochaFixtureDiv.innerHTML = [
63 | '
',
64 | customInputFile,
65 | ].join('')
66 |
67 | bsCustomFileInput.init('#test')
68 |
69 | var testInput = document.getElementById('test')
70 | var otherInput = document.querySelector('.custom-file input[type="file"]')
71 |
72 | expect(testInput.bsCustomFileInput).not.undefined
73 | expect(otherInput.bsCustomFileInput).undefined
74 | })
75 |
76 | it('should display already selected files on init', function () {
77 | input.setAttribute('multiple', '')
78 | Object.defineProperty(input, 'files', {
79 | value: [
80 | new File([], 'myFakeFile.exe'),
81 | new File([], 'fakeImage.png'),
82 | ],
83 | })
84 |
85 | bsCustomFileInput.init()
86 |
87 | var label = document.querySelector('.custom-file-label')
88 |
89 | expect(label.innerHTML).equal('myFakeFile.exe, fakeImage.png')
90 | })
91 | })
92 |
93 | describe('destroy', function () {
94 | it('should remove bsCustomFileInput property', function () {
95 | bsCustomFileInput.init()
96 | bsCustomFileInput.destroy()
97 |
98 | expect(input.bsCustomFileInput).to.undefined
99 | })
100 |
101 | it('should remove event listener on forms', function () {
102 | var form = document.createElement('form')
103 | form.innerHTML = [
104 | '
',
105 | ' ',
106 | '
',
107 | ].join('')
108 |
109 | mochaFixtureDiv.appendChild(form)
110 |
111 | var spy = sinon.spy(form, 'removeEventListener')
112 |
113 | bsCustomFileInput.init()
114 | bsCustomFileInput.destroy()
115 |
116 | expect(spy.called).to.be.true
117 | })
118 | })
119 | })
120 |
--------------------------------------------------------------------------------
/demo/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
Bootstrap custom file input
9 |
14 |
15 |
16 |
17 |
35 |
36 |
37 |
38 |
39 |
Examples
40 |
46 |
47 |
53 |
54 |
55 |
56 |
Example with form
57 |
76 |
77 |
78 |
79 |
Example with label containing a child
80 |
81 |
82 |
83 | Choose several files
84 |
85 |
86 |
87 |
88 |
89 |
100 |
101 |
110 |
111 |
112 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # bs-custom-file-input
2 |
3 | [](https://www.npmjs.com/package/bs-custom-file-input)
4 | [](https://david-dm.org/Johann-S/bs-custom-file-input)
5 | [](https://david-dm.org/Johann-S/bs-custom-file-input?type=dev)
6 | [](https://github.com/Johann-S/bs-custom-file-input/actions?workflow=Tests)
7 | [](https://coveralls.io/github/Johann-S/bs-custom-file-input?branch=master)
8 | [](https://github.com/Johann-S/bs-custom-file-input/tree/master/dist/bs-custom-file-input.min.js)
9 | [](https://www.browserstack.com/automate/public-build/L1Z6cllmR0pVVUZBRmxTaGtEcm1QamUxdTZoQmRLeUFvWVlOcW5iODNVWT0tLUZTVWRKUzc4T05xSmhlZlJObVRKNEE9PQ==--177788f5ac0c50dcd3dd3eed31e39662d5612e7f)
10 |
11 | A little plugin which makes Bootstrap 4 custom file input dynamic with no dependencies.
12 |
13 | You can use it on [React](https://stackblitz.com/edit/bs-custom-file-input-react) and [Angular](https://stackblitz.com/edit/bs-custom-file-input-angular) too because this plugin is written with the most used JavaScript framework: [VanillaJS](http://vanilla-js.com/).
14 |
15 | [Demo](https://bs-custom-file-input.netlify.com/)
16 |
17 | Features:
18 |
19 | - Works with Bootstrap 4
20 | - Works without *dependencies* and jQuery
21 | - Display file name
22 | - Display file names for `multiple` input
23 | - Reset your custom file input to its initial state
24 | - Handle form reset
25 | - Allow custom selectors for input, and form
26 | - Allow to drag and drop files
27 | - Allow you to change the default display with a child in the `
` element
28 | - Built in UMD to be used everywhere
29 | - Small, only **2kb** and less if you gzip it
30 |
31 | ## Table of contents
32 |
33 | - [Install](#install)
34 | - [How to use it](#how-to-use-it)
35 | - [Methods](#methods)
36 | - [Compatibility](#compatibility)
37 | - [Support me](#support-me)
38 | - [Thanks](#thanks)
39 | - [License](#license)
40 |
41 | ## Install
42 |
43 | ### With npm
44 |
45 | ```sh
46 | npm install bs-custom-file-input --save
47 | ```
48 |
49 | ### CDN
50 |
51 | CDN | Link
52 | ------------ | -------------
53 | jsDelivr | [`https://cdn.jsdelivr.net/npm/bs-custom-file-input/dist/bs-custom-file-input.js`](https://cdn.jsdelivr.net/npm/bs-custom-file-input/dist/bs-custom-file-input.js)
54 | jsDelivr, minified | [`https://cdn.jsdelivr.net/npm/bs-custom-file-input/dist/bs-custom-file-input.min.js`](https://cdn.jsdelivr.net/npm/bs-custom-file-input/dist/bs-custom-file-input.min.js)
55 |
56 | ## How to use it
57 |
58 | You should wait for the document ready event and call the `init` method to make your custom file input dynamic.
59 | We expose one global variable available everywhere: `bsCustomFileInput`
60 |
61 | ```js
62 | $(document).ready(function () {
63 | bsCustomFileInput.init()
64 | })
65 | ```
66 |
67 | ### Use it with npm
68 |
69 | ```js
70 | import bsCustomFileInput from 'bs-custom-file-input'
71 | ```
72 |
73 | For more examples check out [this file](https://github.com/Johann-S/bs-custom-file-input/blob/master/tests/index.html).
74 |
75 | This library is UMD ready so you can use it everywhere.
76 |
77 | ## Methods
78 |
79 | ### init
80 |
81 | Finds your Bootstrap custom file input and will make them dynamic.
82 |
83 | #### Parameters
84 |
85 | - `inputSelector`
86 | - default value: `.custom-file input[type="file"]`
87 | - type: `string`
88 |
89 | You can pass a custom input selector, but be sure to pass a **file input selector**
90 | - `formSelector`
91 | - default value: `form`
92 | - type: `string`
93 |
94 | Allows you to pass a custom form selector, but be sure to pass a **form selector**
95 |
96 | ### destroy
97 |
98 | Removes this plugin from your Bootstrap custom file input and restore them at their first initial state
99 |
100 | ## Compatibility
101 |
102 | bsCustomFileInput is compatible with:
103 |
104 | - IE10+
105 | - Edge
106 | - Firefox
107 | - Chrome
108 | - Safari
109 | - Chrome Android
110 | - Safari iOS
111 |
112 | You can find our BrowserStack list of browsers [here](https://github.com/Johann-S/bs-custom-file-input/blob/master/browsers.js).
113 |
114 | ## Support me
115 |
116 | If you want to thank me, you can support me and become my [Patron](https://www.patreon.com/jservoire)
117 |
118 | ## Thanks
119 |
120 | [](https://www.browserstack.com/)
121 |
122 | Thanks to [BrowserStack](https://www.browserstack.com/) for providing the infrastructure that allows us to test in real browsers!
123 |
124 | ## License
125 |
126 | [MIT](https://github.com/Johann-S/bs-custom-file-input/blob/master/LICENSE)
127 |
--------------------------------------------------------------------------------
/dist/bs-custom-file-input.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * bsCustomFileInput v1.3.4 (https://github.com/Johann-S/bs-custom-file-input)
3 | * Copyright 2018 - 2020 Johann-S
4 | * Licensed under MIT (https://github.com/Johann-S/bs-custom-file-input/blob/master/LICENSE)
5 | */
6 | (function (global, factory) {
7 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
8 | typeof define === 'function' && define.amd ? define(factory) :
9 | (global = global || self, global.bsCustomFileInput = factory());
10 | }(this, (function () { 'use strict';
11 |
12 | var Selector = {
13 | CUSTOMFILE: '.custom-file input[type="file"]',
14 | CUSTOMFILELABEL: '.custom-file-label',
15 | FORM: 'form',
16 | INPUT: 'input'
17 | };
18 |
19 | var textNodeType = 3;
20 |
21 | var getDefaultText = function getDefaultText(input) {
22 | var defaultText = '';
23 | var label = input.parentNode.querySelector(Selector.CUSTOMFILELABEL);
24 |
25 | if (label) {
26 | defaultText = label.textContent;
27 | }
28 |
29 | return defaultText;
30 | };
31 |
32 | var findFirstChildNode = function findFirstChildNode(element) {
33 | if (element.childNodes.length > 0) {
34 | var childNodes = [].slice.call(element.childNodes);
35 |
36 | for (var i = 0; i < childNodes.length; i++) {
37 | var node = childNodes[i];
38 |
39 | if (node.nodeType !== textNodeType) {
40 | return node;
41 | }
42 | }
43 | }
44 |
45 | return element;
46 | };
47 |
48 | var restoreDefaultText = function restoreDefaultText(input) {
49 | var defaultText = input.bsCustomFileInput.defaultText;
50 | var label = input.parentNode.querySelector(Selector.CUSTOMFILELABEL);
51 |
52 | if (label) {
53 | var element = findFirstChildNode(label);
54 | element.textContent = defaultText;
55 | }
56 | };
57 |
58 | var fileApi = !!window.File;
59 | var FAKE_PATH = 'fakepath';
60 | var FAKE_PATH_SEPARATOR = '\\';
61 |
62 | var getSelectedFiles = function getSelectedFiles(input) {
63 | if (input.hasAttribute('multiple') && fileApi) {
64 | return [].slice.call(input.files).map(function (file) {
65 | return file.name;
66 | }).join(', ');
67 | }
68 |
69 | if (input.value.indexOf(FAKE_PATH) !== -1) {
70 | var splittedValue = input.value.split(FAKE_PATH_SEPARATOR);
71 | return splittedValue[splittedValue.length - 1];
72 | }
73 |
74 | return input.value;
75 | };
76 |
77 | function handleInputChange() {
78 | var label = this.parentNode.querySelector(Selector.CUSTOMFILELABEL);
79 |
80 | if (label) {
81 | var element = findFirstChildNode(label);
82 | var inputValue = getSelectedFiles(this);
83 |
84 | if (inputValue.length) {
85 | element.textContent = inputValue;
86 | } else {
87 | restoreDefaultText(this);
88 | }
89 | }
90 | }
91 |
92 | function handleFormReset() {
93 | var customFileList = [].slice.call(this.querySelectorAll(Selector.INPUT)).filter(function (input) {
94 | return !!input.bsCustomFileInput;
95 | });
96 |
97 | for (var i = 0, len = customFileList.length; i < len; i++) {
98 | restoreDefaultText(customFileList[i]);
99 | }
100 | }
101 |
102 | var customProperty = 'bsCustomFileInput';
103 | var Event = {
104 | FORMRESET: 'reset',
105 | INPUTCHANGE: 'change'
106 | };
107 | var bsCustomFileInput = {
108 | init: function init(inputSelector, formSelector) {
109 | if (inputSelector === void 0) {
110 | inputSelector = Selector.CUSTOMFILE;
111 | }
112 |
113 | if (formSelector === void 0) {
114 | formSelector = Selector.FORM;
115 | }
116 |
117 | var customFileInputList = [].slice.call(document.querySelectorAll(inputSelector));
118 | var formList = [].slice.call(document.querySelectorAll(formSelector));
119 |
120 | for (var i = 0, len = customFileInputList.length; i < len; i++) {
121 | var input = customFileInputList[i];
122 | Object.defineProperty(input, customProperty, {
123 | value: {
124 | defaultText: getDefaultText(input)
125 | },
126 | writable: true
127 | });
128 | handleInputChange.call(input);
129 | input.addEventListener(Event.INPUTCHANGE, handleInputChange);
130 | }
131 |
132 | for (var _i = 0, _len = formList.length; _i < _len; _i++) {
133 | formList[_i].addEventListener(Event.FORMRESET, handleFormReset);
134 |
135 | Object.defineProperty(formList[_i], customProperty, {
136 | value: true,
137 | writable: true
138 | });
139 | }
140 | },
141 | destroy: function destroy() {
142 | var formList = [].slice.call(document.querySelectorAll(Selector.FORM)).filter(function (form) {
143 | return !!form.bsCustomFileInput;
144 | });
145 | var customFileInputList = [].slice.call(document.querySelectorAll(Selector.INPUT)).filter(function (input) {
146 | return !!input.bsCustomFileInput;
147 | });
148 |
149 | for (var i = 0, len = customFileInputList.length; i < len; i++) {
150 | var input = customFileInputList[i];
151 | restoreDefaultText(input);
152 | input[customProperty] = undefined;
153 | input.removeEventListener(Event.INPUTCHANGE, handleInputChange);
154 | }
155 |
156 | for (var _i2 = 0, _len2 = formList.length; _i2 < _len2; _i2++) {
157 | formList[_i2].removeEventListener(Event.FORMRESET, handleFormReset);
158 |
159 | formList[_i2][customProperty] = undefined;
160 | }
161 | }
162 | };
163 |
164 | return bsCustomFileInput;
165 |
166 | })));
167 | //# sourceMappingURL=bs-custom-file-input.js.map
168 |
--------------------------------------------------------------------------------
/dist/bs-custom-file-input.min.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"bs-custom-file-input.min.js","sources":["../src/selector.js","../src/util.js","../src/eventHandlers.js","../src/index.js"],"sourcesContent":["const Selector = {\n CUSTOMFILE: '.custom-file input[type=\"file\"]',\n CUSTOMFILELABEL: '.custom-file-label',\n FORM: 'form',\n INPUT: 'input',\n}\n\nexport default Selector\n","import Selector from './selector'\n\nconst textNodeType = 3\nconst getDefaultText = (input) => {\n let defaultText = ''\n\n const label = input.parentNode.querySelector(Selector.CUSTOMFILELABEL)\n\n if (label) {\n defaultText = label.textContent\n }\n\n return defaultText\n}\n\nconst findFirstChildNode = (element) => {\n if (element.childNodes.length > 0) {\n const childNodes = [].slice.call(element.childNodes)\n\n for (let i = 0; i < childNodes.length; i++) {\n const node = childNodes[i]\n if (node.nodeType !== textNodeType) {\n return node\n }\n }\n }\n\n return element\n}\n\nconst restoreDefaultText = (input) => {\n const defaultText = input.bsCustomFileInput.defaultText\n const label = input.parentNode.querySelector(Selector.CUSTOMFILELABEL)\n\n if (label) {\n const element = findFirstChildNode(label)\n\n element.textContent = defaultText\n }\n}\n\nexport {\n getDefaultText,\n findFirstChildNode,\n restoreDefaultText,\n}\n","import { findFirstChildNode, restoreDefaultText } from './util'\nimport Selector from './selector'\n\nconst fileApi = !!window.File\nconst FAKE_PATH = 'fakepath'\nconst FAKE_PATH_SEPARATOR = '\\\\'\n\nconst getSelectedFiles = (input) => {\n if (input.hasAttribute('multiple') && fileApi) {\n return [].slice.call(input.files)\n .map((file) => file.name)\n .join(', ')\n }\n\n if (input.value.indexOf(FAKE_PATH) !== -1) {\n const splittedValue = input.value.split(FAKE_PATH_SEPARATOR)\n\n return splittedValue[splittedValue.length - 1]\n }\n\n return input.value\n}\n\nfunction handleInputChange() {\n const label = this.parentNode.querySelector(Selector.CUSTOMFILELABEL)\n\n if (label) {\n const element = findFirstChildNode(label)\n const inputValue = getSelectedFiles(this)\n\n if (inputValue.length) {\n element.textContent = inputValue\n } else {\n restoreDefaultText(this)\n }\n }\n}\n\nfunction handleFormReset() {\n const customFileList = [].slice.call(this.querySelectorAll(Selector.INPUT))\n .filter((input) => !!input.bsCustomFileInput)\n\n for (let i = 0, len = customFileList.length; i < len; i++) {\n restoreDefaultText(customFileList[i])\n }\n}\n\nexport {\n handleInputChange,\n handleFormReset,\n}\n","import { getDefaultText, restoreDefaultText } from './util'\nimport { handleFormReset, handleInputChange } from './eventHandlers'\nimport Selector from './selector'\n\nconst customProperty = 'bsCustomFileInput'\nconst Event = {\n FORMRESET : 'reset',\n INPUTCHANGE : 'change',\n}\n\nconst bsCustomFileInput = {\n init(inputSelector = Selector.CUSTOMFILE, formSelector = Selector.FORM) {\n const customFileInputList = [].slice.call(document.querySelectorAll(inputSelector))\n const formList = [].slice.call(document.querySelectorAll(formSelector))\n\n for (let i = 0, len = customFileInputList.length; i < len; i++) {\n const input = customFileInputList[i]\n\n Object.defineProperty(input, customProperty, {\n value: {\n defaultText: getDefaultText(input),\n },\n writable: true,\n })\n\n handleInputChange.call(input)\n input.addEventListener(Event.INPUTCHANGE, handleInputChange)\n }\n\n for (let i = 0, len = formList.length; i < len; i++) {\n formList[i].addEventListener(Event.FORMRESET, handleFormReset)\n Object.defineProperty(formList[i], customProperty, {\n value: true,\n writable: true,\n })\n }\n },\n\n destroy() {\n const formList = [].slice.call(document.querySelectorAll(Selector.FORM))\n .filter((form) => !!form.bsCustomFileInput)\n const customFileInputList = [].slice.call(document.querySelectorAll(Selector.INPUT))\n .filter((input) => !!input.bsCustomFileInput)\n\n for (let i = 0, len = customFileInputList.length; i < len; i++) {\n const input = customFileInputList[i]\n\n restoreDefaultText(input)\n input[customProperty] = undefined\n\n input.removeEventListener(Event.INPUTCHANGE, handleInputChange)\n }\n\n for (let i = 0, len = formList.length; i < len; i++) {\n formList[i].removeEventListener(Event.FORMRESET, handleFormReset)\n formList[i][customProperty] = undefined\n }\n },\n}\n\nexport default bsCustomFileInput\n"],"names":["Selector","CUSTOMFILE","CUSTOMFILELABEL","FORM","INPUT","findFirstChildNode","element","childNodes","length","slice","call","i","node","nodeType","restoreDefaultText","input","defaultText","bsCustomFileInput","label","parentNode","querySelector","textContent","fileApi","window","File","getSelectedFiles","hasAttribute","files","map","file","name","join","value","indexOf","splittedValue","split","handleInputChange","this","inputValue","handleFormReset","customFileList","querySelectorAll","filter","len","customProperty","Event","init","inputSelector","formSelector","customFileInputList","document","formList","Object","defineProperty","writable","addEventListener","destroy","form","undefined","removeEventListener"],"mappings":"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"}
--------------------------------------------------------------------------------
/tests/units/eventHandlers.spec.js:
--------------------------------------------------------------------------------
1 | var customInputFile = [
2 | '',
3 | ' ',
4 | ' Choose file ',
5 | '
',
6 | ].join('')
7 |
8 | describe('eventHandlers.js', function () {
9 | var input
10 | var mochaFixtureDiv
11 |
12 | before(function () {
13 | mochaFixtureDiv = document.getElementById('mocha-fixture')
14 | })
15 |
16 | beforeEach(function() {
17 | mochaFixtureDiv.innerHTML = customInputFile
18 | input = document.querySelector('input')
19 | })
20 |
21 | afterEach(function () {
22 | mochaFixtureDiv.innerHTML = ''
23 | })
24 |
25 | describe('handleInputChange', function () {
26 | it('should change the label when a file is selected', function (done) {
27 | bsCustomFileInput.init()
28 |
29 | var label = document.querySelector('.custom-file-label')
30 |
31 | input.addEventListener('change', function () {
32 | expect(label.innerHTML).equal(input.value)
33 | done()
34 | })
35 |
36 | Object.defineProperty(input, 'value', {
37 | value: 'myFakeFile.exe',
38 | })
39 |
40 | input.dispatchEvent(new Event('change'))
41 | })
42 |
43 | it('should change the label when a file is selected and escape html', function (done) {
44 | bsCustomFileInput.init()
45 |
46 | var label = document.querySelector('.custom-file-label')
47 | var expectedValue = '<svg onload=alert(1)>'
48 |
49 | input.addEventListener('change', function () {
50 | expect(label.innerHTML).equal(expectedValue)
51 | done()
52 | })
53 |
54 | Object.defineProperty(input, 'value', {
55 | value: '',
56 | })
57 |
58 | input.dispatchEvent(new Event('change'))
59 | })
60 |
61 | it('should remove fakepath if found', function (done) {
62 | bsCustomFileInput.init()
63 |
64 | var label = document.querySelector('.custom-file-label')
65 |
66 | input.addEventListener('change', function () {
67 | expect(label.innerHTML).equal('myFakeFile.exe')
68 | done()
69 | })
70 |
71 | Object.defineProperty(input, 'value', {
72 | value: 'C:\\fakepath\\myFakeFile.exe',
73 | })
74 |
75 | input.dispatchEvent(new Event('change'))
76 | })
77 |
78 | it('should restore default text if value is empty', function (done) {
79 | bsCustomFileInput.init()
80 |
81 | var label = document.querySelector('.custom-file-label')
82 |
83 | function firstListener() {
84 | expect(label.innerHTML).equal('myFakeFile.exe')
85 | input.removeEventListener('change', firstListener)
86 |
87 | input.addEventListener('change', secondListener)
88 | input.value = ''
89 | input.dispatchEvent(new Event('change'))
90 | }
91 |
92 | function secondListener() {
93 | expect(label.innerHTML).equal('Choose file')
94 | done()
95 | }
96 |
97 | input.addEventListener('change', firstListener)
98 |
99 | Object.defineProperty(input, 'value', {
100 | value: 'myFakeFile.exe',
101 | configurable: true,
102 | writable: true,
103 | })
104 |
105 | input.dispatchEvent(new Event('change'))
106 | })
107 |
108 | it('should change the label when files are selected', function (done) {
109 | bsCustomFileInput.init()
110 |
111 | var label = document.querySelector('.custom-file-label')
112 |
113 | input.addEventListener('change', function () {
114 | expect(label.innerHTML).equal('myFakeFile.exe, fakeImage.png')
115 | done()
116 | })
117 |
118 | Object.defineProperty(input, 'files', {
119 | value: [
120 | new File([], 'myFakeFile.exe'),
121 | new File([], 'fakeImage.png'),
122 | ],
123 | })
124 |
125 | input.setAttribute('multiple', '')
126 | input.dispatchEvent(new Event('change'))
127 | })
128 |
129 | it('should do nothing if the input file has no label element', function (done) {
130 | mochaFixtureDiv.innerHTML = [
131 | '',
132 | ' ',
133 | '
',
134 | ].join('')
135 |
136 | bsCustomFileInput.init()
137 |
138 | input = document.querySelector('input')
139 |
140 | input.addEventListener('change', function () {
141 | expect(input.bsCustomFileInput).not.undefined
142 | done()
143 | })
144 |
145 | input.dispatchEvent(new Event('change'))
146 | })
147 |
148 | it('should not write in label if there is a child', function (done) {
149 | mochaFixtureDiv.innerHTML = [
150 | '',
151 | ' ',
152 | ' ',
153 | ' Choose file ',
154 | ' ',
155 | '
',
156 | ].join('')
157 |
158 | bsCustomFileInput.init()
159 |
160 | var span = document.querySelector('.custom-file-label span')
161 | input = document.querySelector('input')
162 |
163 | input.addEventListener('change', function () {
164 | expect(span.innerHTML).equal('myFakeFile.exe, fakeImage.png')
165 | done()
166 | })
167 |
168 | Object.defineProperty(input, 'files', {
169 | value: [
170 | new File([], 'myFakeFile.exe'),
171 | new File([], 'fakeImage.png'),
172 | ],
173 | })
174 |
175 | input.dispatchEvent(new Event('change'))
176 | })
177 |
178 | it('should use the label if no children', function (done) {
179 | mochaFixtureDiv.innerHTML = [
180 | '',
181 | ' ',
182 | ' ',
183 | '
',
184 | ].join('')
185 |
186 | bsCustomFileInput.init()
187 |
188 | var label = document.querySelector('.custom-file-label')
189 | input = document.querySelector('input')
190 |
191 | input.addEventListener('change', function () {
192 | expect(label.innerHTML).equal('myFakeFile.exe, fakeImage.png')
193 | done()
194 | })
195 |
196 | Object.defineProperty(input, 'files', {
197 | value: [
198 | new File([], 'myFakeFile.exe'),
199 | new File([], 'fakeImage.png'),
200 | ],
201 | })
202 |
203 | input.dispatchEvent(new Event('change'))
204 | })
205 | })
206 |
207 | describe('handleFormReset', function () {
208 | var form
209 |
210 | beforeEach(function () {
211 | form = document.createElement('form')
212 | form.innerHTML = customInputFile
213 |
214 | mochaFixtureDiv.innerHTML = ''
215 | mochaFixtureDiv.appendChild(form)
216 |
217 | input = document.querySelector('input')
218 | })
219 |
220 | it('should reset input value and restore default text', function (done) {
221 | bsCustomFileInput.init()
222 | var label = document.querySelector('.custom-file-label')
223 |
224 | input.addEventListener('change', function () {
225 | expect(label.innerHTML).equal('myFakeFile.exe')
226 |
227 | form.reset()
228 |
229 | expect(label.innerHTML).equal('Choose file')
230 | done()
231 | })
232 |
233 | Object.defineProperty(input, 'value', {
234 | value: 'myFakeFile.exe',
235 | })
236 |
237 | input.dispatchEvent(new Event('change'))
238 | })
239 | })
240 | })
241 |
--------------------------------------------------------------------------------
/dist/bs-custom-file-input.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"bs-custom-file-input.js","sources":["../src/selector.js","../src/util.js","../src/eventHandlers.js","../src/index.js"],"sourcesContent":["const Selector = {\n CUSTOMFILE: '.custom-file input[type=\"file\"]',\n CUSTOMFILELABEL: '.custom-file-label',\n FORM: 'form',\n INPUT: 'input',\n}\n\nexport default Selector\n","import Selector from './selector'\n\nconst textNodeType = 3\nconst getDefaultText = (input) => {\n let defaultText = ''\n\n const label = input.parentNode.querySelector(Selector.CUSTOMFILELABEL)\n\n if (label) {\n defaultText = label.textContent\n }\n\n return defaultText\n}\n\nconst findFirstChildNode = (element) => {\n if (element.childNodes.length > 0) {\n const childNodes = [].slice.call(element.childNodes)\n\n for (let i = 0; i < childNodes.length; i++) {\n const node = childNodes[i]\n if (node.nodeType !== textNodeType) {\n return node\n }\n }\n }\n\n return element\n}\n\nconst restoreDefaultText = (input) => {\n const defaultText = input.bsCustomFileInput.defaultText\n const label = input.parentNode.querySelector(Selector.CUSTOMFILELABEL)\n\n if (label) {\n const element = findFirstChildNode(label)\n\n element.textContent = defaultText\n }\n}\n\nexport {\n getDefaultText,\n findFirstChildNode,\n restoreDefaultText,\n}\n","import { findFirstChildNode, restoreDefaultText } from './util'\nimport Selector from './selector'\n\nconst fileApi = !!window.File\nconst FAKE_PATH = 'fakepath'\nconst FAKE_PATH_SEPARATOR = '\\\\'\n\nconst getSelectedFiles = (input) => {\n if (input.hasAttribute('multiple') && fileApi) {\n return [].slice.call(input.files)\n .map((file) => file.name)\n .join(', ')\n }\n\n if (input.value.indexOf(FAKE_PATH) !== -1) {\n const splittedValue = input.value.split(FAKE_PATH_SEPARATOR)\n\n return splittedValue[splittedValue.length - 1]\n }\n\n return input.value\n}\n\nfunction handleInputChange() {\n const label = this.parentNode.querySelector(Selector.CUSTOMFILELABEL)\n\n if (label) {\n const element = findFirstChildNode(label)\n const inputValue = getSelectedFiles(this)\n\n if (inputValue.length) {\n element.textContent = inputValue\n } else {\n restoreDefaultText(this)\n }\n }\n}\n\nfunction handleFormReset() {\n const customFileList = [].slice.call(this.querySelectorAll(Selector.INPUT))\n .filter((input) => !!input.bsCustomFileInput)\n\n for (let i = 0, len = customFileList.length; i < len; i++) {\n restoreDefaultText(customFileList[i])\n }\n}\n\nexport {\n handleInputChange,\n handleFormReset,\n}\n","import { getDefaultText, restoreDefaultText } from './util'\nimport { handleFormReset, handleInputChange } from './eventHandlers'\nimport Selector from './selector'\n\nconst customProperty = 'bsCustomFileInput'\nconst Event = {\n FORMRESET : 'reset',\n INPUTCHANGE : 'change',\n}\n\nconst bsCustomFileInput = {\n init(inputSelector = Selector.CUSTOMFILE, formSelector = Selector.FORM) {\n const customFileInputList = [].slice.call(document.querySelectorAll(inputSelector))\n const formList = [].slice.call(document.querySelectorAll(formSelector))\n\n for (let i = 0, len = customFileInputList.length; i < len; i++) {\n const input = customFileInputList[i]\n\n Object.defineProperty(input, customProperty, {\n value: {\n defaultText: getDefaultText(input),\n },\n writable: true,\n })\n\n handleInputChange.call(input)\n input.addEventListener(Event.INPUTCHANGE, handleInputChange)\n }\n\n for (let i = 0, len = formList.length; i < len; i++) {\n formList[i].addEventListener(Event.FORMRESET, handleFormReset)\n Object.defineProperty(formList[i], customProperty, {\n value: true,\n writable: true,\n })\n }\n },\n\n destroy() {\n const formList = [].slice.call(document.querySelectorAll(Selector.FORM))\n .filter((form) => !!form.bsCustomFileInput)\n const customFileInputList = [].slice.call(document.querySelectorAll(Selector.INPUT))\n .filter((input) => !!input.bsCustomFileInput)\n\n for (let i = 0, len = customFileInputList.length; i < len; i++) {\n const input = customFileInputList[i]\n\n restoreDefaultText(input)\n input[customProperty] = undefined\n\n input.removeEventListener(Event.INPUTCHANGE, handleInputChange)\n }\n\n for (let i = 0, len = formList.length; i < len; i++) {\n formList[i].removeEventListener(Event.FORMRESET, handleFormReset)\n formList[i][customProperty] = undefined\n }\n },\n}\n\nexport default bsCustomFileInput\n"],"names":["Selector","CUSTOMFILE","CUSTOMFILELABEL","FORM","INPUT","textNodeType","getDefaultText","input","defaultText","label","parentNode","querySelector","textContent","findFirstChildNode","element","childNodes","length","slice","call","i","node","nodeType","restoreDefaultText","bsCustomFileInput","fileApi","window","File","FAKE_PATH","FAKE_PATH_SEPARATOR","getSelectedFiles","hasAttribute","files","map","file","name","join","value","indexOf","splittedValue","split","handleInputChange","inputValue","handleFormReset","customFileList","querySelectorAll","filter","len","customProperty","Event","FORMRESET","INPUTCHANGE","init","inputSelector","formSelector","customFileInputList","document","formList","Object","defineProperty","writable","addEventListener","destroy","form","undefined","removeEventListener"],"mappings":"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"}
--------------------------------------------------------------------------------