Skip to content

Commit

Permalink
input-file: create a plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
Johann-S committed Jun 19, 2020
1 parent edbcc40 commit fdaccc5
Show file tree
Hide file tree
Showing 11 changed files with 701 additions and 22 deletions.
4 changes: 2 additions & 2 deletions .bundlewatch.config.json
Expand Up @@ -38,7 +38,7 @@
},
{
"path": "./dist/js/bootstrap.bundle.min.js",
"maxSize": "22.5 kB"
"maxSize": "22.75 kB"
},
{
"path": "./dist/js/bootstrap.esm.js",
Expand All @@ -54,7 +54,7 @@
},
{
"path": "./dist/js/bootstrap.min.js",
"maxSize": "16 kB"
"maxSize": "16.25 kB"
}
],
"ci": {
Expand Down
3 changes: 2 additions & 1 deletion build/build-plugins.js
Expand Up @@ -33,6 +33,7 @@ const bsPlugins = {
Carousel: path.resolve(__dirname, '../js/src/carousel.js'),
Collapse: path.resolve(__dirname, '../js/src/collapse.js'),
Dropdown: path.resolve(__dirname, '../js/src/dropdown.js'),
FileInput: path.resolve(__dirname, '../js/src/file-input.js'),
Modal: path.resolve(__dirname, '../js/src/modal.js'),
Popover: path.resolve(__dirname, '../js/src/popover.js'),
ScrollSpy: path.resolve(__dirname, '../js/src/scrollspy.js'),
Expand Down Expand Up @@ -73,7 +74,7 @@ const getConfigByPluginKey = pluginKey => {
}
}

if (pluginKey === 'Alert' || pluginKey === 'Tab') {
if (pluginKey === 'Alert' || pluginKey === 'Tab' || pluginKey === 'FileInput') {
return defaultPluginConfig
}

Expand Down
2 changes: 2 additions & 0 deletions js/index.esm.js
Expand Up @@ -10,6 +10,7 @@ import Button from './src/button'
import Carousel from './src/carousel'
import Collapse from './src/collapse'
import Dropdown from './src/dropdown'
import FileInput from './src/file-input'
import Modal from './src/modal'
import Popover from './src/popover'
import ScrollSpy from './src/scrollspy'
Expand All @@ -23,6 +24,7 @@ export {
Carousel,
Collapse,
Dropdown,
FileInput,
Modal,
Popover,
ScrollSpy,
Expand Down
2 changes: 2 additions & 0 deletions js/index.umd.js
Expand Up @@ -10,6 +10,7 @@ import Button from './src/button'
import Carousel from './src/carousel'
import Collapse from './src/collapse'
import Dropdown from './src/dropdown'
import FileInput from './src/file-input'
import Modal from './src/modal'
import Popover from './src/popover'
import ScrollSpy from './src/scrollspy'
Expand All @@ -23,6 +24,7 @@ export default {
Carousel,
Collapse,
Dropdown,
FileInput,
Modal,
Popover,
ScrollSpy,
Expand Down
163 changes: 163 additions & 0 deletions js/src/file-input.js
@@ -0,0 +1,163 @@
/**
* --------------------------------------------------------------------------
* Bootstrap (v5.0.0-alpha1): file-input.js
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* --------------------------------------------------------------------------
*/

import { getjQuery } from './util/index'
import Data from './dom/data'
import EventHandler from './dom/event-handler'
import SelectorEngine from './dom/selector-engine'

/**
* ------------------------------------------------------------------------
* Constants
* ------------------------------------------------------------------------
*/

const NAME = 'fileInput'
const VERSION = '5.0.0-alpha1'
const DATA_KEY = 'bs.file-input'
const EVENT_KEY = `.${DATA_KEY}`
const DATA_API_KEY = '.data-api'

const SELECTOR_DATA_TOGGLE = '[data-toggle="file-input"]'
const SELECTOR_FILE_INPUT = '.form-file-input'
const SELECTOR_FILE_INPUT_LABEL = '.form-file-text'
const SELECTOR_FORM = 'form'

const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`
const EVENT_CHANGE_DATA_API = `change${EVENT_KEY}${DATA_API_KEY}`
const EVENT_RESET_DATA_API = `reset${EVENT_KEY}${DATA_API_KEY}`

// TODO: remove when we drop Opera Mini support
const HAS_FILE_API = Boolean(window.File)
const FAKE_PATH = 'fakepath'
const FAKE_PATH_SEPARATOR = '\\'

/**
* ------------------------------------------------------------------------
* Class Definition
* ------------------------------------------------------------------------
*/

class FileInput {
constructor(element) {
this._element = element
this._labelInput = SelectorEngine.findOne(SELECTOR_FILE_INPUT_LABEL, this._element)
this._input = SelectorEngine.findOne(SELECTOR_FILE_INPUT, this._element)
this._defaultText = this._labelInput.textContent

EventHandler.on(this._input, EVENT_CHANGE_DATA_API, () => this._handleChange())
Data.setData(element, DATA_KEY, this)
}

// Getters

static get VERSION() {
return VERSION
}

// Public

dispose() {
[window, this._element].forEach(htmlElement => EventHandler.off(htmlElement, EVENT_KEY))

Data.removeData(this._element, DATA_KEY)
this._element = null
}

restoreDefaultText() {
this._labelInput.textContent = this._defaultText
}

// Private

_handleChange() {
const inputValue = this._getSelectedFiles()

if (inputValue.length) {
this._labelInput.textContent = inputValue
} else {
this.restoreDefaultText()
}
}

_getSelectedFiles() {
if (this._input.hasAttribute('multiple') && HAS_FILE_API) {
return [].slice.call(this._input.files)
.map(file => file.name)
.join(', ')
}

if (this._input.value.indexOf(FAKE_PATH) !== -1) {
const splitValue = this._input.value.split(FAKE_PATH_SEPARATOR)

return splitValue[splitValue.length - 1]
}

return this._input.value
}

// Static

static jQueryInterface() {
return this.each(function () {
FileInput.createInstance(this)
})
}

static getInstance(element) {
return Data.getData(element, DATA_KEY)
}

static createInstance(element) {
return Data.getData(element, DATA_KEY) ?
Data.getData(element, DATA_KEY) :
new FileInput(element)
}
}

/**
* ------------------------------------------------------------------------
* Data Api implementation
* ------------------------------------------------------------------------
*/
EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, event => {
FileInput.createInstance(event.target.closest(SELECTOR_DATA_TOGGLE))
})

EventHandler.on(document, EVENT_RESET_DATA_API, SELECTOR_FORM, event => {
const form = event.target

SelectorEngine.find(SELECTOR_DATA_TOGGLE, form)
.filter(inputFileNode => FileInput.getInstance(inputFileNode))
.forEach(inputFileNode => {
const inputFile = FileInput.getInstance(inputFileNode)

inputFile.restoreDefaultText()
})
})

const $ = getjQuery()

/**
* ------------------------------------------------------------------------
* jQuery
* ------------------------------------------------------------------------
* add .fileInput to jQuery only if jQuery is present
*/

/* istanbul ignore if */
if ($) {
const JQUERY_NO_CONFLICT = $.fn[NAME]
$.fn[NAME] = FileInput.jQueryInterface
$.fn[NAME].Constructor = FileInput
$.fn[NAME].noConflict = () => {
$.fn[NAME] = JQUERY_NO_CONFLICT
return FileInput.jQueryInterface
}
}

export default FileInput
7 changes: 7 additions & 0 deletions js/tests/helpers/fixture.js
Expand Up @@ -39,3 +39,10 @@ export const jQueryMock = {
})
}
}

export const mockFileApi = (part, name) => {
return {
part,
name
}
}

0 comments on commit fdaccc5

Please sign in to comment.