finished initial refactoring of discrete es6 inputs, renaming toggle to switch and adding a BaseToggle (checkbox|radio|switch) to better align with Material Design nomenclature

This commit is contained in:
Kevin Ross 2015-12-05 15:07:37 -06:00
parent 3a547cd0f3
commit c5282e7097
12 changed files with 205 additions and 116 deletions

View File

@ -100,12 +100,14 @@ module.exports = function (grunt) {
modules: 'ignore' modules: 'ignore'
}, },
files: { files: {
'dist/js/babel/baseInput.js': 'js/src/baseInput.js',
'dist/js/babel/baseToggle.js': 'js/src/baseToggle.js',
'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/textInput.js', 'dist/js/babel/textInput.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/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/fileInput.js',
'dist/js/babel/bootstrapMaterialDesign.js': 'js/src/bootstrapMaterialDesign.js', 'dist/js/babel/bootstrapMaterialDesign.js': 'js/src/bootstrapMaterialDesign.js',
@ -121,12 +123,14 @@ module.exports = function (grunt) {
'docs/assets/js/dist/application.js': 'docs/assets/js/src/application.js', 'docs/assets/js/dist/application.js': 'docs/assets/js/src/application.js',
// generate core so we have local debugging // generate core so we have local debugging
'docs/dist/js/babel/baseInput.js': 'js/src/baseInput.js',
'docs/dist/js/babel/baseToggle.js': 'js/src/baseToggle.js',
'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/textInput.js', 'docs/dist/js/babel/textInput.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/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/fileInput.js',
'docs/dist/js/babel/bootstrapMaterialDesign.js': 'js/src/bootstrapMaterialDesign.js', 'docs/dist/js/babel/bootstrapMaterialDesign.js': 'js/src/bootstrapMaterialDesign.js',
@ -145,12 +149,14 @@ module.exports = function (grunt) {
modules: 'umd' modules: 'umd'
}, },
files: { files: {
'dist/js/umd/baseInput.js': 'js/src/baseInput.js',
'dist/js/umd/baseToggle.js': 'js/src/baseToggle.js',
'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/textInput.js', 'dist/js/umd/textInput.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/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/fileInput.js',
'dist/js/umd/bootstrapMaterialDesign.js': 'js/src/bootstrapMaterialDesign.js', 'dist/js/umd/bootstrapMaterialDesign.js': 'js/src/bootstrapMaterialDesign.js',
@ -205,6 +211,8 @@ module.exports = function (grunt) {
}, },
bootstrap: { bootstrap: {
src: [ src: [
'dist/js/babel/baseInput.js',
'dist/js/babel/baseToggle.js',
'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',

View File

@ -10,14 +10,15 @@
"coreJs": [ "coreJs": [
"../dist/js/babel/baseInput.js", "../dist/js/babel/baseInput.js",
"../dist/js/babel/baseToggle.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",
"../dist/js/babel/fileInput.js", "../dist/js/babel/fileInput.js",
"../dist/js/babel/input.js", "../dist/js/babel/textInput.js",
"../dist/js/babel/radio.js", "../dist/js/babel/radio.js",
"../dist/js/babel/ripples.js", "../dist/js/babel/ripples.js",
"../dist/js/babel/togglebutton.js", "../dist/js/babel/switch.js",
"../dist/js/babel/util.js" "../dist/js/babel/util.js"
] ]
} }

View File

@ -36,12 +36,12 @@ const BaseInput = (($) => {
// Enforce no overlap between components to prevent side effects // Enforce no overlap between components to prevent side effects
this._rejectInvalidComponentMatches() this._rejectInvalidComponentMatches()
// Enforce required classes for a consistent rendering
this._rejectWithoutRequiredClasses()
// Enforce expected structure (if any) // Enforce expected structure (if any)
this.rejectWithoutRequiredStructure() this.rejectWithoutRequiredStructure()
// Enforce required classes for a consistent rendering
this._rejectWithoutRequiredClasses()
if(this.config.formGroup.autoCreate) { if(this.config.formGroup.autoCreate) {
// Will create form-group if necessary // Will create form-group if necessary
this.autoCreateFormGroup() this.autoCreateFormGroup()
@ -76,12 +76,12 @@ const BaseInput = (($) => {
// implement // implement
} }
addFormGroupFocus(formGroup) { addFormGroupFocus() {
formGroup.addClass(ClassName.IS_FOCUSED) this.$formGroup.addClass(ClassName.IS_FOCUSED)
} }
removeFormGroupFocus(formGroup) { removeFormGroupFocus() {
formGroup.removeClass(ClassName.IS_FOCUSED) this.$formGroup.removeClass(ClassName.IS_FOCUSED)
} }
addHasError() { addHasError() {

88
js/src/baseToggle.js Normal file
View File

@ -0,0 +1,88 @@
import BaseInput from './baseInput'
//import TextInput from './textInput'
//import FileInput from './fileInput'
//import Radio from './radio'
//import Switch from './switch'
import Util from './util'
const BaseToggle = (($) => {
/**
* ------------------------------------------------------------------------
* Constants
* ------------------------------------------------------------------------
*/
const Default = {
formGroup: {
autoCreate: true
}
}
const Selector = {
LABEL: 'label'
}
/**
* ------------------------------------------------------------------------
* Class Definition
* ------------------------------------------------------------------------
*/
class BaseToggle extends BaseInput {
constructor(element, config, inputType, outerClass) {
super(element, Default, config)
this.$element.after(this.config.template)
// '.checkbox|switch|radio > label > input[type=checkbox|radio]'
// '.${this.outerClass} > label > input[type=${this.inputType}]'
this.inputType = inputType
this.outerClass = outerClass
}
dispose() {
super.dispose(DATA_KEY)
}
// ------------------------------------------------------------------------
// protected
// Demarcation element (e.g. first child of a form-group)
outerElement() {
// '.checkbox|switch|radio > label > input[type=checkbox|radio]'
// '.${this.outerClass} > label > input[type=${this.inputType}]'
return this.$element.parent().parent()
}
rejectWithoutRequiredStructure() {
// '.checkbox|switch|radio > label > input[type=checkbox|radio]'
// '.${this.outerClass} > label > input[type=${this.inputType}]'
Util.assert(this.$element.parent().prop('tagName') === 'label', `${component} parent element should be <label>.`)
Util.assert(this.outerElement().hasClass(this.outerClass), `${component} grandparent element should have class .${this.outerClass}.`)
}
// ------------------------------------------------------------------------
// protected
addFocusListener() {
// checkboxes didn't appear to bubble to the document, so we'll bind these directly
this.$formGroup.find(Selector.LABEL).hover(() => {
this.addFormGroupFocus()
}, () => {
this.removeFormGroupFocus()
})
}
addChangeListener() {
this.$element.change(() => {
this.$element.blur()
})
}
// ------------------------------------------------------------------------
// private
}
return BaseToggle
})(jQuery)
export default BaseToggle

View File

@ -1,4 +1,4 @@
import BaseInput from './baseInput' import BaseToggle from './baseToggle'
import TextInput from './textInput' import TextInput from './textInput'
import FileInput from './fileInput' import FileInput from './fileInput'
import Radio from './radio' import Radio from './radio'
@ -17,11 +17,12 @@ 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-decorator'><span class='check'></span></span>`,
formGroup: { invalidComponentMatches: [FileInput, Radio, TextInput]
autoCreate: true }
},
invalidComponentMatches: [TextInput, FileInput, Radio, Switch] const Selector = {
LABEL: 'label'
} }
/** /**
@ -29,15 +30,10 @@ const Checkbox = (($) => {
* Class Definition * Class Definition
* ------------------------------------------------------------------------ * ------------------------------------------------------------------------
*/ */
class Checkbox extends BaseInput { class Checkbox extends BaseToggle {
constructor(element, config) { constructor(element, config, inputType = NAME, outerClass = NAME) {
super(element, Default, config) super(element, $.extend({}, Default, config), inputType, outerClass)
this.$element.after(this.config.template)
}
dispose() {
super.dispose(DATA_KEY)
} }
static matches($element) { static matches($element) {
@ -55,37 +51,9 @@ const Checkbox = (($) => {
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// protected // protected
// 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 // protected
addFocusListener() {
// checkboxes didn't appear to bubble to the document, so we'll bind these directly
this.$formGroup.find('.checkbox label').hover(() => {
Util.addFormGroupFocus(this.$formGroup)
}, () => {
Util.removeFormGroupFocus(this.$formGroup)
})
}
addChangeListener() {
this.$element.change(() => {
this.$element.blur()
})
}
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// private // private

View File

@ -1,3 +1,8 @@
import BaseInput from './baseInput'
import Checkbox from './checkbox'
import Radio from './radio'
import Switch from './switch'
import TextInput from './textInput'
import Util from './util' import Util from './util'
// FileInput decorator, to be called after Input // FileInput decorator, to be called after Input
@ -12,35 +17,36 @@ const FileInput = (($) => {
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 = {
formGroup: {
autoCreate: true
},
invalidComponentMatches: [Checkbox, Radio, Switch, TextInput]
}
const ClassName = { const ClassName = {
IS_FILEINPUT: 'is-fileinput', IS_FILEINPUT: 'is-fileinput',
} }
const Selector = {
FILENAMES: 'input.form-control[readonly]'
}
/** /**
* ------------------------------------------------------------------------ * ------------------------------------------------------------------------
* Class Definition * Class Definition
* ------------------------------------------------------------------------ * ------------------------------------------------------------------------
*/ */
class FileInput { class FileInput extends BaseInput {
constructor(element, config) { constructor(element, config) {
this.$element = $(element) super(element, Default, config)
this.config = $.extend({}, Default, config)
this.$formGroup = Util.findFormGroup(this.$element)
this.$formGroup.addClass(ClassName.IS_FILEINPUT) this.$formGroup.addClass(ClassName.IS_FILEINPUT)
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) { static matches($element) {
@ -51,23 +57,27 @@ const FileInput = (($) => {
} }
static rejectMatch(component, $element) { static rejectMatch(component, $element) {
if (this.matches($element)) { Util.assert(this.matches($element), `${component} component is invalid for type='file'.`)
let msg = `${component} component is invalid for type='file'.`
$.error(msg)
}
} }
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// private // protected
_bindEventListeners() {
rejectWithoutRequiredStructure() {
// FIXME: implement this once we determine how we want to implement files since BS4 has tried to take a shot at this
}
addFocusListener() {
this.$formGroup this.$formGroup
.on('focus', () => { .on('focus', () => {
Util.addFormGroupFocus(this.$formGroup) this.addFormGroupFocus()
}) })
.on('blur', () => { .on('blur', () => {
Util.removeFormGroupFocus(this.$formGroup) this.removeFormGroupFocus()
}) })
}
addChangeListener() {
// set the fileinput readonly field with the name of the file // set the fileinput readonly field with the name of the file
this.$element.on('change', () => { this.$element.on('change', () => {
let value = '' let value = ''
@ -80,10 +90,13 @@ const FileInput = (($) => {
} else { } else {
this.addIsEmpty() this.addIsEmpty()
} }
this.$formGroup.find('input.form-control[readonly]').val(value) this.$formGroup.find(Selector.FILENAMES).val(value)
}) })
} }
// ------------------------------------------------------------------------
// private
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// static // static
static _jQueryInterface(config) { static _jQueryInterface(config) {

View File

@ -1,4 +1,9 @@
//import Util from './util' import BaseToggle from './baseToggle'
import TextInput from './textInput'
import FileInput from './fileInput'
import Checkbox from './checkbox'
import Switch from './switch'
import Util from './util'
// Radio decorator, to be called after Input // Radio decorator, to be called after Input
const Radio = (($) => { const Radio = (($) => {
@ -13,7 +18,8 @@ const Radio = (($) => {
const JQUERY_NO_CONFLICT = $.fn[NAME] const JQUERY_NO_CONFLICT = $.fn[NAME]
const Default = { const Default = {
template: `<span class='circle'></span><span class='check'></span>` template: `<span class='radio-decorator'></span><span class='check'></span>`,
invalidComponentMatches: [Checkbox, FileInput, Switch, TextInput]
} }
/** /**
@ -21,21 +27,27 @@ const Radio = (($) => {
* Class Definition * Class Definition
* ------------------------------------------------------------------------ * ------------------------------------------------------------------------
*/ */
class Radio { class Radio extends BaseToggle {
constructor(element, config) { constructor(element, config) {
this.$element = $(element) super(element, $.extend({}, Default, config), NAME, NAME)
this.config = $.extend({}, Default, config)
this.$element.after(this.config.template)
} }
dispose() { static matches($element) {
$.removeData(this.$element, DATA_KEY) // '.radio > label > input[type=radio]'
this.$element = null if ($element.attr('type') === 'radio') {
this.config = null return true
}
return false
} }
static rejectMatch(component, $element) {
Util.assert(this.matches($element), `${component} component is invalid for type='radio'.`)
}
// ------------------------------------------------------------------------
// protected
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// private // private

View File

@ -1,4 +1,4 @@
//import Util from './util' import Checkbox from './checkbox'
// Switch decorator, to be called after Input // Switch decorator, to be called after Input
const Switch = (($) => { const Switch = (($) => {
@ -21,21 +21,20 @@ const Switch = (($) => {
* Class Definition * Class Definition
* ------------------------------------------------------------------------ * ------------------------------------------------------------------------
*/ */
class Switch { class Switch extends Checkbox {
constructor(element, config) { constructor(element, config) {
this.$element = $(element) super(element, $.extend({}, Default, config), 'checkbox', NAME)
this.config = $.extend({}, Default, config) // selector: '.switch > label > input[type=checkbox]'
this.$element.after(this.config.template)
} }
dispose() { dispose() {
$.removeData(this.$element, DATA_KEY) super.dispose(DATA_KEY)
this.$element = null
this.config = null
} }
// ------------------------------------------------------------------------
// protected
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// private // private

View File

@ -77,10 +77,10 @@ const TextInput = (($) => {
addFocusListener() { addFocusListener() {
this.$element this.$element
.on('focus', () => { .on('focus', () => {
this.addFormGroupFocus(this.$formGroup) this.addFormGroupFocus()
}) })
.on('blur', () => { .on('blur', () => {
this.removeFormGroupFocus(this.$formGroup) this.removeFormGroupFocus()
}) })
} }

View File

@ -24,7 +24,7 @@
pointer-events: none; pointer-events: none;
} }
.checkbox-material { .checkbox-decorator {
vertical-align: middle; vertical-align: middle;
position: relative; position: relative;
top: 3px; top: 3px;
@ -75,19 +75,19 @@
input[type=checkbox] { input[type=checkbox] {
&:focus + .checkbox-material .check:after { &:focus + .checkbox-decorator .check:after {
opacity: 0.2; opacity: 0.2;
} }
&:checked { &:checked {
// FIXME: once working - combine further to reduce code // FIXME: once working - combine further to reduce code
& + .checkbox-material .check { & + .checkbox-decorator .check {
color: $mdb-checkbox-checked-color; color: $mdb-checkbox-checked-color;
border-color: $mdb-checkbox-checked-color; border-color: $mdb-checkbox-checked-color;
} }
& + .checkbox-material .check:before { & + .checkbox-decorator .check:before {
color: $mdb-checkbox-checked-color; color: $mdb-checkbox-checked-color;
box-shadow: 0 0 0 10px, box-shadow: 0 0 0 10px,
10px -10px 0 10px, 10px -10px 0 10px,
@ -98,22 +98,22 @@
animation: checkbox-on $mdb-checkbox-animation-check forwards; animation: checkbox-on $mdb-checkbox-animation-check forwards;
} }
& + .checkbox-material:before { & + .checkbox-decorator:before {
animation: rippleOn $mdb-checkbox-animation-ripple; animation: rippleOn $mdb-checkbox-animation-ripple;
} }
& + .checkbox-material .check:after { & + .checkbox-decorator .check:after {
//background-color: $brand-success; // FIXME: seems like tho wrong color, test and make sure it can be removed //background-color: $brand-success; // FIXME: seems like tho wrong color, test and make sure it can be removed
animation: rippleOn $mdb-checkbox-animation-ripple forwards; // Ripple effect on check animation: rippleOn $mdb-checkbox-animation-ripple forwards; // Ripple effect on check
} }
} }
&:not(:checked) { &:not(:checked) {
& + .checkbox-material:before { & + .checkbox-decorator:before {
animation: rippleOff $mdb-checkbox-animation-ripple; animation: rippleOff $mdb-checkbox-animation-ripple;
} }
& + .checkbox-material .check:after { & + .checkbox-decorator .check:after {
animation: rippleOff $mdb-checkbox-animation-ripple forwards; // Ripple effect on uncheck animation: rippleOff $mdb-checkbox-animation-ripple forwards; // Ripple effect on uncheck
} }
@ -123,12 +123,12 @@
// Style for disabled inputs // Style for disabled inputs
fieldset[disabled] &, fieldset[disabled] &,
fieldset[disabled] & input[type=checkbox], fieldset[disabled] & input[type=checkbox],
input[type=checkbox][disabled]:not(:checked) ~ .checkbox-material .check:before, input[type=checkbox][disabled]:not(:checked) ~ .checkbox-decorator .check:before,
input[type=checkbox][disabled]:not(:checked) ~ .checkbox-material .check, input[type=checkbox][disabled]:not(:checked) ~ .checkbox-decorator .check,
input[type=checkbox][disabled] + .circle { input[type=checkbox][disabled] + .radio-decorator {
opacity: 0.5; opacity: 0.5;
} }
input[type=checkbox][disabled] + .checkbox-material .check:after { input[type=checkbox][disabled] + .checkbox-decorator .check:after {
background-color: $mdb-text-color-primary; background-color: $mdb-text-color-primary;
transform: rotate(-45deg); transform: rotate(-45deg);
} }

View File

@ -26,7 +26,7 @@
img { img {
background: rgba(0,0,0,0.1); background: rgba(0,0,0,0.1);
padding: 1px; padding: 1px;
&.circle { &.radio-decorator {
border-radius: 100%; border-radius: 100%;
} }
} }
@ -43,7 +43,7 @@
margin-right: -7px; margin-right: -7px;
margin-top: 5px; margin-top: 5px;
margin-bottom: -5px; margin-bottom: -5px;
.checkbox-material { .checkbox-decorator {
left: -10px; left: -10px;
} }
} }

View File

@ -2,7 +2,7 @@
@mixin radio-color($color, $opacity){ @mixin radio-color($color, $opacity){
& ~ .check, & ~ .check,
& ~ .circle { & ~ .radio-decorator {
opacity: $opacity; opacity: $opacity;
} }
@ -10,7 +10,7 @@
background-color: $color; background-color: $color;
} }
& ~ .circle { & ~ .radio-decorator {
border-color: $color; border-color: $color;
} }
} }
@ -29,7 +29,7 @@
top: 2px; top: 2px;
transition-duration: 0.2s; transition-duration: 0.2s;
} }
.circle { .radio-decorator {
border: 2px solid $mdb-radio-color-off; border: 2px solid $mdb-radio-color-off;
height: 15px; height: 15px;
width: 15px; width: 15px;