mirror of
https://github.com/mdbootstrap/mdb-ui-kit.git
synced 2024-11-27 12:04:09 +03:00
343 lines
11 KiB
JavaScript
343 lines
11 KiB
JavaScript
import Util from './util'
|
|
|
|
const BaseInput = (($) => {
|
|
|
|
const ClassName = {
|
|
FORM_GROUP: 'form-group',
|
|
MDB_FORM_GROUP: 'mdb-form-group',
|
|
MDB_LABEL: 'mdb-label',
|
|
MDB_LABEL_STATIC: 'mdb-label-static',
|
|
MDB_LABEL_PLACEHOLDER: 'mdb-label-placeholder',
|
|
MDB_LABEL_FLOATING: 'mdb-label-floating',
|
|
HAS_DANGER: 'has-danger',
|
|
IS_FILLED: 'is-filled',
|
|
IS_FOCUSED: 'is-focused'
|
|
}
|
|
|
|
const Selector = {
|
|
FORM_GROUP: `.${ClassName.FORM_GROUP}`,
|
|
MDB_FORM_GROUP: `.${ClassName.MDB_FORM_GROUP}`,
|
|
MDB_LABEL_WILDCARD: `label[class^='${ClassName.MDB_LABEL}'], label[class*=' ${ClassName.MDB_LABEL}']` // match any label variant if specified
|
|
}
|
|
|
|
const Default = {
|
|
validate: false,
|
|
formGroup: {
|
|
required: false
|
|
},
|
|
mdbFormGroup: {
|
|
template: `<span class='mdb-form-group'></span>`,
|
|
create: true, // create a wrapper if form-group not found
|
|
required: true // not recommended to turn this off, only used for inline components
|
|
},
|
|
mdbLabel: {
|
|
required: false,
|
|
|
|
// Prioritized find order for resolving the label to be used as an mdb-label if not specified in the markup
|
|
// - a function(thisComponent); or
|
|
// - a string selector used like $mdbFormGroup.find(selector)
|
|
//
|
|
// Note this only runs if $mdbFormGroup.find(Selector.MDB_LABEL_WILDCARD) fails to find a label (as authored in the markup)
|
|
//
|
|
selectors: [
|
|
`.form-control-label`, // in the case of horizontal or inline forms, this will be marked
|
|
`> label` // usual case for text inputs, first child. Deeper would find toggle labels so don't do that.
|
|
],
|
|
className: ClassName.MDB_LABEL_STATIC
|
|
},
|
|
requiredClasses: [],
|
|
invalidComponentMatches: [],
|
|
convertInputSizeVariations: true
|
|
}
|
|
|
|
const FormControlSizeMarkers = {
|
|
'form-control-lg': 'mdb-form-group-lg',
|
|
'form-control-sm': 'mdb-form-group-sm'
|
|
}
|
|
|
|
/**
|
|
* ------------------------------------------------------------------------
|
|
* Class Definition
|
|
* ------------------------------------------------------------------------
|
|
*/
|
|
class BaseInput {
|
|
|
|
/**
|
|
*
|
|
* @param element
|
|
* @param config
|
|
* @param properties - anything that needs to be set as this[key] = value. Works around the need to call `super` before using `this`
|
|
*/
|
|
constructor($element, config, properties = {}) {
|
|
this.$element = $element
|
|
this.config = $.extend(true, {}, Default, config)
|
|
|
|
// set properties for use in the constructor initialization
|
|
for (let key in properties) {
|
|
this[key] = properties[key]
|
|
}
|
|
|
|
// Enforce no overlap between components to prevent side effects
|
|
this._rejectInvalidComponentMatches()
|
|
|
|
// Enforce expected structure (if any)
|
|
this.rejectWithoutRequiredStructure()
|
|
|
|
// Enforce required classes for a consistent rendering
|
|
this._rejectWithoutRequiredClasses()
|
|
|
|
// Resolve the form-group first, it will be used for mdb-form-group if possible
|
|
// note: different components have different rules
|
|
this.$formGroup = this.findFormGroup(this.config.formGroup.required)
|
|
|
|
// Will add mdb-form-group to form-group or create an mdb-form-group
|
|
// Performance Note: for those forms that are really performance driven, create the markup with the .mdb-form-group to avoid
|
|
// rendering changes once added.
|
|
this.$mdbFormGroup = this.resolveMdbFormGroup()
|
|
|
|
// Resolve and mark the mdbLabel if necessary as defined by the config
|
|
this.$mdbLabel = this.resolveMdbLabel()
|
|
|
|
// Signal to the mdb-form-group that a form-control-* variation is being used
|
|
this.resolveMdbFormGroupSizing()
|
|
|
|
this.addFocusListener()
|
|
this.addChangeListener()
|
|
}
|
|
|
|
dispose(dataKey) {
|
|
$.removeData(this.$element, dataKey)
|
|
this.$element = null
|
|
this.$mdbFormGroup = null
|
|
this.config = null
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
// protected
|
|
|
|
rejectWithoutRequiredStructure() {
|
|
// implement
|
|
}
|
|
|
|
addFocusListener() {
|
|
this.$element
|
|
.on('focus', () => {
|
|
this.addFormGroupFocus()
|
|
})
|
|
.on('blur', () => {
|
|
this.removeFormGroupFocus()
|
|
})
|
|
}
|
|
|
|
addChangeListener() {
|
|
this.$element
|
|
.on('keydown paste', (event) => {
|
|
if (Util.isChar(event)) {
|
|
this.addIsFilled()
|
|
}
|
|
})
|
|
.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.isEmpty()) {
|
|
this.removeIsFilled()
|
|
} else {
|
|
this.addIsFilled()
|
|
}
|
|
|
|
if (this.config.validate) {
|
|
// 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.removeHasDanger()
|
|
} else {
|
|
this.addHasDanger()
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
addFormGroupFocus() {
|
|
if (!this.$element.prop('disabled')) {
|
|
this.$mdbFormGroup.addClass(ClassName.IS_FOCUSED)
|
|
}
|
|
}
|
|
|
|
removeFormGroupFocus() {
|
|
this.$mdbFormGroup.removeClass(ClassName.IS_FOCUSED)
|
|
}
|
|
|
|
addHasDanger() {
|
|
this.$mdbFormGroup.addClass(ClassName.HAS_DANGER)
|
|
}
|
|
|
|
removeHasDanger() {
|
|
this.$mdbFormGroup.removeClass(ClassName.HAS_DANGER)
|
|
}
|
|
|
|
removeIsFilled() {
|
|
this.$mdbFormGroup.removeClass(ClassName.IS_FILLED)
|
|
}
|
|
|
|
addIsFilled() {
|
|
this.$mdbFormGroup.addClass(ClassName.IS_FILLED)
|
|
}
|
|
|
|
isEmpty() {
|
|
return (this.$element.val() === null || this.$element.val() === undefined || this.$element.val() === '')
|
|
}
|
|
|
|
// Will add mdb-form-group to form-group or create a mdb-form-group if necessary
|
|
resolveMdbFormGroup() {
|
|
let mfg = this.findMdbFormGroup(false)
|
|
if (mfg === undefined || mfg.length === 0) {
|
|
if (this.config.mdbFormGroup.create && (this.$formGroup === undefined || this.$formGroup.length === 0)) {
|
|
// If a form-group doesn't exist (not recommended), take a guess and wrap the element (assuming no label).
|
|
// note: it's possible to make this smarter, but I need to see valid cases before adding any complexity.
|
|
this.outerElement().wrap(this.config.mdbFormGroup.template)
|
|
} else {
|
|
// a form-group does exist, add our marker class to it
|
|
this.$formGroup.addClass(ClassName.MDB_FORM_GROUP)
|
|
|
|
// OLD: may want to implement this after all, see how the styling turns out, but using an existing form-group is less manipulation of the dom and therefore preferable
|
|
// A form-group does exist, so add an mdb-form-group wrapping it's internal contents
|
|
//fg.wrapInner(this.config.mdbFormGroup.template)
|
|
}
|
|
|
|
mfg = this.findMdbFormGroup(this.config.mdbFormGroup.required)
|
|
}
|
|
|
|
return mfg
|
|
}
|
|
|
|
// Demarcation element (e.g. first child of a form-group)
|
|
// Subclasses such as file inputs may have different structures
|
|
outerElement() {
|
|
return this.$element
|
|
}
|
|
|
|
// Will add mdb-label to mdb-form-group if not already specified
|
|
resolveMdbLabel() {
|
|
|
|
let label = this.$mdbFormGroup.find(Selector.MDB_LABEL_WILDCARD)
|
|
if (label === undefined || label.length === 0) {
|
|
// we need to find it based on the configured selectors
|
|
label = this.findMdbLabel(this.config.mdbLabel.required)
|
|
|
|
if (label === undefined || label.length === 0) {
|
|
// no label found, and finder did not require one
|
|
} else {
|
|
// a candidate label was found, add the configured default class name
|
|
label.addClass(this.config.mdbLabel.className)
|
|
}
|
|
}
|
|
|
|
return label
|
|
}
|
|
|
|
// Find mdb-label variant based on the config selectors
|
|
findMdbLabel(raiseError = true) {
|
|
let label = null
|
|
|
|
// use the specified selector order
|
|
for (let selector of this.config.mdbLabel.selectors) {
|
|
if ($.isFunction(selector)) {
|
|
label = selector(this)
|
|
} else {
|
|
label = this.$mdbFormGroup.find(selector)
|
|
}
|
|
|
|
if (label !== undefined && label.length > 0) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (label.length === 0 && raiseError) {
|
|
$.error(`Failed to find ${Selector.MDB_LABEL_WILDCARD} within form-group for ${Util.describe(this.$element)}`)
|
|
}
|
|
return label
|
|
}
|
|
|
|
// Find mdb-form-group
|
|
findMdbFormGroup(raiseError = true) {
|
|
let mfg = this.$element.closest(Selector.MDB_FORM_GROUP)
|
|
if (mfg.length === 0 && raiseError) {
|
|
$.error(`Failed to find ${Selector.MDB_FORM_GROUP} for ${Util.describe(this.$element)}`)
|
|
}
|
|
return mfg
|
|
}
|
|
|
|
// Find mdb-form-group
|
|
findFormGroup(raiseError = true) {
|
|
let fg = this.$element.closest(Selector.FORM_GROUP)
|
|
if (fg.length === 0 && raiseError) {
|
|
$.error(`Failed to find ${Selector.FORM_GROUP} for ${Util.describe(this.$element)}`)
|
|
}
|
|
return fg
|
|
}
|
|
|
|
// Due to the interconnected nature of labels/inputs/help-blocks, signal the mdb-form-group-* size variation based on
|
|
// a found form-control-* size
|
|
resolveMdbFormGroupSizing() {
|
|
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 FormControlSizeMarkers) {
|
|
if (this.$element.hasClass(inputSize)) {
|
|
//this.$element.removeClass(inputSize)
|
|
this.$mdbFormGroup.addClass(FormControlSizeMarkers[inputSize])
|
|
}
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
// private
|
|
_rejectInvalidComponentMatches() {
|
|
for (let otherComponent of this.config.invalidComponentMatches) {
|
|
otherComponent.rejectMatch(this.constructor.name, this.$element)
|
|
}
|
|
}
|
|
|
|
_rejectWithoutRequiredClasses() {
|
|
for (let requiredClass of this.config.requiredClasses) {
|
|
|
|
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}`)
|
|
}
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
// static
|
|
|
|
}
|
|
|
|
return BaseInput
|
|
|
|
})(jQuery)
|
|
|
|
export default BaseInput
|