mirror of
https://github.com/mdbootstrap/mdb-ui-kit.git
synced 2025-02-02 21:04:13 +03:00
es6 refactoring classes into discrete input classes without overlap, and enforcing classes and structure. TextInput and Checkbox done, more to go.
This commit is contained in:
parent
93f0402161
commit
3a547cd0f3
16
Gruntfile.js
16
Gruntfile.js
|
@ -103,9 +103,9 @@ 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/input.js': 'js/src/input.js',
|
'dist/js/babel/input.js': 'js/src/textInput.js',
|
||||||
'dist/js/babel/checkbox.js': 'js/src/checkbox.js',
|
'dist/js/babel/checkbox.js': 'js/src/checkbox.js',
|
||||||
'dist/js/babel/togglebutton.js': 'js/src/togglebutton.js',
|
'dist/js/babel/togglebutton.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/fileInput.js',
|
||||||
'dist/js/babel/bootstrapMaterialDesign.js': 'js/src/bootstrapMaterialDesign.js',
|
'dist/js/babel/bootstrapMaterialDesign.js': 'js/src/bootstrapMaterialDesign.js',
|
||||||
|
@ -124,9 +124,9 @@ 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/input.js': 'js/src/input.js',
|
'docs/dist/js/babel/input.js': 'js/src/textInput.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/togglebutton.js': 'js/src/togglebutton.js',
|
'docs/dist/js/babel/togglebutton.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/fileInput.js',
|
||||||
'docs/dist/js/babel/bootstrapMaterialDesign.js': 'js/src/bootstrapMaterialDesign.js',
|
'docs/dist/js/babel/bootstrapMaterialDesign.js': 'js/src/bootstrapMaterialDesign.js',
|
||||||
|
@ -148,9 +148,9 @@ 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/input.js': 'js/src/input.js',
|
'dist/js/umd/input.js': 'js/src/textInput.js',
|
||||||
'dist/js/umd/checkbox.js': 'js/src/checkbox.js',
|
'dist/js/umd/checkbox.js': 'js/src/checkbox.js',
|
||||||
'dist/js/umd/togglebutton.js': 'js/src/togglebutton.js',
|
'dist/js/umd/togglebutton.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/fileInput.js',
|
||||||
'dist/js/umd/bootstrapMaterialDesign.js': 'js/src/bootstrapMaterialDesign.js',
|
'dist/js/umd/bootstrapMaterialDesign.js': 'js/src/bootstrapMaterialDesign.js',
|
||||||
|
@ -208,9 +208,9 @@ 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/input.js',
|
'dist/js/babel/textInput.js',
|
||||||
'dist/js/babel/checkbox.js',
|
'dist/js/babel/checkbox.js',
|
||||||
'dist/js/babel/togglebutton.js',
|
'dist/js/babel/switch.js',
|
||||||
'dist/js/babel/radio.js',
|
'dist/js/babel/radio.js',
|
||||||
'dist/js/babel/fileInput.js',
|
'dist/js/babel/fileInput.js',
|
||||||
'dist/js/babel/bootstrapMaterialDesign.js',
|
'dist/js/babel/bootstrapMaterialDesign.js',
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
],
|
],
|
||||||
|
|
||||||
"coreJs": [
|
"coreJs": [
|
||||||
|
"../dist/js/babel/baseInput.js",
|
||||||
"../dist/js/babel/autofill.js",
|
"../dist/js/babel/autofill.js",
|
||||||
"../dist/js/babel/bootstrapMaterialDesign.js",
|
"../dist/js/babel/bootstrapMaterialDesign.js",
|
||||||
"../dist/js/babel/checkbox.js",
|
"../dist/js/babel/checkbox.js",
|
||||||
|
|
164
js/src/baseInput.js
Normal file
164
js/src/baseInput.js
Normal file
|
@ -0,0 +1,164 @@
|
||||||
|
import Util from './util'
|
||||||
|
|
||||||
|
const BaseInput = (($) => {
|
||||||
|
|
||||||
|
const Default = {
|
||||||
|
formGroup: {
|
||||||
|
template: `<div class='form-group'></div>`,
|
||||||
|
required: true,
|
||||||
|
autoCreate: false
|
||||||
|
},
|
||||||
|
requiredClasses: ['form-control'],
|
||||||
|
invalidComponentMatches: []
|
||||||
|
}
|
||||||
|
|
||||||
|
const ClassName = {
|
||||||
|
FORM_GROUP: 'form-group',
|
||||||
|
HAS_ERROR: 'has-error',
|
||||||
|
IS_EMPTY: 'is-empty'
|
||||||
|
}
|
||||||
|
|
||||||
|
const Selector = {
|
||||||
|
FORM_GROUP: `.${ClassName.FORM_GROUP}` //,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ------------------------------------------------------------------------
|
||||||
|
* Class Definition
|
||||||
|
* ------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
class BaseInput {
|
||||||
|
|
||||||
|
constructor(element, defaultConfig, config) {
|
||||||
|
this.$element = $(element)
|
||||||
|
this.config = $.extend({}, Default, defaultConfig, config)
|
||||||
|
|
||||||
|
// Enforce no overlap between components to prevent side effects
|
||||||
|
this._rejectInvalidComponentMatches()
|
||||||
|
|
||||||
|
// Enforce required classes for a consistent rendering
|
||||||
|
this._rejectWithoutRequiredClasses()
|
||||||
|
|
||||||
|
// Enforce expected structure (if any)
|
||||||
|
this.rejectWithoutRequiredStructure()
|
||||||
|
|
||||||
|
if(this.config.formGroup.autoCreate) {
|
||||||
|
// Will create form-group if necessary
|
||||||
|
this.autoCreateFormGroup()
|
||||||
|
}
|
||||||
|
|
||||||
|
// different components have different rules, always run this separately
|
||||||
|
this.$formGroup = this.findFormGroup(this.config.formGroup.required)
|
||||||
|
|
||||||
|
this.addFocusListener()
|
||||||
|
this.addChangeListener()
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose(dataKey) {
|
||||||
|
$.removeData(this.$element, dataKey)
|
||||||
|
this.$element = null
|
||||||
|
this.$formGroup = null
|
||||||
|
this.config = null
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// protected
|
||||||
|
|
||||||
|
rejectWithoutRequiredStructure(){
|
||||||
|
// implement
|
||||||
|
}
|
||||||
|
|
||||||
|
addFocusListener() {
|
||||||
|
// implement
|
||||||
|
}
|
||||||
|
|
||||||
|
addChangeListener() {
|
||||||
|
// implement
|
||||||
|
}
|
||||||
|
|
||||||
|
addFormGroupFocus(formGroup) {
|
||||||
|
formGroup.addClass(ClassName.IS_FOCUSED)
|
||||||
|
}
|
||||||
|
|
||||||
|
removeFormGroupFocus(formGroup) {
|
||||||
|
formGroup.removeClass(ClassName.IS_FOCUSED)
|
||||||
|
}
|
||||||
|
|
||||||
|
addHasError() {
|
||||||
|
this.$formGroup.addClass(ClassName.HAS_ERROR)
|
||||||
|
}
|
||||||
|
|
||||||
|
removeHasError() {
|
||||||
|
this.$formGroup.removeClass(ClassName.HAS_ERROR)
|
||||||
|
}
|
||||||
|
|
||||||
|
addIsEmpty() {
|
||||||
|
this.$formGroup.addClass(ClassName.IS_EMPTY)
|
||||||
|
}
|
||||||
|
|
||||||
|
removeIsEmpty() {
|
||||||
|
this.$formGroup.removeClass(ClassName.IS_EMPTY)
|
||||||
|
}
|
||||||
|
|
||||||
|
isEmpty() {
|
||||||
|
return (this.$element.val() === null || this.$element.val() === undefined || this.$element.val() === '')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find or create a form-group if necessary
|
||||||
|
autoCreateFormGroup() {
|
||||||
|
let fg = this.findFormGroup(false)
|
||||||
|
if (fg === null || fg.length === 0) {
|
||||||
|
this.outerElement().wrap(this.config.formGroup.template)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Demarcation element (e.g. first child of a form-group)
|
||||||
|
// Subclasses such as file inputs may have different structures
|
||||||
|
outerElement(){
|
||||||
|
return this.$element
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find expected form-group
|
||||||
|
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
|
||||||
|
if (fg.length === 0 && raiseError) {
|
||||||
|
$.error(`Failed to find form-group for ${$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
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// private
|
||||||
|
_rejectInvalidComponentMatches(){
|
||||||
|
for(let otherComponent in this.config.invalidComponentMatches){
|
||||||
|
otherComponent.rejectMatch(this.constructor.name, $element)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_rejectWithoutRequiredClasses(){
|
||||||
|
for(let requiredClass in this.config.requiredClasses){
|
||||||
|
if(!$element.hasClass(requiredClass)){
|
||||||
|
$.error(`${this.constructor.name} elements require class: ${requiredClass}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// static
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return BaseInput
|
||||||
|
|
||||||
|
})(jQuery)
|
||||||
|
|
||||||
|
export default BaseInput
|
18
js/src/bootstrapMaterialDesign.js
vendored
18
js/src/bootstrapMaterialDesign.js
vendored
|
@ -8,7 +8,7 @@
|
||||||
*
|
*
|
||||||
* NOTE: If omitting use of this class, please note that the Input component must be
|
* 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,
|
* initialized prior to other decorating components such as radio, checkbox,
|
||||||
* togglebutton, fileInput.
|
* switch, fileInput.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
const BootstrapMaterialDesign = (($) => {
|
const BootstrapMaterialDesign = (($) => {
|
||||||
|
@ -42,18 +42,18 @@ const BootstrapMaterialDesign = (($) => {
|
||||||
'.ripple' // generic marker class to add ripple to elements
|
'.ripple' // generic marker class to add ripple to elements
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
input: {
|
textInput: {
|
||||||
selector: [
|
selector: [
|
||||||
'input.form-control',
|
'input[type=text]',
|
||||||
'textarea.form-control',
|
'textarea',
|
||||||
'select.form-control'
|
'select'
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
checkbox: {
|
checkbox: {
|
||||||
selector: '.checkbox > label > input[type=checkbox]'
|
selector: '.checkbox > label > input[type=checkbox]'
|
||||||
},
|
},
|
||||||
togglebutton: {
|
switch: {
|
||||||
selector: '.togglebutton > label > input[type=checkbox]'
|
selector: '.switch > label > input[type=checkbox]'
|
||||||
},
|
},
|
||||||
radio: {
|
radio: {
|
||||||
selector: '.radio > label > input[type=radio]'
|
selector: '.radio > label > input[type=radio]'
|
||||||
|
@ -68,9 +68,9 @@ const BootstrapMaterialDesign = (($) => {
|
||||||
// create an ordered component list for instantiation
|
// create an ordered component list for instantiation
|
||||||
instantiation: [
|
instantiation: [
|
||||||
'ripples',
|
'ripples',
|
||||||
'input',
|
'textInput',
|
||||||
'checkbox',
|
'checkbox',
|
||||||
'togglebutton',
|
'switch',
|
||||||
'radio',
|
'radio',
|
||||||
'fileInput',
|
'fileInput',
|
||||||
'autofill'
|
'autofill'
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
|
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'
|
||||||
|
|
||||||
// Checkbox decorator, to be called after Input
|
|
||||||
const Checkbox = (($) => {
|
const Checkbox = (($) => {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -13,7 +17,11 @@ const Checkbox = (($) => {
|
||||||
const JQUERY_NO_CONFLICT = $.fn[NAME]
|
const JQUERY_NO_CONFLICT = $.fn[NAME]
|
||||||
|
|
||||||
const Default = {
|
const Default = {
|
||||||
template: `<span class='checkbox-material'><span class='check'></span></span>`
|
template: `<span class='checkbox-material'><span class='check'></span></span>`,
|
||||||
|
formGroup: {
|
||||||
|
autoCreate: true
|
||||||
|
},
|
||||||
|
invalidComponentMatches: [TextInput, FileInput, Radio, Switch]
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -21,40 +29,66 @@ const Checkbox = (($) => {
|
||||||
* Class Definition
|
* Class Definition
|
||||||
* ------------------------------------------------------------------------
|
* ------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
class Checkbox {
|
class Checkbox extends BaseInput {
|
||||||
|
|
||||||
constructor(element, config) {
|
constructor(element, config) {
|
||||||
this.$element = $(element)
|
super(element, Default, config)
|
||||||
this.config = $.extend({}, Default, config)
|
|
||||||
|
|
||||||
this.$element.after(this.config.template)
|
this.$element.after(this.config.template)
|
||||||
this.$formGroup = Util.findFormGroup(this.$element, false)
|
|
||||||
|
|
||||||
this._bindEventListeners()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dispose() {
|
dispose() {
|
||||||
$.removeData(this.$element, DATA_KEY)
|
super.dispose(DATA_KEY)
|
||||||
this.$element = null
|
}
|
||||||
this.$formGroup = null
|
|
||||||
this.config = null
|
static matches($element) {
|
||||||
|
// '.checkbox > label > input[type=checkbox]'
|
||||||
|
if ($element.attr('type') === 'checkbox') {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
static rejectMatch(component, $element) {
|
||||||
|
Util.assert(this.matches($element), `${component} component is invalid for type='checkbox'.`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
// private
|
// protected
|
||||||
_bindEventListeners() {
|
|
||||||
|
// Demarcation element (e.g. first child of a form-group)
|
||||||
|
// Subclasses such as file inputs may have different structures
|
||||||
|
outerElement() {
|
||||||
|
// '.checkbox > label > input[type=checkbox]'
|
||||||
|
return this.$element.parent().parent()
|
||||||
|
}
|
||||||
|
|
||||||
|
rejectWithoutRequiredStructure() {
|
||||||
|
// '.checkbox > label > input[type=checkbox]'
|
||||||
|
Util.assert(this.$element.parent().prop('tagName') === 'label', `${component} parent element should be <label>.`)
|
||||||
|
Util.assert(this.outerElement().hasClass('checkbox'), `${component} grandparent element should have class .checkbox.`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// protected
|
||||||
|
|
||||||
|
addFocusListener() {
|
||||||
// checkboxes didn't appear to bubble to the document, so we'll bind these directly
|
// checkboxes didn't appear to bubble to the document, so we'll bind these directly
|
||||||
this.$formGroup.find('.checkbox label').hover(() => {
|
this.$formGroup.find('.checkbox label').hover(() => {
|
||||||
Util.addFormGroupFocus(this.$formGroup)
|
Util.addFormGroupFocus(this.$formGroup)
|
||||||
}, () => {
|
}, () => {
|
||||||
Util.removeFormGroupFocus(this.$formGroup)
|
Util.removeFormGroupFocus(this.$formGroup)
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
addChangeListener() {
|
||||||
this.$element.change(() => {
|
this.$element.change(() => {
|
||||||
this.$element.blur()
|
this.$element.blur()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// private
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
// static
|
// static
|
||||||
static _jQueryInterface(config) {
|
static _jQueryInterface(config) {
|
||||||
|
|
|
@ -16,7 +16,6 @@ const FileInput = (($) => {
|
||||||
|
|
||||||
const ClassName = {
|
const ClassName = {
|
||||||
IS_FILEINPUT: 'is-fileinput',
|
IS_FILEINPUT: 'is-fileinput',
|
||||||
IS_EMPTY: 'is-empty'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -44,6 +43,20 @@ const FileInput = (($) => {
|
||||||
this.config = null
|
this.config = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static matches($element) {
|
||||||
|
if ($element.attr('type') === 'file') {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
static rejectMatch(component, $element) {
|
||||||
|
if (this.matches($element)) {
|
||||||
|
let msg = `${component} component is invalid for type='file'.`
|
||||||
|
$.error(msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
// private
|
// private
|
||||||
_bindEventListeners() {
|
_bindEventListeners() {
|
||||||
|
@ -63,22 +76,14 @@ const FileInput = (($) => {
|
||||||
})
|
})
|
||||||
value = value.substring(0, value.length - 2)
|
value = value.substring(0, value.length - 2)
|
||||||
if (value) {
|
if (value) {
|
||||||
this._removeIsEmpty()
|
this.removeIsEmpty()
|
||||||
} else {
|
} else {
|
||||||
this._addIsEmpty()
|
this.addIsEmpty()
|
||||||
}
|
}
|
||||||
this.$formGroup.find('input.form-control[readonly]').val(value)
|
this.$formGroup.find('input.form-control[readonly]').val(value)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
_addIsEmpty() {
|
|
||||||
this.$formGroup.addClass(ClassName.IS_EMPTY)
|
|
||||||
}
|
|
||||||
|
|
||||||
_removeIsEmpty() {
|
|
||||||
this.$formGroup.removeClass(ClassName.IS_EMPTY)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
// static
|
// static
|
||||||
static _jQueryInterface(config) {
|
static _jQueryInterface(config) {
|
||||||
|
|
|
@ -1,19 +1,19 @@
|
||||||
//import Util from './util'
|
//import Util from './util'
|
||||||
|
|
||||||
// Togglebutton decorator, to be called after Input
|
// Switch decorator, to be called after Input
|
||||||
const Togglebutton = (($) => {
|
const Switch = (($) => {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ------------------------------------------------------------------------
|
* ------------------------------------------------------------------------
|
||||||
* Constants
|
* Constants
|
||||||
* ------------------------------------------------------------------------
|
* ------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
const NAME = 'togglebutton'
|
const NAME = 'switch'
|
||||||
const DATA_KEY = `mdb.${NAME}`
|
const DATA_KEY = `mdb.${NAME}`
|
||||||
const JQUERY_NO_CONFLICT = $.fn[NAME]
|
const JQUERY_NO_CONFLICT = $.fn[NAME]
|
||||||
|
|
||||||
const Default = {
|
const Default = {
|
||||||
template: `<span class='toggle'></span>`
|
template: `<span class='switch-decorator'></span>`
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -21,7 +21,7 @@ const Togglebutton = (($) => {
|
||||||
* Class Definition
|
* Class Definition
|
||||||
* ------------------------------------------------------------------------
|
* ------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
class Togglebutton {
|
class Switch {
|
||||||
|
|
||||||
constructor(element, config) {
|
constructor(element, config) {
|
||||||
this.$element = $(element)
|
this.$element = $(element)
|
||||||
|
@ -47,7 +47,7 @@ const Togglebutton = (($) => {
|
||||||
let data = $element.data(DATA_KEY)
|
let data = $element.data(DATA_KEY)
|
||||||
|
|
||||||
if (!data) {
|
if (!data) {
|
||||||
data = new Togglebutton(this, config)
|
data = new Switch(this, config)
|
||||||
$element.data(DATA_KEY, data)
|
$element.data(DATA_KEY, data)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -59,15 +59,15 @@ const Togglebutton = (($) => {
|
||||||
* jQuery
|
* jQuery
|
||||||
* ------------------------------------------------------------------------
|
* ------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
$.fn[NAME] = Togglebutton._jQueryInterface
|
$.fn[NAME] = Switch._jQueryInterface
|
||||||
$.fn[NAME].Constructor = Togglebutton
|
$.fn[NAME].Constructor = Switch
|
||||||
$.fn[NAME].noConflict = () => {
|
$.fn[NAME].noConflict = () => {
|
||||||
$.fn[NAME] = JQUERY_NO_CONFLICT
|
$.fn[NAME] = JQUERY_NO_CONFLICT
|
||||||
return Togglebutton._jQueryInterface
|
return Switch._jQueryInterface
|
||||||
}
|
}
|
||||||
|
|
||||||
return Togglebutton
|
return Switch
|
||||||
|
|
||||||
})(jQuery)
|
})(jQuery)
|
||||||
|
|
||||||
export default Togglebutton
|
export default Switch
|
|
@ -1,13 +1,18 @@
|
||||||
|
import BaseInput from './baseInput'
|
||||||
|
import Checkbox from './checkbox'
|
||||||
|
import FileInput from './fileInput'
|
||||||
|
import Radio from './radio'
|
||||||
|
import Switch from './switch'
|
||||||
import Util from './util'
|
import Util from './util'
|
||||||
|
|
||||||
const Input = (($) => {
|
const TextInput = (($) => {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ------------------------------------------------------------------------
|
* ------------------------------------------------------------------------
|
||||||
* Constants
|
* Constants
|
||||||
* ------------------------------------------------------------------------
|
* ------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
const NAME = 'input'
|
const NAME = 'textInput'
|
||||||
const DATA_KEY = `mdb.${NAME}`
|
const DATA_KEY = `mdb.${NAME}`
|
||||||
const JQUERY_NO_CONFLICT = $.fn[NAME]
|
const JQUERY_NO_CONFLICT = $.fn[NAME]
|
||||||
|
|
||||||
|
@ -15,23 +20,15 @@ const Input = (($) => {
|
||||||
convertInputSizeVariations: true,
|
convertInputSizeVariations: true,
|
||||||
template: `<span class='material-input'></span>`,
|
template: `<span class='material-input'></span>`,
|
||||||
formGroup: {
|
formGroup: {
|
||||||
template: `<div class='form-group'></div>`
|
autoCreate: true
|
||||||
}
|
},
|
||||||
|
requiredClasses: ['form-control'],
|
||||||
|
invalidComponentMatches: [Checkbox, FileInput, Radio, Switch]
|
||||||
}
|
}
|
||||||
|
|
||||||
const InputSizeConversions = {
|
const InputSizeConversions = {
|
||||||
'input-lg': 'form-group-lg',
|
'textInput-lg': 'form-group-lg',
|
||||||
'input-sm': 'form-group-sm'
|
'textInput-sm': 'form-group-sm'
|
||||||
}
|
|
||||||
|
|
||||||
const ClassName = {
|
|
||||||
IS_EMPTY: 'is-empty',
|
|
||||||
FORM_GROUP: 'form-group',
|
|
||||||
HAS_ERROR: 'has-error'
|
|
||||||
}
|
|
||||||
|
|
||||||
const Selector = {
|
|
||||||
FORM_GROUP: `.${ClassName.FORM_GROUP}` //,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -39,115 +36,95 @@ const Input = (($) => {
|
||||||
* Class Definition
|
* Class Definition
|
||||||
* ------------------------------------------------------------------------
|
* ------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
class Input {
|
class TextInput extends BaseInput {
|
||||||
|
|
||||||
constructor(element, config) {
|
constructor(element, config) {
|
||||||
this.$element = $(element)
|
super(element, Default, config)
|
||||||
this.config = $.extend({}, Default, config)
|
|
||||||
|
|
||||||
// Requires form-group standard markup (will add it if necessary)
|
|
||||||
this.$formGroup = this._findOrCreateFormGroup()
|
|
||||||
|
|
||||||
this._convertInputSizeVariations()
|
this._convertInputSizeVariations()
|
||||||
|
|
||||||
// Initially mark as empty
|
// Initially mark as empty
|
||||||
if (this._isEmpty()) {
|
if (this.isEmpty()) {
|
||||||
this.$formGroup.addClass(ClassName.IS_EMPTY)
|
this.addIsEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add marker div the end of the form-group
|
// Add marker div the end of the form-group
|
||||||
this.$formGroup.append(this.config.template)
|
this.$formGroup.append(this.config.template)
|
||||||
|
|
||||||
this._bindEventListeners()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dispose() {
|
dispose() {
|
||||||
$.removeData(this.$element, DATA_KEY)
|
super.dispose(DATA_KEY)
|
||||||
this.$element = null
|
}
|
||||||
this.$formGroup = null
|
|
||||||
this.config = null
|
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'.`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
// private
|
// protected
|
||||||
|
|
||||||
_bindEventListeners() {
|
addFocusListener() {
|
||||||
|
this.$element
|
||||||
|
.on('focus', () => {
|
||||||
|
this.addFormGroupFocus(this.$formGroup)
|
||||||
|
})
|
||||||
|
.on('blur', () => {
|
||||||
|
this.removeFormGroupFocus(this.$formGroup)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
addChangeListener() {
|
||||||
this.$element
|
this.$element
|
||||||
.on('keydown paste', (event) => {
|
.on('keydown paste', (event) => {
|
||||||
if (Util.isChar(event)) {
|
if (Util.isChar(event)) {
|
||||||
this._removeIsEmpty()
|
this.removeIsEmpty()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.on('keyup change', (event) => {
|
.on('keyup change', (event) => {
|
||||||
let isValid = (typeof this.$element[0].checkValidity === 'undefined' || this.$element[0].checkValidity())
|
|
||||||
|
|
||||||
if (this.$element.val() === '' && isValid) {
|
// make sure empty is added back when there is a programmatic value change.
|
||||||
this._addIsEmpty()
|
// 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 {
|
} else {
|
||||||
this._removeIsEmpty()
|
this.removeIsEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validation events do not bubble, so they must be attached directly to the input: http://jsfiddle.net/PEpRM/1/
|
// 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
|
// Further, even the bind method is being caught, but since we are already calling #checkValidity here, just alter
|
||||||
// the form-group on change.
|
// 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.
|
// 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.
|
// BUT, I've left it here for backwards compatibility.
|
||||||
|
let isValid = (typeof this.$element[0].checkValidity === 'undefined' || this.$element[0].checkValidity())
|
||||||
if (isValid) {
|
if (isValid) {
|
||||||
this._removeHasError()
|
this.removeHasError()
|
||||||
} else {
|
} else {
|
||||||
this._addHasError()
|
this.addHasError()
|
||||||
}
|
|
||||||
})
|
|
||||||
.on('focus', () => {
|
|
||||||
Util.addFormGroupFocus(this.$formGroup)
|
|
||||||
})
|
|
||||||
.on('blur', () => {
|
|
||||||
Util.removeFormGroupFocus(this.$formGroup)
|
|
||||||
})
|
|
||||||
// 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')
|
|
||||||
.on('change', () => {
|
|
||||||
if (this.$element.attr('type') === 'file') {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let value = this.$element.val()
|
|
||||||
if (value) {
|
|
||||||
this._removeIsEmpty()
|
|
||||||
} else {
|
|
||||||
this._addIsEmpty()
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
_addHasError() {
|
// ------------------------------------------------------------------------
|
||||||
this.$formGroup.addClass(ClassName.HAS_ERROR)
|
// private
|
||||||
}
|
|
||||||
|
|
||||||
_removeHasError() {
|
|
||||||
this.$formGroup.removeClass(ClassName.HAS_ERROR)
|
|
||||||
}
|
|
||||||
|
|
||||||
_addIsEmpty() {
|
|
||||||
this.$formGroup.addClass(ClassName.IS_EMPTY)
|
|
||||||
}
|
|
||||||
|
|
||||||
_removeIsEmpty() {
|
|
||||||
this.$formGroup.removeClass(ClassName.IS_EMPTY)
|
|
||||||
}
|
|
||||||
|
|
||||||
_isEmpty() {
|
|
||||||
return (this.$element.val() === null || this.$element.val() === undefined || this.$element.val() === '')
|
|
||||||
}
|
|
||||||
|
|
||||||
_convertInputSizeVariations() {
|
_convertInputSizeVariations() {
|
||||||
if (!this.config.convertInputSizeVariations) {
|
if (!this.config.convertInputSizeVariations) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Modification - Change input-sm/lg to form-group-sm/lg instead (preferred standard and simpler css/less variants)
|
// Modification - Change textInput-sm/lg to form-group-sm/lg instead (preferred standard and simpler css/less variants)
|
||||||
for (let inputSize in InputSizeConversions) {
|
for (let inputSize in InputSizeConversions) {
|
||||||
if (this.$element.hasClass(inputSize)) {
|
if (this.$element.hasClass(inputSize)) {
|
||||||
this.$element.removeClass(inputSize)
|
this.$element.removeClass(inputSize)
|
||||||
|
@ -156,15 +133,6 @@ const Input = (($) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_findOrCreateFormGroup() {
|
|
||||||
let fg = this.$element.closest(Selector.FORM_GROUP) // note that form-group may be grandparent in the case of an input-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
|
|
||||||
}
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
// static
|
// static
|
||||||
static _jQueryInterface(config) {
|
static _jQueryInterface(config) {
|
||||||
|
@ -173,7 +141,7 @@ const Input = (($) => {
|
||||||
let data = $element.data(DATA_KEY)
|
let data = $element.data(DATA_KEY)
|
||||||
|
|
||||||
if (!data) {
|
if (!data) {
|
||||||
data = new Input(this, config)
|
data = new TextInput(this, config)
|
||||||
$element.data(DATA_KEY, data)
|
$element.data(DATA_KEY, data)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -185,15 +153,15 @@ const Input = (($) => {
|
||||||
* jQuery
|
* jQuery
|
||||||
* ------------------------------------------------------------------------
|
* ------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
$.fn[NAME] = Input._jQueryInterface
|
$.fn[NAME] = TextInput._jQueryInterface
|
||||||
$.fn[NAME].Constructor = Input
|
$.fn[NAME].Constructor = TextInput
|
||||||
$.fn[NAME].noConflict = () => {
|
$.fn[NAME].noConflict = () => {
|
||||||
$.fn[NAME] = JQUERY_NO_CONFLICT
|
$.fn[NAME] = JQUERY_NO_CONFLICT
|
||||||
return Input._jQueryInterface
|
return TextInput._jQueryInterface
|
||||||
}
|
}
|
||||||
|
|
||||||
return Input
|
return TextInput
|
||||||
|
|
||||||
})(jQuery)
|
})(jQuery)
|
||||||
|
|
||||||
export default Input
|
export default TextInput
|
|
@ -16,15 +16,6 @@ const Util = (($) => {
|
||||||
transition: 'transitionend'
|
transition: 'transitionend'
|
||||||
}
|
}
|
||||||
|
|
||||||
const ClassName = {
|
|
||||||
IS_FOCUSED: 'is-focused',
|
|
||||||
FORM_GROUP: 'form-group'
|
|
||||||
}
|
|
||||||
|
|
||||||
const Selector = {
|
|
||||||
FORM_GROUP: `.${ClassName.FORM_GROUP}` //,
|
|
||||||
}
|
|
||||||
|
|
||||||
function transitionEndTest() {
|
function transitionEndTest() {
|
||||||
if (window.QUnit) {
|
if (window.QUnit) {
|
||||||
return false
|
return false
|
||||||
|
@ -75,23 +66,10 @@ const Util = (($) => {
|
||||||
return false
|
return false
|
||||||
},
|
},
|
||||||
|
|
||||||
addFormGroupFocus(formGroup) {
|
assert(test, message) {
|
||||||
formGroup.addClass(ClassName.IS_FOCUSED)
|
if (test) {
|
||||||
},
|
$.error(message)
|
||||||
|
|
||||||
removeFormGroupFocus(formGroup) {
|
|
||||||
formGroup.removeClass(ClassName.IS_FOCUSED)
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
Find expected form-group
|
|
||||||
*/
|
|
||||||
findFormGroup($element, raiseError = true) {
|
|
||||||
let fg = $element.closest(Selector.FORM_GROUP) // note that form-group may be grandparent in the case of an input-group
|
|
||||||
if (fg.length === 0 && raiseError) {
|
|
||||||
$.error(`Failed to find form-group for ${$element}`)
|
|
||||||
}
|
}
|
||||||
return fg
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
@import 'reboot';
|
@import 'reboot';
|
||||||
@import 'buttons';
|
@import 'buttons';
|
||||||
@import 'checkboxes';
|
@import 'checkboxes';
|
||||||
@import 'togglebutton';
|
@import 'switch';
|
||||||
@import 'radios';
|
@import 'radios';
|
||||||
@import 'inputs';
|
@import 'inputs';
|
||||||
@import 'form';
|
@import 'form';
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
|
|
||||||
|
|
||||||
.togglebutton {
|
.switch {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
&, label, input, .toggle {
|
&, label, input, .switch-decorator {
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
label {
|
label {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: $mdb-toggle-label-color;
|
color: $mdb-switch-label-color;
|
||||||
|
|
||||||
// Hide original checkbox
|
// Hide original checkbox
|
||||||
input[type=checkbox] {
|
input[type=checkbox] {
|
||||||
|
@ -16,8 +16,8 @@
|
||||||
height: 0;
|
height: 0;
|
||||||
}
|
}
|
||||||
// Switch bg off and disabled
|
// Switch bg off and disabled
|
||||||
.toggle,
|
.switch-decorator,
|
||||||
input[type=checkbox][disabled] + .toggle {
|
input[type=checkbox][disabled] + .switch-decorator {
|
||||||
content: "";
|
content: "";
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 30px;
|
width: 30px;
|
||||||
|
@ -29,7 +29,7 @@
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
// Handle off
|
// Handle off
|
||||||
.toggle:after {
|
.switch-decorator:after {
|
||||||
content: "";
|
content: "";
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 20px;
|
width: 20px;
|
||||||
|
@ -45,33 +45,33 @@
|
||||||
input[type=checkbox] {
|
input[type=checkbox] {
|
||||||
// Handle disabled
|
// Handle disabled
|
||||||
&[disabled] {
|
&[disabled] {
|
||||||
& + .toggle:after,
|
& + .switch-decorator:after,
|
||||||
&:checked + .toggle:after {
|
&:checked + .switch-decorator:after {
|
||||||
background-color: #BDBDBD;
|
background-color: #BDBDBD;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
& + .toggle:active:after,
|
& + .switch-decorator:active:after,
|
||||||
&[disabled] + .toggle:active:after {
|
&[disabled] + .switch-decorator:active:after {
|
||||||
box-shadow: 0 1px 3px 1px rgba(0, 0, 0, 0.4), 0 0 0 15px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 1px 3px 1px rgba(0, 0, 0, 0.4), 0 0 0 15px rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ripple off and disabled
|
// Ripple off and disabled
|
||||||
&:checked + .toggle:after {
|
&:checked + .switch-decorator:after {
|
||||||
left: 15px;
|
left: 15px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
& label input[type=checkbox]:checked {
|
& label input[type=checkbox]:checked {
|
||||||
& + .toggle {
|
& + .switch-decorator {
|
||||||
background-color: rgba($brand-primary, (50/100)); // Switch bg on
|
background-color: rgba($brand-primary, (50/100)); // Switch bg on
|
||||||
}
|
}
|
||||||
|
|
||||||
& + .toggle:after {
|
& + .switch-decorator:after {
|
||||||
background-color: $brand-primary; // Handle on
|
background-color: $brand-primary; // Handle on
|
||||||
}
|
}
|
||||||
|
|
||||||
& + .toggle:active:after {
|
& + .switch-decorator:active:after {
|
||||||
box-shadow: 0 1px 3px 1px rgba(0, 0, 0, 0.4), 0 0 0 15px rgba($brand-primary, (10/100)); // Ripple on
|
box-shadow: 0 1px 3px 1px rgba(0, 0, 0, 0.4), 0 0 0 15px rgba($brand-primary, (10/100)); // Ripple on
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -85,7 +85,7 @@ $mdb-popover-color: #ececec !default;
|
||||||
$mdb-dropdown-font-size: 16px !default;
|
$mdb-dropdown-font-size: 16px !default;
|
||||||
|
|
||||||
// Toggle
|
// Toggle
|
||||||
$mdb-toggle-label-color: $mdb-checkbox-label-color !default;
|
$mdb-switch-label-color: $mdb-checkbox-label-color !default;
|
||||||
|
|
||||||
// Radio:
|
// Radio:
|
||||||
$mdb-radio-label-color: $mdb-checkbox-label-color !default;
|
$mdb-radio-label-color: $mdb-checkbox-label-color !default;
|
||||||
|
|
Loading…
Reference in New Issue
Block a user