diff --git a/Gruntfile.js b/Gruntfile.js index ec125f06..b4be5ed7 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -399,8 +399,8 @@ module.exports = function (grunt) { files: 'js/src/*.js', tasks: ['dist-js'] }, - docsjs: { - files: ['docs/assets/js/src/*.js'], + docsjs: { // watch both the source and docs js + files: ['js/src/*.js', 'docs/assets/js/src/*.js'], tasks: ['docs-js'] }, core: { diff --git a/docs/material-design/test.md b/docs/material-design/test.md index 3d80f990..7592fdfa 100644 --- a/docs/material-design/test.md +++ b/docs/material-design/test.md @@ -17,6 +17,7 @@ group: material-design {% endexample %} + ## With label-placeholder Perhaps this isn't worth doing, considering the context. we need to override the top calc to determine where this goes, or perhaps we should switch to a bottom calc for everything? {% example html %} diff --git a/js/src/autofill.js b/js/src/autofill.js index eba5a7a9..76d49e9b 100644 --- a/js/src/autofill.js +++ b/js/src/autofill.js @@ -1,3 +1,5 @@ +import Base from './base' + const Autofill = (($) => { /** @@ -17,20 +19,17 @@ const Autofill = (($) => { * Class Definition * ------------------------------------------------------------------------ */ - class Autofill { + class Autofill extends Base { constructor($element, config) { - this.$element = $element - this.config = $.extend(true, {}, Default, config) + super($element, $.extend(true, {}, Default, config)) this._watchLoading() this._attachEventHandlers() } dispose() { - $.removeData(this.$element, DATA_KEY) - this.$element = null - this.config = null + super.dispose(DATA_KEY) } // ------------------------------------------------------------------------ diff --git a/js/src/base.js b/js/src/base.js new file mode 100644 index 00000000..e957a594 --- /dev/null +++ b/js/src/base.js @@ -0,0 +1,95 @@ +import Util from './util' + +const Base = (($) => { + + 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}` + } + + const Default = {} + + /** + * ------------------------------------------------------------------------ + * Class Definition + * ------------------------------------------------------------------------ + */ + class 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 = {}) { + 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] + } + } + + dispose(dataKey) { + $.removeData(this.$element, dataKey) + this.$element = null + this.config = null + } + + // ------------------------------------------------------------------------ + // protected + + addFormGroupFocus() { + if (!this.$element.prop('disabled')) { + this.$mdbFormGroup.addClass(ClassName.IS_FOCUSED) + } + } + + removeFormGroupFocus() { + this.$mdbFormGroup.removeClass(ClassName.IS_FOCUSED) + } + + removeIsFilled() { + this.$mdbFormGroup.removeClass(ClassName.IS_FILLED) + } + + addIsFilled() { + this.$mdbFormGroup.addClass(ClassName.IS_FILLED) + } + + // 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 + } + + // ------------------------------------------------------------------------ + // private + + // ------------------------------------------------------------------------ + // static + + } + + return Base + +})(jQuery) + +export default Base diff --git a/js/src/baseInput.js b/js/src/baseInput.js index 028a07d9..2fae5a8e 100644 --- a/js/src/baseInput.js +++ b/js/src/baseInput.js @@ -1,3 +1,4 @@ +import Base from './base' import Util from './util' const BaseInput = (($) => { @@ -26,7 +27,7 @@ const BaseInput = (($) => { required: false }, mdbFormGroup: { - template: `<span class='mdb-form-group'></span>`, + template: `<span class='${ClassName.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 }, @@ -60,7 +61,7 @@ const BaseInput = (($) => { * Class Definition * ------------------------------------------------------------------------ */ - class BaseInput { + class BaseInput extends Base { /** * @@ -69,13 +70,7 @@ const BaseInput = (($) => { * @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] - } + super($element, $.extend(true, {}, Default, config), properties) // Enforce no overlap between components to prevent side effects this._rejectInvalidComponentMatches() @@ -106,10 +101,9 @@ const BaseInput = (($) => { } dispose(dataKey) { - $.removeData(this.$element, dataKey) - this.$element = null + super.dispose(dataKey) this.$mdbFormGroup = null - this.config = null + this.$formGroup = null } // ------------------------------------------------------------------------ @@ -163,16 +157,6 @@ const BaseInput = (($) => { }) } - 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) } @@ -181,14 +165,6 @@ const BaseInput = (($) => { 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() === '') } @@ -264,15 +240,6 @@ const BaseInput = (($) => { 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) diff --git a/js/src/bootstrapMaterialDesign.js b/js/src/bootstrapMaterialDesign.js index 3f679e70..89e773eb 100644 --- a/js/src/bootstrapMaterialDesign.js +++ b/js/src/bootstrapMaterialDesign.js @@ -37,6 +37,27 @@ const BootstrapMaterialDesign = (($) => { className: 'mdb-label-static' // default style of label to be used if not specified in the html markup } }, + autofill: { + selector: 'body' + }, + checkbox: { + selector: '.checkbox > label > input[type=checkbox]' + }, + checkboxInline: { + selector: 'label.checkbox-inline > input[type=checkbox]' + }, + collapseInline: { + selector: '.mdb-collapse-inline [data-toggle="collapse"]' + }, + file: { + selector: 'input[type=file]' + }, + radio: { + selector: '.radio > label > input[type=radio]' + }, + radioInline: { + selector: 'label.radio-inline > input[type=radio]' + }, ripples: { //selector: ['.btn:not(.btn-link):not(.ripple-none)'] // testing only selector: [ @@ -49,43 +70,26 @@ const BootstrapMaterialDesign = (($) => { '.ripple' // generic marker class to add ripple to elements ] }, - text: { - // omit inputs we have specialized components to handle - selector: [`input[type!='checkbox'][type!='radio'][type!='file']`] - }, - file: { - selector: 'input[type=file]' - }, - checkbox: { - selector: '.checkbox > label > input[type=checkbox]' - }, - checkboxInline: { - selector: 'label.checkbox-inline > input[type=checkbox]' - }, - radio: { - selector: '.radio > label > input[type=radio]' - }, - radioInline: { - selector: 'label.radio-inline > input[type=radio]' - }, select: { selector: ['select'] }, switch: { selector: '.switch > label > input[type=checkbox]' }, + text: { + // omit inputs we have specialized components to handle + selector: [`input[type!='checkbox'][type!='radio'][type!='file']`] + }, textarea: { selector: ['textarea'] }, - autofill: { - selector: 'body' - }, arrive: true, // create an ordered component list for instantiation instantiation: [ 'ripples', 'checkbox', 'checkboxInline', + 'collapseInline', 'file', 'radio', 'radioInline', diff --git a/js/src/checkbox.js b/js/src/checkbox.js index 84969414..05e4d21c 100644 --- a/js/src/checkbox.js +++ b/js/src/checkbox.js @@ -1,9 +1,9 @@ import BaseSelection from './baseSelection' -import Text from './text' -import File from './file' -import Radio from './radio' -import Textarea from './textarea' -import Select from './select' +//import Text from './text' +//import File from './file' +//import Radio from './radio' +//import Textarea from './textarea' +//import Select from './select' import Util from './util' const Checkbox = (($) => { @@ -30,9 +30,9 @@ const Checkbox = (($) => { class Checkbox extends BaseSelection { constructor($element, config, properties = {inputType: NAME, outerClass: NAME}) { - super($element, $.extend(true, { - invalidComponentMatches: [File, Radio, Text, Textarea, Select] - }, Default, config), properties) + super($element, $.extend(true, + //{invalidComponentMatches: [File, Radio, Text, Textarea, Select]}, + Default, config), properties) } dispose(dataKey = DATA_KEY) { diff --git a/js/src/collapseInline.js b/js/src/collapseInline.js new file mode 100644 index 00000000..3c6efb03 --- /dev/null +++ b/js/src/collapseInline.js @@ -0,0 +1,113 @@ +import Base from './base' +import Util from './util' + +const CollapseInline = (($) => { + + /** + * ------------------------------------------------------------------------ + * Constants + * ------------------------------------------------------------------------ + */ + const NAME = 'collapseInline' + const DATA_KEY = `mdb.${NAME}` + const JQUERY_NAME = `mdb${NAME.charAt(0).toUpperCase() + NAME.slice(1)}` + const JQUERY_NO_CONFLICT = $.fn[JQUERY_NAME] + + const Selector = { + ANY_INPUT: 'input, select, textarea' + } + + const ClassName = { + IN: 'in', + COLLAPSE: 'collapse', + COLLAPSING: 'collapsing', + COLLAPSED: 'collapsed', + WIDTH: 'width' + } + const Default = {} + + /** + * ------------------------------------------------------------------------ + * Class Definition + * ------------------------------------------------------------------------ + */ + class CollapseInline extends Base { + + // $element is expected to be the trigger + // i.e. <button class="btn mdb-btn-icon" for="search" data-toggle="collapse" data-target="#search-field" aria-expanded="false" aria-controls="search-field"> + constructor($element, config) { + super($element, $.extend(true, {}, Default, config)) + this.$mdbFormGroup = this.findMdbFormGroup(true) + + let collapseSelector = $element.data('target') + this.$collapse = $(collapseSelector) + + Util.assert($element, this.$collapse.length === 0, `Cannot find collapse target for ${Util.describe($element)}`) + Util.assert(this.$collapse, !this.$collapse.hasClass(ClassName.COLLAPSE), `${Util.describe(this.$collapse)} is expected to have the '${ClassName.COLLAPSE}' class. It is being targeted by ${Util.describe($element)}`) + + // find the first input for focusing + let $inputs = this.$mdbFormGroup.find(Selector.ANY_INPUT) + if ($inputs.length > 0) { + this.$input = $inputs.first() + } + + // automatically add the marker class to collapse width instead of height - nice convenience because it is easily forgotten + if (!this.$collapse.hasClass(ClassName.WIDTH)) { + this.$collapse.addClass(ClassName.WIDTH) + } + + if (this.$input) { + // add a listener to set focus + this.$collapse.on('shown.bs.collapse', () => { + this.$input.focus() + }) + + // add a listener to collapse field + this.$input.blur(() => { + this.$collapse.collapse('hide') + }) + } + } + + dispose() { + super.dispose(DATA_KEY) + this.$mdbFormGroup = null + this.$collapse = null + this.$input = null + } + + // ------------------------------------------------------------------------ + // private + + // ------------------------------------------------------------------------ + // static + static _jQueryInterface(config) { + return this.each(function () { + let $element = $(this) + let data = $element.data(DATA_KEY) + + if (!data) { + data = new CollapseInline($element, config) + $element.data(DATA_KEY, data) + } + }) + } + } + + /** + * ------------------------------------------------------------------------ + * jQuery + * ------------------------------------------------------------------------ + */ + $.fn[JQUERY_NAME] = CollapseInline._jQueryInterface + $.fn[JQUERY_NAME].Constructor = CollapseInline + $.fn[JQUERY_NAME].noConflict = () => { + $.fn[JQUERY_NAME] = JQUERY_NO_CONFLICT + return CollapseInline._jQueryInterface + } + + return CollapseInline + +})(jQuery) + +export default CollapseInline diff --git a/js/src/file.js b/js/src/file.js index 6316e6e1..cfd65b05 100644 --- a/js/src/file.js +++ b/js/src/file.js @@ -1,10 +1,10 @@ import BaseInput from './baseInput' -import Checkbox from './checkbox' -import Radio from './radio' -import Switch from './switch' -import Text from './text' -import Textarea from './textarea' -import Select from './select' +//import Checkbox from './checkbox' +//import Radio from './radio' +//import Switch from './switch' +//import Text from './text' +//import Textarea from './textarea' +//import Select from './select' import Util from './util' const File = (($) => { @@ -38,7 +38,9 @@ const File = (($) => { class File extends BaseInput { constructor($element, config) { - super($element, $.extend(true, {invalidComponentMatches: [Checkbox, Radio, Text, Textarea, Select, Switch]}, Default, config)) + super($element, $.extend(true, + //{invalidComponentMatches: [Checkbox, Radio, Text, Textarea, Select, Switch]}, + Default, config)) this.$mdbFormGroup.addClass(ClassName.IS_FILE) } diff --git a/js/src/index.js b/js/src/index.js index 701a276d..0efbcbe6 100644 --- a/js/src/index.js +++ b/js/src/index.js @@ -10,20 +10,19 @@ /* eslint-disable no-unused-vars */ import '../../node_modules/babel-polyfill/dist/polyfill' -import BaseInput from './baseInput' -import BaseFormControl from './baseFormControl' -import BaseSelection from './baseSelection' -import Util from './util' -import Ripples from './ripples' -import Autofill from './autofill' -import Text from './text' -import Textarea from './textarea' -import Select from './select' +// invalidComponentMatches is currently disabled due to https://github.com/rollup/rollup/issues/428#issuecomment-170066452 import Checkbox from './checkbox' import CheckboxInline from './checkboxInline' -import Switch from './switch' +import CollapseInline from './collapseInline' +import File from './file' import Radio from './radio' import RadioInline from './radioInline' -import File from './file' +import Select from './select' +import Switch from './switch' +import Text from './text' +import Textarea from './textarea' + +import Ripples from './ripples' +import Autofill from './autofill' import BootstrapMaterialDesign from './bootstrapMaterialDesign' /* eslint-enable no-unused-vars */ diff --git a/js/src/radio.js b/js/src/radio.js index 92cdfb21..b6c7c9ac 100644 --- a/js/src/radio.js +++ b/js/src/radio.js @@ -1,8 +1,8 @@ import BaseSelection from './baseSelection' -import Text from './text' -import File from './file' -import Checkbox from './checkbox' -import Switch from './switch' +//import Text from './text' +//import File from './file' +//import Checkbox from './checkbox' +//import Switch from './switch' import Util from './util' const Radio = (($) => { @@ -29,9 +29,9 @@ const Radio = (($) => { class Radio extends BaseSelection { constructor($element, config, properties = {inputType: NAME, outerClass: NAME}) { - super($element, $.extend(true, { - invalidComponentMatches: [Checkbox, File, Switch, Text] - }, Default, config), properties) + super($element, $.extend(true, + //{invalidComponentMatches: [Checkbox, File, Switch, Text]}, + Default, config), properties) } dispose(dataKey = DATA_KEY) { diff --git a/js/src/select.js b/js/src/select.js index 6bc60b66..cf7a1447 100644 --- a/js/src/select.js +++ b/js/src/select.js @@ -1,10 +1,10 @@ import BaseFormControl from './baseFormControl' -import Checkbox from './checkbox' -import File from './file' -import Radio from './radio' -import Switch from './switch' -import Text from './text' -import Textarea from './textarea' +//import Checkbox from './checkbox' +//import File from './file' +//import Radio from './radio' +//import Switch from './switch' +//import Text from './text' +//import Textarea from './textarea' import Util from './util' const Select = (($) => { @@ -31,7 +31,9 @@ const Select = (($) => { class Select extends BaseFormControl { constructor($element, config) { - super($element, $.extend(true, {invalidComponentMatches: [Checkbox, File, Radio, Switch, Text, Textarea]}, Default, config)) + super($element, $.extend(true, + //{invalidComponentMatches: [Checkbox, File, Radio, Switch, Text, Textarea]}, + Default, config)) // floating labels will cover the options, so trigger them to be above (if used) this.addIsFilled() diff --git a/js/src/text.js b/js/src/text.js index 38f55c74..ca58175f 100644 --- a/js/src/text.js +++ b/js/src/text.js @@ -1,10 +1,10 @@ import BaseFormControl from './baseFormControl' -import Checkbox from './checkbox' -import File from './file' -import Radio from './radio' -import Switch from './switch' -import Textarea from './textarea' -import Select from './select' +//import Checkbox from './checkbox' +//import File from './file' +//import Radio from './radio' +//import Switch from './switch' +//import Textarea from './textarea' +//import Select from './select' import Util from './util' const Text = (($) => { @@ -29,7 +29,9 @@ const Text = (($) => { class Text extends BaseFormControl { constructor($element, config) { - super($element, $.extend(true, {invalidComponentMatches: [Checkbox, File, Radio, Switch, Select, Textarea]}, Default, config)) + super($element, $.extend(true, + //{invalidComponentMatches: [Checkbox, File, Radio, Switch, Select, Textarea]}, + Default, config)) } dispose(dataKey = DATA_KEY) { diff --git a/js/src/textarea.js b/js/src/textarea.js index 9093df6b..205483b1 100644 --- a/js/src/textarea.js +++ b/js/src/textarea.js @@ -1,10 +1,10 @@ import BaseFormControl from './baseFormControl' -import Checkbox from './checkbox' -import File from './file' -import Radio from './radio' -import Switch from './switch' -import Text from './text' -import Select from './select' +//import Checkbox from './checkbox' +//import File from './file' +//import Radio from './radio' +//import Switch from './switch' +//import Text from './text' +//import Select from './select' import Util from './util' const Textarea = (($) => { @@ -29,7 +29,9 @@ const Textarea = (($) => { class Textarea extends BaseFormControl { constructor($element, config) { - super($element, $.extend(true, {invalidComponentMatches: [Checkbox, File, Radio, Text, Select, Switch]}, Default, config)) + super($element, $.extend(true, + //{invalidComponentMatches: [Checkbox, File, Radio, Text, Select, Switch]}, + Default, config)) } dispose() {