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'
},
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/ripples.js': 'js/src/ripples.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/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/fileInput.js': 'js/src/fileInput.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',
// 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/ripples.js': 'js/src/ripples.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/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/fileInput.js': 'js/src/fileInput.js',
'docs/dist/js/babel/bootstrapMaterialDesign.js': 'js/src/bootstrapMaterialDesign.js',
@ -145,12 +149,14 @@ module.exports = function (grunt) {
modules: 'umd'
},
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/ripples.js': 'js/src/ripples.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/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/fileInput.js': 'js/src/fileInput.js',
'dist/js/umd/bootstrapMaterialDesign.js': 'js/src/bootstrapMaterialDesign.js',
@ -205,6 +211,8 @@ module.exports = function (grunt) {
},
bootstrap: {
src: [
'dist/js/babel/baseInput.js',
'dist/js/babel/baseToggle.js',
'dist/js/babel/util.js',
'dist/js/babel/ripples.js',
'dist/js/babel/autofill.js',

View File

@ -10,14 +10,15 @@
"coreJs": [
"../dist/js/babel/baseInput.js",
"../dist/js/babel/baseToggle.js",
"../dist/js/babel/autofill.js",
"../dist/js/babel/bootstrapMaterialDesign.js",
"../dist/js/babel/checkbox.js",
"../dist/js/babel/fileInput.js",
"../dist/js/babel/input.js",
"../dist/js/babel/textInput.js",
"../dist/js/babel/radio.js",
"../dist/js/babel/ripples.js",
"../dist/js/babel/togglebutton.js",
"../dist/js/babel/switch.js",
"../dist/js/babel/util.js"
]
}

View File

@ -36,12 +36,12 @@ const BaseInput = (($) => {
// 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()
// Enforce required classes for a consistent rendering
this._rejectWithoutRequiredClasses()
if(this.config.formGroup.autoCreate) {
// Will create form-group if necessary
this.autoCreateFormGroup()
@ -76,12 +76,12 @@ const BaseInput = (($) => {
// implement
}
addFormGroupFocus(formGroup) {
formGroup.addClass(ClassName.IS_FOCUSED)
addFormGroupFocus() {
this.$formGroup.addClass(ClassName.IS_FOCUSED)
}
removeFormGroupFocus(formGroup) {
formGroup.removeClass(ClassName.IS_FOCUSED)
removeFormGroupFocus() {
this.$formGroup.removeClass(ClassName.IS_FOCUSED)
}
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 FileInput from './fileInput'
import Radio from './radio'
@ -17,11 +17,12 @@ const Checkbox = (($) => {
const JQUERY_NO_CONFLICT = $.fn[NAME]
const Default = {
template: `<span class='checkbox-material'><span class='check'></span></span>`,
formGroup: {
autoCreate: true
},
invalidComponentMatches: [TextInput, FileInput, Radio, Switch]
template: `<span class='checkbox-decorator'><span class='check'></span></span>`,
invalidComponentMatches: [FileInput, Radio, TextInput]
}
const Selector = {
LABEL: 'label'
}
/**
@ -29,15 +30,10 @@ const Checkbox = (($) => {
* Class Definition
* ------------------------------------------------------------------------
*/
class Checkbox extends BaseInput {
class Checkbox extends BaseToggle {
constructor(element, config) {
super(element, Default, config)
this.$element.after(this.config.template)
}
dispose() {
super.dispose(DATA_KEY)
constructor(element, config, inputType = NAME, outerClass = NAME) {
super(element, $.extend({}, Default, config), inputType, outerClass)
}
static matches($element) {
@ -55,37 +51,9 @@ const Checkbox = (($) => {
// ------------------------------------------------------------------------
// 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
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

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'
// FileInput decorator, to be called after Input
@ -12,35 +17,36 @@ const FileInput = (($) => {
const DATA_KEY = `mdb.${NAME}`
const JQUERY_NO_CONFLICT = $.fn[NAME]
const Default = {}
const Default = {
formGroup: {
autoCreate: true
},
invalidComponentMatches: [Checkbox, Radio, Switch, TextInput]
}
const ClassName = {
IS_FILEINPUT: 'is-fileinput',
}
const Selector = {
FILENAMES: 'input.form-control[readonly]'
}
/**
* ------------------------------------------------------------------------
* Class Definition
* ------------------------------------------------------------------------
*/
class FileInput {
class FileInput extends BaseInput {
constructor(element, config) {
this.$element = $(element)
this.config = $.extend({}, Default, config)
this.$formGroup = Util.findFormGroup(this.$element)
super(element, Default, config)
this.$formGroup.addClass(ClassName.IS_FILEINPUT)
this._bindEventListeners()
}
dispose() {
$.removeData(this.$element, DATA_KEY)
this.$element = null
this.$formGroup = null
this.config = null
super.dispose(DATA_KEY)
}
static matches($element) {
@ -51,23 +57,27 @@ const FileInput = (($) => {
}
static rejectMatch(component, $element) {
if (this.matches($element)) {
let msg = `${component} component is invalid for type='file'.`
$.error(msg)
}
Util.assert(this.matches($element), `${component} component is invalid for type='file'.`)
}
// ------------------------------------------------------------------------
// private
_bindEventListeners() {
// protected
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
.on('focus', () => {
Util.addFormGroupFocus(this.$formGroup)
this.addFormGroupFocus()
})
.on('blur', () => {
Util.removeFormGroupFocus(this.$formGroup)
this.removeFormGroupFocus()
})
}
addChangeListener() {
// set the fileinput readonly field with the name of the file
this.$element.on('change', () => {
let value = ''
@ -80,10 +90,13 @@ const FileInput = (($) => {
} else {
this.addIsEmpty()
}
this.$formGroup.find('input.form-control[readonly]').val(value)
this.$formGroup.find(Selector.FILENAMES).val(value)
})
}
// ------------------------------------------------------------------------
// private
// ------------------------------------------------------------------------
// static
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
const Radio = (($) => {
@ -13,7 +18,8 @@ const Radio = (($) => {
const JQUERY_NO_CONFLICT = $.fn[NAME]
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,20 +27,26 @@ const Radio = (($) => {
* Class Definition
* ------------------------------------------------------------------------
*/
class Radio {
class Radio extends BaseToggle {
constructor(element, config) {
this.$element = $(element)
this.config = $.extend({}, Default, config)
this.$element.after(this.config.template)
super(element, $.extend({}, Default, config), NAME, NAME)
}
dispose() {
$.removeData(this.$element, DATA_KEY)
this.$element = null
this.config = null
static matches($element) {
// '.radio > label > input[type=radio]'
if ($element.attr('type') === 'radio') {
return true
}
return false
}
static rejectMatch(component, $element) {
Util.assert(this.matches($element), `${component} component is invalid for type='radio'.`)
}
// ------------------------------------------------------------------------
// protected
// ------------------------------------------------------------------------
// private

View File

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

View File

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

View File

@ -24,7 +24,7 @@
pointer-events: none;
}
.checkbox-material {
.checkbox-decorator {
vertical-align: middle;
position: relative;
top: 3px;
@ -75,19 +75,19 @@
input[type=checkbox] {
&:focus + .checkbox-material .check:after {
&:focus + .checkbox-decorator .check:after {
opacity: 0.2;
}
&:checked {
// FIXME: once working - combine further to reduce code
& + .checkbox-material .check {
& + .checkbox-decorator .check {
color: $mdb-checkbox-checked-color;
border-color: $mdb-checkbox-checked-color;
}
& + .checkbox-material .check:before {
& + .checkbox-decorator .check:before {
color: $mdb-checkbox-checked-color;
box-shadow: 0 0 0 10px,
10px -10px 0 10px,
@ -98,22 +98,22 @@
animation: checkbox-on $mdb-checkbox-animation-check forwards;
}
& + .checkbox-material:before {
& + .checkbox-decorator:before {
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
animation: rippleOn $mdb-checkbox-animation-ripple forwards; // Ripple effect on check
}
}
&:not(:checked) {
& + .checkbox-material:before {
& + .checkbox-decorator:before {
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
}
@ -123,12 +123,12 @@
// Style for disabled inputs
fieldset[disabled] &,
fieldset[disabled] & input[type=checkbox],
input[type=checkbox][disabled]:not(:checked) ~ .checkbox-material .check:before,
input[type=checkbox][disabled]:not(:checked) ~ .checkbox-material .check,
input[type=checkbox][disabled] + .circle {
input[type=checkbox][disabled]:not(:checked) ~ .checkbox-decorator .check:before,
input[type=checkbox][disabled]:not(:checked) ~ .checkbox-decorator .check,
input[type=checkbox][disabled] + .radio-decorator {
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;
transform: rotate(-45deg);
}

View File

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

View File

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