2016-01-26 21:12:48 +03:00
import Base from './base'
import Util from './util'
const BaseInput = ( ( $ ) => {
const ClassName = {
FORM _GROUP : 'form-group' ,
2016-03-28 19:13:18 +03:00
BMD _FORM _GROUP : 'bmd-form-group' ,
BMD _LABEL : 'bmd-label' ,
BMD _LABEL _STATIC : 'bmd-label-static' ,
BMD _LABEL _PLACEHOLDER : 'bmd-label-placeholder' ,
BMD _LABEL _FLOATING : 'bmd-label-floating' ,
2016-01-26 21:12:48 +03:00
HAS _DANGER : 'has-danger' ,
IS _FILLED : 'is-filled' ,
IS _FOCUSED : 'is-focused'
}
const Selector = {
FORM _GROUP : ` . ${ ClassName . FORM _GROUP } ` ,
2016-03-28 19:13:18 +03:00
BMD _FORM _GROUP : ` . ${ ClassName . BMD _FORM _GROUP } ` ,
BMD _LABEL _WILDCARD : ` label[class^=' ${ ClassName . BMD _LABEL } '], label[class*=' ${ ClassName . BMD _LABEL } '] ` // match any label variant if specified
2016-01-26 21:12:48 +03:00
}
const Default = {
validate : false ,
formGroup : {
required : false
} ,
mdbFormGroup : {
2016-03-28 19:13:18 +03:00
template : ` <span class=' ${ ClassName . BMD _FORM _GROUP } '></span> ` ,
2016-01-26 21:12:48 +03:00
create : true , // create a wrapper if form-group not found
required : true // not recommended to turn this off, only used for inline components
} ,
label : {
required : false ,
2016-03-21 17:56:51 +03:00
// Prioritized find order for resolving the label to be used as an bmd-label if not specified in the markup
2016-01-26 21:12:48 +03:00
// - a function(thisComponent); or
// - a string selector used like $mdbFormGroup.find(selector)
//
2016-03-28 19:13:18 +03:00
// Note this only runs if $mdbFormGroup.find(Selector.BMD_LABEL_WILDCARD) fails to find a label (as authored in the markup)
2016-01-26 21:12:48 +03:00
//
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.
] ,
2016-03-28 19:13:18 +03:00
className : ClassName . BMD _LABEL _STATIC
2016-01-26 21:12:48 +03:00
} ,
requiredClasses : [ ] ,
invalidComponentMatches : [ ] ,
convertInputSizeVariations : true
}
const FormControlSizeMarkers = {
2016-03-21 17:56:51 +03:00
'form-control-lg' : 'bmd-form-group-lg' ,
'form-control-sm' : 'bmd-form-group-sm'
2016-01-26 21:12:48 +03:00
}
/ * *
* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
* Class Definition
* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
* /
class BaseInput extends Base {
/ * *
*
* @ 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 = { } ) {
super ( $element , $ . extend ( true , { } , Default , config ) , properties )
// 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 ( )
2016-03-21 17:56:51 +03:00
// Resolve the form-group first, it will be used for bmd-form-group if possible
2016-01-26 21:12:48 +03:00
// note: different components have different rules
this . $formGroup = this . findFormGroup ( this . config . formGroup . required )
2016-03-21 17:56:51 +03:00
// Will add bmd-form-group to form-group or create an bmd-form-group
// Performance Note: for those forms that are really performance driven, create the markup with the .bmd-form-group to avoid
2016-01-26 21:12:48 +03:00
// rendering changes once added.
this . $mdbFormGroup = this . resolveMdbFormGroup ( )
// Resolve and mark the mdbLabel if necessary as defined by the config
this . $mdbLabel = this . resolveMdbLabel ( )
2016-03-21 17:56:51 +03:00
// Signal to the bmd-form-group that a form-control-* variation is being used
2016-01-26 21:12:48 +03:00
this . resolveMdbFormGroupSizing ( )
this . addFocusListener ( )
this . addChangeListener ( )
}
dispose ( dataKey ) {
super . dispose ( dataKey )
this . $mdbFormGroup = null
this . $formGroup = 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' , ( ) => {
// 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 ( )
}
}
} )
}
addHasDanger ( ) {
this . $mdbFormGroup . addClass ( ClassName . HAS _DANGER )
}
removeHasDanger ( ) {
this . $mdbFormGroup . removeClass ( ClassName . HAS _DANGER )
}
isEmpty ( ) {
return ( this . $element . val ( ) === null || this . $element . val ( ) === undefined || this . $element . val ( ) === '' )
}
2016-03-21 17:56:51 +03:00
// Will add bmd-form-group to form-group or create a bmd-form-group if necessary
2016-01-26 21:12:48 +03:00
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
2016-03-28 19:13:18 +03:00
this . $formGroup . addClass ( ClassName . BMD _FORM _GROUP )
2016-01-26 21:12:48 +03:00
// 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
2016-03-21 17:56:51 +03:00
// A form-group does exist, so add an bmd-form-group wrapping it's internal contents
2016-01-26 21:12:48 +03:00
//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
}
2016-03-21 17:56:51 +03:00
// Will add bmd-label to bmd-form-group if not already specified
2016-01-26 21:12:48 +03:00
resolveMdbLabel ( ) {
2016-03-28 19:13:18 +03:00
let label = this . $mdbFormGroup . find ( Selector . BMD _LABEL _WILDCARD )
2016-01-26 21:12:48 +03:00
if ( label === undefined || label . length === 0 ) {
// we need to find it based on the configured selectors
label = this . findMdbLabel ( this . config . label . 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 . label . className )
}
}
return label
}
2016-03-21 17:56:51 +03:00
// Find bmd-label variant based on the config selectors
2016-01-26 21:12:48 +03:00
findMdbLabel ( raiseError = true ) {
let label = null
// use the specified selector order
for ( let selector of this . config . label . 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 ) {
2016-03-28 19:13:18 +03:00
$ . error ( ` Failed to find ${ Selector . BMD _LABEL _WILDCARD } within form-group for ${ Util . describe ( this . $element ) } ` )
2016-01-26 21:12:48 +03:00
}
return label
}
2016-03-21 17:56:51 +03:00
// Find bmd-form-group
2016-01-26 21:12:48 +03:00
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
}
2016-03-21 17:56:51 +03:00
// Due to the interconnected nature of labels/inputs/help-blocks, signal the bmd-form-group-* size variation based on
2016-01-26 21:12:48 +03:00
// 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