discretely separated all form input types into discrete es6 classes to allow for easy configuration/enforcement of markup/classes/structure

This commit is contained in:
Kevin Ross 2015-12-05 17:45:38 -06:00
parent 6f0e41a486
commit 108da48a0b
17 changed files with 426 additions and 261 deletions

View File

@ -105,11 +105,13 @@ module.exports = function (grunt) {
'dist/js/babel/util.js': 'js/src/util.js', 'dist/js/babel/util.js': 'js/src/util.js',
'dist/js/babel/ripples.js': 'js/src/ripples.js', 'dist/js/babel/ripples.js': 'js/src/ripples.js',
'dist/js/babel/autofill.js': 'js/src/autofill.js', 'dist/js/babel/autofill.js': 'js/src/autofill.js',
'dist/js/babel/textInput.js': 'js/src/textInput.js', 'dist/js/babel/text.js': 'js/src/text.js',
'dist/js/babel/textarea.js': 'js/src/textarea.js',
'dist/js/babel/select.js': 'js/src/select.js',
'dist/js/babel/checkbox.js': 'js/src/checkbox.js', 'dist/js/babel/checkbox.js': 'js/src/checkbox.js',
'dist/js/babel/switch.js': 'js/src/switch.js', 'dist/js/babel/switch.js': 'js/src/switch.js',
'dist/js/babel/radio.js': 'js/src/radio.js', 'dist/js/babel/radio.js': 'js/src/radio.js',
'dist/js/babel/fileInput.js': 'js/src/fileInput.js', 'dist/js/babel/fileInput.js': 'js/src/file.js',
'dist/js/babel/bootstrapMaterialDesign.js': 'js/src/bootstrapMaterialDesign.js', 'dist/js/babel/bootstrapMaterialDesign.js': 'js/src/bootstrapMaterialDesign.js',
} }
}, },
@ -128,11 +130,13 @@ module.exports = function (grunt) {
'docs/dist/js/babel/util.js': 'js/src/util.js', 'docs/dist/js/babel/util.js': 'js/src/util.js',
'docs/dist/js/babel/ripples.js': 'js/src/ripples.js', 'docs/dist/js/babel/ripples.js': 'js/src/ripples.js',
'docs/dist/js/babel/autofill.js': 'js/src/autofill.js', 'docs/dist/js/babel/autofill.js': 'js/src/autofill.js',
'docs/dist/js/babel/textInput.js': 'js/src/textInput.js', 'docs/dist/js/babel/text.js': 'js/src/text.js',
'docs/dist/js/babel/textarea.js': 'js/src/textarea.js',
'docs/dist/js/babel/select.js': 'js/src/select.js',
'docs/dist/js/babel/checkbox.js': 'js/src/checkbox.js', 'docs/dist/js/babel/checkbox.js': 'js/src/checkbox.js',
'docs/dist/js/babel/switch.js': 'js/src/switch.js', 'docs/dist/js/babel/switch.js': 'js/src/switch.js',
'docs/dist/js/babel/radio.js': 'js/src/radio.js', 'docs/dist/js/babel/radio.js': 'js/src/radio.js',
'docs/dist/js/babel/fileInput.js': 'js/src/fileInput.js', 'docs/dist/js/babel/fileInput.js': 'js/src/file.js',
'docs/dist/js/babel/bootstrapMaterialDesign.js': 'js/src/bootstrapMaterialDesign.js', 'docs/dist/js/babel/bootstrapMaterialDesign.js': 'js/src/bootstrapMaterialDesign.js',
} }
}, },
@ -154,11 +158,13 @@ module.exports = function (grunt) {
'dist/js/umd/util.js': 'js/src/util.js', 'dist/js/umd/util.js': 'js/src/util.js',
'dist/js/umd/ripples.js': 'js/src/ripples.js', 'dist/js/umd/ripples.js': 'js/src/ripples.js',
'dist/js/umd/autofill.js': 'js/src/autofill.js', 'dist/js/umd/autofill.js': 'js/src/autofill.js',
'dist/js/umd/textInput.js': 'js/src/textInput.js', 'dist/js/umd/text.js': 'js/src/text.js',
'dist/js/umd/textarea.js': 'js/src/textarea.js',
'dist/js/umd/select.js': 'js/src/select.js',
'dist/js/umd/checkbox.js': 'js/src/checkbox.js', 'dist/js/umd/checkbox.js': 'js/src/checkbox.js',
'dist/js/umd/switch.js': 'js/src/switch.js', 'dist/js/umd/switch.js': 'js/src/switch.js',
'dist/js/umd/radio.js': 'js/src/radio.js', 'dist/js/umd/radio.js': 'js/src/radio.js',
'dist/js/umd/fileInput.js': 'js/src/fileInput.js', 'dist/js/umd/fileInput.js': 'js/src/file.js',
'dist/js/umd/bootstrapMaterialDesign.js': 'js/src/bootstrapMaterialDesign.js', 'dist/js/umd/bootstrapMaterialDesign.js': 'js/src/bootstrapMaterialDesign.js',
} }
} }
@ -216,11 +222,13 @@ module.exports = function (grunt) {
'dist/js/babel/util.js', 'dist/js/babel/util.js',
'dist/js/babel/ripples.js', 'dist/js/babel/ripples.js',
'dist/js/babel/autofill.js', 'dist/js/babel/autofill.js',
'dist/js/babel/textInput.js', 'dist/js/babel/text.js',
'dist/js/babel/textarea.js',
'dist/js/babel/select.js',
'dist/js/babel/checkbox.js', 'dist/js/babel/checkbox.js',
'dist/js/babel/switch.js', 'dist/js/babel/switch.js',
'dist/js/babel/radio.js', 'dist/js/babel/radio.js',
'dist/js/babel/fileInput.js', 'dist/js/babel/file.js',
'dist/js/babel/bootstrapMaterialDesign.js', 'dist/js/babel/bootstrapMaterialDesign.js',
], ],
dest: 'dist/js/<%= pkg.name %>.js' dest: 'dist/js/<%= pkg.name %>.js'

View File

@ -15,7 +15,9 @@
"../dist/js/babel/bootstrapMaterialDesign.js", "../dist/js/babel/bootstrapMaterialDesign.js",
"../dist/js/babel/checkbox.js", "../dist/js/babel/checkbox.js",
"../dist/js/babel/fileInput.js", "../dist/js/babel/fileInput.js",
"../dist/js/babel/textInput.js", "../dist/js/babel/text.js",
"../dist/js/babel/textarea.js",
"../dist/js/babel/select.js",
"../dist/js/babel/radio.js", "../dist/js/babel/radio.js",
"../dist/js/babel/ripples.js", "../dist/js/babel/ripples.js",
"../dist/js/babel/switch.js", "../dist/js/babel/switch.js",

View File

@ -1,5 +1,3 @@
//import Util from './util'
const Autofill = (($) => { const Autofill = (($) => {
/** /**

View File

@ -1,13 +1,16 @@
import Util from './util'
const BaseInput = (($) => { const BaseInput = (($) => {
const Default = { const Default = {
formGroup: { formGroup: {
template: `<div class='form-group'></div>`, template: `<div class='form-group'></div>`,
required: true, required: true,
autoCreate: false autoCreate: true
}, },
requiredClasses: ['form-control'], requiredClasses: [],
invalidComponentMatches: [] invalidComponentMatches: [],
convertInputSizeVariations: true
} }
const ClassName = { const ClassName = {
@ -20,6 +23,11 @@ const BaseInput = (($) => {
FORM_GROUP: `.${ClassName.FORM_GROUP}` //, FORM_GROUP: `.${ClassName.FORM_GROUP}` //,
} }
const FormControlSizeConversions = {
'form-control-lg': 'form-group-lg',
'form-control-sm': 'form-group-sm'
}
/** /**
* ------------------------------------------------------------------------ * ------------------------------------------------------------------------
* Class Definition * Class Definition
@ -27,9 +35,9 @@ const BaseInput = (($) => {
*/ */
class BaseInput { class BaseInput {
constructor(element, defaultConfig, config) { constructor(element, config) {
this.$element = $(element) this.$element = $(element)
this.config = $.extend({}, Default, defaultConfig, config) this.config = $.extend({}, Default, config)
// Enforce no overlap between components to prevent side effects // Enforce no overlap between components to prevent side effects
this._rejectInvalidComponentMatches() this._rejectInvalidComponentMatches()
@ -48,6 +56,8 @@ const BaseInput = (($) => {
// different components have different rules, always run this separately // different components have different rules, always run this separately
this.$formGroup = this.findFormGroup(this.config.formGroup.required) this.$formGroup = this.findFormGroup(this.config.formGroup.required)
this._convertFormControlSizeVariations()
this.addFocusListener() this.addFocusListener()
this.addChangeListener() this.addChangeListener()
} }
@ -67,11 +77,45 @@ const BaseInput = (($) => {
} }
addFocusListener() { addFocusListener() {
// implement this.$element
.on('focus', () => {
this.addFormGroupFocus()
})
.on('blur', () => {
this.removeFormGroupFocus()
})
} }
addChangeListener() { addChangeListener() {
// implement this.$element
.on('keydown paste', (event) => {
if (Util.isChar(event)) {
this.removeIsEmpty()
}
})
.on('keyup change', (event) => {
// make sure empty is added back when there is a programmatic value change.
// NOTE: programmatic changing of value using $.val() must trigger the change event i.e. $.val('x').trigger('change')
if (this.$element.val()) {
this.addIsEmpty()
} else {
this.removeIsEmpty()
}
// Validation events do not bubble, so they must be attached directly to the text: http://jsfiddle.net/PEpRM/1/
// Further, even the bind method is being caught, but since we are already calling #checkValidity here, just alter
// the form-group on change.
//
// NOTE: I'm not sure we should be intervening regarding validation, this seems better as a README and snippet of code.
// BUT, I've left it here for backwards compatibility.
let isValid = (typeof this.$element[0].checkValidity === 'undefined' || this.$element[0].checkValidity())
if (isValid) {
this.removeHasError()
} else {
this.addHasError()
}
})
} }
addFormGroupFocus() { addFormGroupFocus() {
@ -120,16 +164,7 @@ const BaseInput = (($) => {
findFormGroup(raiseError = true) { findFormGroup(raiseError = true) {
let fg = this.$element.closest(Selector.FORM_GROUP) // note that form-group may be grandparent in the case of an input-group let fg = this.$element.closest(Selector.FORM_GROUP) // note that form-group may be grandparent in the case of an input-group
if (fg.length === 0 && raiseError) { if (fg.length === 0 && raiseError) {
$.error(`Failed to find form-group for ${this.$element}`) $.error(`Failed to find form-group for ${Util.describe(this.$element)}`)
}
return fg
}
findOrCreateFormGroup() {
let fg = this.$element.closest(Selector.FORM_GROUP) // note that form-group may be grandparent in the case of an baseInput-group
if (fg === null || fg.length === 0) {
this.$element.wrap(this.config.formGroup.template)
fg = this.$element.closest(Selector.FORM_GROUP) // find node after attached (otherwise additional attachments don't work)
} }
return fg return fg
} }
@ -137,15 +172,45 @@ const BaseInput = (($) => {
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// private // private
_rejectInvalidComponentMatches() { _rejectInvalidComponentMatches() {
for (let otherComponent in this.config.invalidComponentMatches) { for (let otherComponent of this.config.invalidComponentMatches) {
otherComponent.rejectMatch(this.constructor.name, this.$element) otherComponent.rejectMatch(this.constructor.name, this.$element)
} }
} }
_rejectWithoutRequiredClasses() { _rejectWithoutRequiredClasses() {
for (let requiredClass in this.config.requiredClasses) { for (let requiredClass of this.config.requiredClasses) {
if (!this.$element.hasClass(requiredClass)) {
$.error(`${this.constructor.name} elements require class: ${requiredClass}`) let found = false
// allow one of several classes to be passed in x||y
if (requiredClass.indexOf('||') !== -1) {
let oneOf = requiredClass.split('||')
for (let requiredClass of oneOf) {
if (this.$element.hasClass(requiredClass)) {
found = true
break
}
}
} else if (this.$element.hasClass(requiredClass)) {
found = true
}
// error if not found
if (!found) {
$.error(`${this.constructor.name} element: ${Util.describe(this.$element)} requires class: ${requiredClass}`)
}
}
}
_convertFormControlSizeVariations() {
if (!this.config.convertInputSizeVariations) {
return
}
// Modification - Change text-sm/lg to form-group-sm/lg instead (preferred standard and simpler css/less variants)
for (let inputSize in FormControlSizeConversions) {
if (this.$element.hasClass(inputSize)) {
this.$element.removeClass(inputSize)
this.$formGroup.addClass(FormControlSizeConversions[inputSize])
} }
} }
} }

View File

@ -1,8 +1,4 @@
import BaseInput from './baseInput' import BaseInput from './baseInput'
//import TextInput from './textInput'
//import FileInput from './fileInput'
//import Radio from './radio'
//import Switch from './switch'
import Util from './util' import Util from './util'
const BaseToggle = (($) => { const BaseToggle = (($) => {
@ -13,9 +9,6 @@ const BaseToggle = (($) => {
* ------------------------------------------------------------------------ * ------------------------------------------------------------------------
*/ */
const Default = { const Default = {
formGroup: {
autoCreate: true
}
} }
const Selector = { const Selector = {
@ -30,7 +23,7 @@ const BaseToggle = (($) => {
class BaseToggle extends BaseInput { class BaseToggle extends BaseInput {
constructor(element, config, inputType, outerClass) { constructor(element, config, inputType, outerClass) {
super(element, Default, config) super(element, $.extend({}, Default, config))
this.$element.after(this.config.template) this.$element.after(this.config.template)
// '.checkbox|switch|radio > label > input[type=checkbox|radio]' // '.checkbox|switch|radio > label > input[type=checkbox|radio]'
// '.${this.outerClass} > label > input[type=${this.inputType}]' // '.${this.outerClass} > label > input[type=${this.inputType}]'

View File

@ -1,15 +1,8 @@
//import Util from './util'
/** /**
* $.bootstrapMaterialDesign(config) is a macro class to configure the components generally * $.bootstrapMaterialDesign(config) is a macro class to configure the components generally
* used in Material Design for Bootstrap. You may pass overrides to the configurations * used in Material Design for Bootstrap. You may pass overrides to the configurations
* which will be passed into each component, or you may omit use of this class and * which will be passed into each component, or you may omit use of this class and
* configure each component separately. * configure each component separately.
*
* NOTE: If omitting use of this class, please note that the Input component must be
* initialized prior to other decorating components such as radio, checkbox,
* switch, fileInput.
*
*/ */
const BootstrapMaterialDesign = (($) => { const BootstrapMaterialDesign = (($) => {
@ -42,12 +35,14 @@ const BootstrapMaterialDesign = (($) => {
'.ripple' // generic marker class to add ripple to elements '.ripple' // generic marker class to add ripple to elements
] ]
}, },
textInput: { text: {
selector: [ selector: ['input[type=text]']
'input[type=text]', },
'textarea', textarea: {
'select' selector: ['textarea']
] },
select: {
selector: ['select']
}, },
checkbox: { checkbox: {
selector: '.checkbox > label > input[type=checkbox]' selector: '.checkbox > label > input[type=checkbox]'
@ -58,7 +53,7 @@ const BootstrapMaterialDesign = (($) => {
radio: { radio: {
selector: '.radio > label > input[type=radio]' selector: '.radio > label > input[type=radio]'
}, },
fileInput: { file: {
selector: 'input[type=file]' selector: 'input[type=file]'
}, },
autofill: { autofill: {
@ -68,11 +63,13 @@ const BootstrapMaterialDesign = (($) => {
// create an ordered component list for instantiation // create an ordered component list for instantiation
instantiation: [ instantiation: [
'ripples', 'ripples',
'textInput',
'checkbox', 'checkbox',
'switch', 'file',
'radio', 'radio',
'fileInput', 'switch',
'text',
'textarea',
'select',
'autofill' 'autofill'
] ]
} }

View File

@ -1,7 +1,9 @@
import BaseToggle from './baseToggle' import BaseToggle from './baseToggle'
import TextInput from './textInput' import Text from './text'
import FileInput from './fileInput' import File from './file'
import Radio from './radio' import Radio from './radio'
import Textarea from './textare'
import Select from './select'
import Util from './util' import Util from './util'
const Checkbox = (($) => { const Checkbox = (($) => {
@ -16,8 +18,7 @@ const Checkbox = (($) => {
const JQUERY_NO_CONFLICT = $.fn[NAME] const JQUERY_NO_CONFLICT = $.fn[NAME]
const Default = { const Default = {
template: `<span class='checkbox-decorator'><span class='check'></span></span>`, template: `<span class='checkbox-decorator'><span class='check'></span></span>`
invalidComponentMatches: [FileInput, Radio, TextInput]
} }
/** /**
@ -28,7 +29,9 @@ const Checkbox = (($) => {
class Checkbox extends BaseToggle { class Checkbox extends BaseToggle {
constructor(element, config, inputType = NAME, outerClass = NAME) { constructor(element, config, inputType = NAME, outerClass = NAME) {
super(element, $.extend({}, Default, config), inputType, outerClass) super(element, $.extend({
invalidComponentMatches: [File, Radio, Text, Textarea, Select]
}, Default, config), inputType, outerClass)
} }
dispose() { dispose() {

View File

@ -2,30 +2,24 @@ import BaseInput from './baseInput'
import Checkbox from './checkbox' import Checkbox from './checkbox'
import Radio from './radio' import Radio from './radio'
import Switch from './switch' import Switch from './switch'
import TextInput from './textInput' import Text from './text'
import Textarea from './textare'
import Select from './select'
import Util from './util' import Util from './util'
// FileInput decorator, to be called after Input const File = (($) => {
const FileInput = (($) => {
/** /**
* ------------------------------------------------------------------------ * ------------------------------------------------------------------------
* Constants * Constants
* ------------------------------------------------------------------------ * ------------------------------------------------------------------------
*/ */
const NAME = 'fileInput' const NAME = 'file'
const DATA_KEY = `mdb.${NAME}` const DATA_KEY = `mdb.${NAME}`
const JQUERY_NO_CONFLICT = $.fn[NAME] const JQUERY_NO_CONFLICT = $.fn[NAME]
const Default = {
formGroup: {
autoCreate: true
},
invalidComponentMatches: [Checkbox, Radio, Switch, TextInput]
}
const ClassName = { const ClassName = {
IS_FILEINPUT: 'is-fileinput' IS_FILE: 'is-file'
} }
const Selector = { const Selector = {
@ -37,12 +31,12 @@ const FileInput = (($) => {
* Class Definition * Class Definition
* ------------------------------------------------------------------------ * ------------------------------------------------------------------------
*/ */
class FileInput extends BaseInput { class File extends BaseInput {
constructor(element, config) { constructor(element, config) {
super(element, Default, config) super(element, $.extend({invalidComponentMatches: [Checkbox, Radio, Text, Textarea, Select, Switch]}, config))
this.$formGroup.addClass(ClassName.IS_FILEINPUT) this.$formGroup.addClass(ClassName.IS_FILE)
} }
dispose() { dispose() {
@ -105,7 +99,7 @@ const FileInput = (($) => {
let data = $element.data(DATA_KEY) let data = $element.data(DATA_KEY)
if (!data) { if (!data) {
data = new FileInput(this, config) data = new File(this, config)
$element.data(DATA_KEY, data) $element.data(DATA_KEY, data)
} }
}) })
@ -117,15 +111,15 @@ const FileInput = (($) => {
* jQuery * jQuery
* ------------------------------------------------------------------------ * ------------------------------------------------------------------------
*/ */
$.fn[NAME] = FileInput._jQueryInterface $.fn[NAME] = File._jQueryInterface
$.fn[NAME].Constructor = FileInput $.fn[NAME].Constructor = File
$.fn[NAME].noConflict = () => { $.fn[NAME].noConflict = () => {
$.fn[NAME] = JQUERY_NO_CONFLICT $.fn[NAME] = JQUERY_NO_CONFLICT
return FileInput._jQueryInterface return File._jQueryInterface
} }
return FileInput return File
})(jQuery) })(jQuery)
export default FileInput export default File

View File

@ -1,11 +1,10 @@
import BaseToggle from './baseToggle' import BaseToggle from './baseToggle'
import TextInput from './textInput' import Text from './text'
import FileInput from './fileInput' import File from './file'
import Checkbox from './checkbox' import Checkbox from './checkbox'
import Switch from './switch' import Switch from './switch'
import Util from './util' import Util from './util'
// Radio decorator, to be called after Input
const Radio = (($) => { const Radio = (($) => {
/** /**
@ -18,8 +17,7 @@ const Radio = (($) => {
const JQUERY_NO_CONFLICT = $.fn[NAME] const JQUERY_NO_CONFLICT = $.fn[NAME]
const Default = { const Default = {
template: `<span class='radio-decorator'></span><span class='check'></span>`, template: `<span class='radio-decorator'></span><span class='check'></span>`
invalidComponentMatches: [Checkbox, FileInput, Switch, TextInput]
} }
/** /**
@ -30,7 +28,9 @@ const Radio = (($) => {
class Radio extends BaseToggle { class Radio extends BaseToggle {
constructor(element, config) { constructor(element, config) {
super(element, $.extend({}, Default, config), NAME, NAME) super(element, $.extend({
invalidComponentMatches: [Checkbox, File, Switch, Text]
}, Default, config), NAME, NAME)
} }
dispose() { dispose() {

87
js/src/select.js Normal file
View File

@ -0,0 +1,87 @@
import Checkbox from './checkbox'
import File from './file'
import Radio from './radio'
import Switch from './switch'
import Text from './text'
import Textarea from './textare'
import Util from './util'
const Select = (($) => {
/**
* ------------------------------------------------------------------------
* Constants
* ------------------------------------------------------------------------
*/
const NAME = 'select'
const DATA_KEY = `mdb.${NAME}`
const JQUERY_NO_CONFLICT = $.fn[NAME]
const Default = {
requiredClasses: ['form-control||c-select']
}
/**
* ------------------------------------------------------------------------
* Class Definition
* ------------------------------------------------------------------------
*/
class Select extends Text {
constructor(element, config) {
super(element, $.extend({invalidComponentMatches: [Checkbox, File, Radio, Switch, Text, Textarea]}, Default, config))
}
dispose() {
super.dispose(DATA_KEY)
}
static matches($element) {
if ($element.prop('tagName') === 'select') {
return true
}
return false
}
static rejectMatch(component, $element) {
Util.assert(this.matches($element), `${component} component is invalid for <select>.`)
}
// ------------------------------------------------------------------------
// protected
// ------------------------------------------------------------------------
// private
// ------------------------------------------------------------------------
// static
static _jQueryInterface(config) {
return this.each(function () {
let $element = $(this)
let data = $element.data(DATA_KEY)
if (!data) {
data = new Select(this, config)
$element.data(DATA_KEY, data)
}
})
}
}
/**
* ------------------------------------------------------------------------
* jQuery
* ------------------------------------------------------------------------
*/
$.fn[NAME] = Select._jQueryInterface
$.fn[NAME].Constructor = Select
$.fn[NAME].noConflict = () => {
$.fn[NAME] = JQUERY_NO_CONFLICT
return Select._jQueryInterface
}
return Select
})(jQuery)
export default Select

View File

@ -1,6 +1,5 @@
import Checkbox from './checkbox' import Checkbox from './checkbox'
// Switch decorator, to be called after Input
const Switch = (($) => { const Switch = (($) => {
/** /**

97
js/src/text.js Normal file
View File

@ -0,0 +1,97 @@
import BaseInput from './baseInput'
import Checkbox from './checkbox'
import File from './file'
import Radio from './radio'
import Switch from './switch'
import Textarea from './textare'
import Select from './select'
import Util from './util'
const Text = (($) => {
/**
* ------------------------------------------------------------------------
* Constants
* ------------------------------------------------------------------------
*/
const NAME = 'text'
const DATA_KEY = `mdb.${NAME}`
const JQUERY_NO_CONFLICT = $.fn[NAME]
const Default = {
template: `<span class='text-input-decorator'></span>`,
requiredClasses: ['form-control']
}
/**
* ------------------------------------------------------------------------
* Class Definition
* ------------------------------------------------------------------------
*/
class Text extends BaseInput {
constructor(element, config) {
super(element, $.extend({invalidComponentMatches: [Checkbox, File, Radio, Select, Switch, Textarea]}, Default, config))
// Initially mark as empty
if (this.isEmpty()) {
this.addIsEmpty()
}
// Add marker div the end of the form-group
this.$formGroup.append(this.config.template)
}
dispose(dataKey = DATA_KEY) {
super.dispose(dataKey)
}
static matches($element) {
if ($element.attr('type') === 'text') {
return true
}
return false
}
static rejectMatch(component, $element) {
Util.assert(this.matches($element), `${component} component is invalid for type='text'.`)
}
// ------------------------------------------------------------------------
// protected
// ------------------------------------------------------------------------
// private
// ------------------------------------------------------------------------
// static
static _jQueryInterface(config) {
return this.each(function () {
let $element = $(this)
let data = $element.data(DATA_KEY)
if (!data) {
data = new Text(this, config)
$element.data(DATA_KEY, data)
}
})
}
}
/**
* ------------------------------------------------------------------------
* jQuery
* ------------------------------------------------------------------------
*/
$.fn[NAME] = Text._jQueryInterface
$.fn[NAME].Constructor = Text
$.fn[NAME].noConflict = () => {
$.fn[NAME] = JQUERY_NO_CONFLICT
return Text._jQueryInterface
}
return Text
})(jQuery)
export default Text

View File

@ -1,167 +0,0 @@
import BaseInput from './baseInput'
import Checkbox from './checkbox'
import FileInput from './fileInput'
import Radio from './radio'
import Switch from './switch'
import Util from './util'
const TextInput = (($) => {
/**
* ------------------------------------------------------------------------
* Constants
* ------------------------------------------------------------------------
*/
const NAME = 'textInput'
const DATA_KEY = `mdb.${NAME}`
const JQUERY_NO_CONFLICT = $.fn[NAME]
const Default = {
convertInputSizeVariations: true,
template: `<span class='material-input'></span>`,
formGroup: {
autoCreate: true
},
requiredClasses: ['form-control'],
invalidComponentMatches: [Checkbox, FileInput, Radio, Switch]
}
const InputSizeConversions = {
'textInput-lg': 'form-group-lg',
'textInput-sm': 'form-group-sm'
}
/**
* ------------------------------------------------------------------------
* Class Definition
* ------------------------------------------------------------------------
*/
class TextInput extends BaseInput {
constructor(element, config) {
super(element, Default, config)
this._convertInputSizeVariations()
// Initially mark as empty
if (this.isEmpty()) {
this.addIsEmpty()
}
// Add marker div the end of the form-group
this.$formGroup.append(this.config.template)
}
dispose() {
super.dispose(DATA_KEY)
}
static matches($element) {
if (
($element.attr('type') === 'text')
|| ($element.prop('tagName') === 'textarea')
|| ($element.prop('tagName') === 'select')
) {
return true
}
return false
}
static rejectMatch(component, $element) {
Util.assert(this.matches($element), `${component} component is invalid for type='text'.`)
}
// ------------------------------------------------------------------------
// protected
addFocusListener() {
this.$element
.on('focus', () => {
this.addFormGroupFocus()
})
.on('blur', () => {
this.removeFormGroupFocus()
})
}
addChangeListener() {
this.$element
.on('keydown paste', (event) => {
if (Util.isChar(event)) {
this.removeIsEmpty()
}
})
.on('keyup change', (event) => {
// make sure empty is added back when there is a programmatic value change.
// NOTE: programmatic changing of value using $.val() must trigger the change event i.e. $.val('x').trigger('change')
if (this.$element.val()) {
this.addIsEmpty()
} else {
this.removeIsEmpty()
}
// Validation events do not bubble, so they must be attached directly to the textInput: http://jsfiddle.net/PEpRM/1/
// Further, even the bind method is being caught, but since we are already calling #checkValidity here, just alter
// the form-group on change.
//
// NOTE: I'm not sure we should be intervening regarding validation, this seems better as a README and snippet of code.
// BUT, I've left it here for backwards compatibility.
let isValid = (typeof this.$element[0].checkValidity === 'undefined' || this.$element[0].checkValidity())
if (isValid) {
this.removeHasError()
} else {
this.addHasError()
}
})
}
// ------------------------------------------------------------------------
// private
_convertInputSizeVariations() {
if (!this.config.convertInputSizeVariations) {
return
}
// Modification - Change textInput-sm/lg to form-group-sm/lg instead (preferred standard and simpler css/less variants)
for (let inputSize in InputSizeConversions) {
if (this.$element.hasClass(inputSize)) {
this.$element.removeClass(inputSize)
this.$formGroup.addClass(InputSizeConversions[inputSize])
}
}
}
// ------------------------------------------------------------------------
// static
static _jQueryInterface(config) {
return this.each(function () {
let $element = $(this)
let data = $element.data(DATA_KEY)
if (!data) {
data = new TextInput(this, config)
$element.data(DATA_KEY, data)
}
})
}
}
/**
* ------------------------------------------------------------------------
* jQuery
* ------------------------------------------------------------------------
*/
$.fn[NAME] = TextInput._jQueryInterface
$.fn[NAME].Constructor = TextInput
$.fn[NAME].noConflict = () => {
$.fn[NAME] = JQUERY_NO_CONFLICT
return TextInput._jQueryInterface
}
return TextInput
})(jQuery)
export default TextInput

85
js/src/textarea.js Normal file
View File

@ -0,0 +1,85 @@
import Checkbox from './checkbox'
import File from './file'
import Radio from './radio'
import Switch from './switch'
import Text from './text'
import Select from './select'
import Util from './util'
const Textarea = (($) => {
/**
* ------------------------------------------------------------------------
* Constants
* ------------------------------------------------------------------------
*/
const NAME = 'textarea'
const DATA_KEY = `mdb.${NAME}`
const JQUERY_NO_CONFLICT = $.fn[NAME]
const Default = {}
/**
* ------------------------------------------------------------------------
* Class Definition
* ------------------------------------------------------------------------
*/
class Textarea extends Text {
constructor(element, config) {
super(element, $.extend({invalidComponentMatches: [Checkbox, File, Radio, Text, Select, Switch]}, Default, config))
}
dispose() {
super.dispose(DATA_KEY)
}
static matches($element) {
if ($element.prop('tagName') === 'textarea') {
return true
}
return false
}
static rejectMatch(component, $element) {
Util.assert(this.matches($element), `${component} component is invalid for <textarea>.`)
}
// ------------------------------------------------------------------------
// protected
// ------------------------------------------------------------------------
// private
// ------------------------------------------------------------------------
// static
static _jQueryInterface(config) {
return this.each(function () {
let $element = $(this)
let data = $element.data(DATA_KEY)
if (!data) {
data = new Textarea(this, config)
$element.data(DATA_KEY, data)
}
})
}
}
/**
* ------------------------------------------------------------------------
* jQuery
* ------------------------------------------------------------------------
*/
$.fn[NAME] = Textarea._jQueryInterface
$.fn[NAME].Constructor = Textarea
$.fn[NAME].noConflict = () => {
$.fn[NAME] = JQUERY_NO_CONFLICT
return Textarea._jQueryInterface
}
return Textarea
})(jQuery)
export default Textarea

View File

@ -70,6 +70,10 @@ const Util = (($) => {
if (test) { if (test) {
$.error(message) $.error(message)
} }
},
describe($element) {
return `${$element[0].outerHTML.split('>')[0]}>`
} }
} }

View File

@ -220,7 +220,7 @@
box-shadow: none; box-shadow: none;
transition-duration: 0.3s; transition-duration: 0.3s;
.material-input:after { .text-input-decorator:after {
background-color: $brand-primary; background-color: $brand-primary;
} }
} }
@ -259,7 +259,7 @@
select { select {
appearance: none; // Fix for OS X appearance: none; // Fix for OS X
& ~ .material-input:after { & ~ .text-input-decorator:after {
display: none; display: none;
} }
} }

View File

@ -159,8 +159,8 @@
// margin: 0; // margin: 0;
// padding: 0; // padding: 0;
// //
// .material-input:before, // .text-input-decorator:before,
// &.is-focused .material-input:after { // &.is-focused .text-input-decorator:after {
// background-color: inherit; // background-color: inherit;
// } // }
// } // }