diff --git a/Gruntfile.js b/Gruntfile.js index 3d65a720..55f59163 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -107,8 +107,8 @@ module.exports = function (grunt) { 'js/dist/checkbox.js' : 'js/src/checkbox.js', 'js/dist/togglebutton.js' : 'js/src/togglebutton.js', 'js/dist/radio.js' : 'js/src/radio.js', - 'js/dist/fileinput.js' : 'js/src/fileinput.js', - //'js/dist/scrollspy.js' : 'js/src/scrollspy.js', + 'js/dist/fileinput.js' : 'js/src/fileInput.js', + 'js/dist/bootstrapMaterialDesign.js' : 'js/src/bootstrapMaterialDesign.js', //'js/dist/tab.js' : 'js/src/tab.js', //'js/dist/tooltip.js' : 'js/src/tooltip.js', //'js/dist/popover.js' : 'js/src/popover.js' @@ -134,9 +134,8 @@ module.exports = function (grunt) { 'dist/js/umd/checkbox.js' : 'js/src/checkbox.js', 'dist/js/umd/togglebutton.js' : 'js/src/togglebutton.js', 'dist/js/umd/radio.js' : 'js/src/radio.js', - 'dist/js/umd/fileinput.js' : 'js/src/fileinput.js', - //'dist/js/umd/modal.js' : 'js/src/modal.js', - //'dist/js/umd/scrollspy.js' : 'js/src/scrollspy.js', + 'dist/js/umd/fileinput.js' : 'js/src/fileInput.js', + 'dist/js/umd/bootstrapMaterialDesign.js' : 'js/src/bootstrapMaterialDesign.js', //'dist/js/umd/tab.js' : 'js/src/tab.js', //'dist/js/umd/tooltip.js' : 'js/src/tooltip.js', //'dist/js/umd/popover.js' : 'js/src/popover.js' @@ -197,8 +196,8 @@ module.exports = function (grunt) { 'js/src/checkbox.js', 'js/src/togglebutton.js', 'js/src/radio.js', - 'js/src/fileinput.js', - //'js/src/scrollspy.js', + 'js/src/fileInput.js', + 'js/src/bootstrapMaterialDesign.js', //'js/src/tab.js', //'js/src/tooltip.js', //'js/src/popover.js' diff --git a/js/.eslintrc b/js/.eslintrc index e334c8b1..05e50cdc 100644 --- a/js/.eslintrc +++ b/js/.eslintrc @@ -155,6 +155,7 @@ "space-in-parens": 2, "space-return-throw-case": 2, "space-unary-ops": 2, + "quotes": [2, "single", "avoid-escape"], // es6 "arrow-parens": 2, diff --git a/js/src/autofill.js b/js/src/autofill.js index faadc77f..53c80375 100644 --- a/js/src/autofill.js +++ b/js/src/autofill.js @@ -45,10 +45,10 @@ const Autofill = (($) => { // This part of code will detect autofill when the page is loading (username and password inputs for example) _onLoading() { setInterval(() => { - $("input[type!=checkbox]").each((index, element) => { + $('input[type!=checkbox]').each((index, element) => { let $element = $(element) - if ($element.val() && $element.val() !== $element.attr("value")) { - $element.triggerStart("change") + if ($element.val() && $element.val() !== $element.attr('value')) { + $element.triggerStart('change') } }) }, 100) @@ -59,18 +59,18 @@ const Autofill = (($) => { // (because user can select from the autofill dropdown only when the input has focus) let focused = null $(document) - .on("focus", "input", (event) => { - let $inputs = $(event.currentTarget).closest("form").find("input").not("[type=file]") + .on('focus', 'input', (event) => { + let $inputs = $(event.currentTarget).closest('form').find('input').not('[type=file]') focused = setInterval(() => { $inputs.each((index, element) => { let $element = $(element) - if ($element.val() !== $element.attr("value")) { - $element.triggerStart("change") + if ($element.val() !== $element.attr('value')) { + $element.triggerStart('change') } }) }, 100) }) - .on("blur", ".form-group input", () => { + .on('blur', '.form-group input', () => { clearInterval(focused) }) } diff --git a/js/src/bootstrapMaterialDesign.js b/js/src/bootstrapMaterialDesign.js new file mode 100644 index 00000000..f55d50fb --- /dev/null +++ b/js/src/bootstrapMaterialDesign.js @@ -0,0 +1,127 @@ +//import Util from './util' + +/** + * $.bootstrapMaterialDesign(config) is a macro class to configure the components generally + * used in Material Design for Bootstrap. You may pass overrides to the configurations + * which will be passed into each component, or you may omit use of this class and + * configure each component separately. + * + * 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, + * togglebutton, fileInput. + * + */ +const BootstrapMaterialDesign = (($) => { + + /** + * ------------------------------------------------------------------------ + * Constants + * ------------------------------------------------------------------------ + */ + const NAME = 'bootstrapMaterialDesign' + const DATA_KEY = `mdb.${NAME}` + const JQUERY_NO_CONFLICT = $.fn[NAME] + + /** + * + * Default configuration for each component. + * - selector: may be a string or an array. Any array will be joined with a comma to generate the selector + * - filter: selector to individually filter elements sent to components e.g. ripple defaults `:not(.ripple-none)` + * - disable any component by defining it as false with an override. e.g. $.bootstrapMaterialDesign({ input: false }) + */ + const Default = { + ripples: { + selector: [ + '.btn:not(.btn-link)', + '.card-image', + '.navbar a', + '.dropdown-menu a', + '.nav-tabs a', + '.pagination li:not(.active):not(.disabled) a', + '.ripple' // generic marker class to add ripple to elements + ], + filter: ':not(.ripple-none)' + }, + input: { + selector: [ + 'input.form-control', + 'textarea.form-control', + 'select.form-control' + ] + }, + checkbox: { + selector: '.checkbox > label > input[type=checkbox]' + }, + togglebutton: { + selector: '.togglebutton > label > input[type=checkbox]' + }, + radio: { + selector: '.radio > label > input[type=radio]' + }, + fileInput: { + selector: 'input[type=file]' + }, + autofill: true, + arrive: true + } + + /** + * ------------------------------------------------------------------------ + * Class Definition + * ------------------------------------------------------------------------ + */ + class BootstrapMaterialDesign { + + constructor(element, config) { + this.element = element + this.config = $.extend({}, Default, config) + + // create an ordered component list for instantiation + } + + dispose() { + $.removeData(this.element, DATA_KEY) + this.element = null + this.config = null + } + + // ------------------------------------------------------------------------ + // private + + _bar(element) { + let x = 1 + return x + } + + // ------------------------------------------------------------------------ + // static + static _jQueryInterface(config) { + return this.each(function () { + let $element = $(this) + let data = $element.data(DATA_KEY) + + if (!data) { + data = new BootstrapMaterialDesign(this, config) + $element.data(DATA_KEY, data) + } + }) + } + } + + /** + * ------------------------------------------------------------------------ + * jQuery + * ------------------------------------------------------------------------ + */ + $.fn[NAME] = BootstrapMaterialDesign._jQueryInterface + $.fn[NAME].Constructor = BootstrapMaterialDesign + $.fn[NAME].noConflict = () => { + $.fn[NAME] = JQUERY_NO_CONFLICT + return BootstrapMaterialDesign._jQueryInterface + } + + return BootstrapMaterialDesign + +})(jQuery) + +export default BootstrapMaterialDesign diff --git a/js/src/input.js b/js/src/input.js index c435a326..5bc8b881 100644 --- a/js/src/input.js +++ b/js/src/input.js @@ -15,13 +15,13 @@ const Input = (($) => { convertInputSizeVariations: true, template: ``, formGroup: { - template: `"
` + template: `
` } } const InputSizeConversions = { - "input-lg": "form-group-lg", - "input-sm": "form-group-sm" + 'input-lg': 'form-group-lg', + 'input-sm': 'form-group-sm' } const ClassName = { @@ -80,9 +80,9 @@ const Input = (($) => { } }) .on('keyup change', (event) => { - let isValid = (typeof this.element[0].checkValidity === "undefined" || this.element[0].checkValidity()) + let isValid = (typeof this.element[0].checkValidity === 'undefined' || this.element[0].checkValidity()) - if (this.element.val() === "" && isValid) { + if (this.element.val() === '' && isValid) { this._addIsEmpty() } else { this._removeIsEmpty() @@ -139,7 +139,7 @@ const Input = (($) => { } _isEmpty() { - return (this.element.val() === null || this.element.val() === undefined || this.element.val() === "") + return (this.element.val() === null || this.element.val() === undefined || this.element.val() === '') } _convertInputSizeVariations() { diff --git a/js/src/old/material.js b/js/src/old/material.js index 3983dbb6..00a194f2 100644 --- a/js/src/old/material.js +++ b/js/src/old/material.js @@ -30,28 +30,28 @@ $.material = { "options": { // These options set what will be started by $.material.init() - "input": true, - "ripples": true, - "checkbox": true, - "togglebutton": true, - "radio": true, - "arrive": true, - "autofill": false, + //"input": true, + //"ripples": true, + //"checkbox": true, + //"togglebutton": true, + //"radio": true, + //"arrive": true, + //"autofill": false, - "withRipples": [ - ".btn:not(.btn-link)", - ".card-image", - ".navbar a:not(.ripple-none)", - ".dropdown-menu a", - ".nav-tabs a:not(.ripple-none)", - ".withripple", - ".pagination li:not(.active):not(.disabled) a:not(.ripple-none)" - ].join(","), - "inputElements": "input.form-control, textarea.form-control, select.form-control", - "checkboxElements": ".checkbox > label > input[type=checkbox]", - "togglebuttonElements": ".togglebutton > label > input[type=checkbox]", - "radioElements": ".radio > label > input[type=radio]" , - "fileInputElements": 'input[type=file]' + //"withRipples": [ + // ".btn:not(.btn-link)", + // ".card-image", + // ".navbar a:not(.ripple-none)", + // ".dropdown-menu a", + // ".nav-tabs a:not(.ripple-none)", + // ".withripple", + // ".pagination li:not(.active):not(.disabled) a:not(.ripple-none)" + //].join(","), + //"inputElements": "input.form-control, textarea.form-control, select.form-control", + //"checkboxElements": ".checkbox > label > input[type=checkbox]", + //"togglebuttonElements": ".togglebutton > label > input[type=checkbox]", + //"radioElements": ".radio > label > input[type=radio]" , + //"fileInputElements": 'input[type=file]' }, //"checkbox": function(selector) { // // Add fake-checkbox to material checkboxes diff --git a/js/src/ripples.js b/js/src/ripples.js index 5808b94d..954139bc 100644 --- a/js/src/ripples.js +++ b/js/src/ripples.js @@ -11,14 +11,29 @@ const Ripples = (($) => { const DATA_KEY = `mdb.${NAME}` const JQUERY_NO_CONFLICT = $.fn[NAME] + const ClassName = { + CONTAINER: 'ripple-container', + DECORATOR: 'ripple-decorator' + } + + const Selector = { + CONTAINER: `.${ClassName.CONTAINER}`, + DECORATOR: `.${ClassName.DECORATOR}` //, + } + + const Default = { - containerSelector: '.ripple-container', - rippleSelector: 'div.ripple', - containerTemplate: `
`, - rippleTemplate: `
`, + container: { + template: `
` + }, + decorator: { + template: `${ClassName.DECORATOR}'>` + }, + trigger: { + start: 'mousedown touchstart', + end: 'mouseup mouseleave touchend' + }, touchUserAgentRegex: /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i, - triggerStart: 'mousedown touchstart', - triggerEnd: 'mouseup mouseleave touchend', duration: 500 } @@ -41,7 +56,7 @@ const Ripples = (($) => { $.removeData(this.element, DATA_KEY) this.element = null this.containerElement = null - this.rippleElement = null + this.decoratorElement = null this.config = null } @@ -51,7 +66,7 @@ const Ripples = (($) => { _onStartRipple(event) { // Verify if the user is just touching on a device and return if so - if (this.isTouch() && event.type === "mousedown") { + if (this.isTouch() && event.type === 'mousedown') { return } @@ -68,10 +83,10 @@ const Ripples = (($) => { } // set the location and color each time (even if element is cached) - this.rippleElement.css({ - "left": relX, - "top": relY, - "background-color": this._getRipplesColor() + this.decoratorElement.css({ + 'left': relX, + 'top': relY, + 'background-color': this._getRipplesColor() }) // Make sure the ripple has the styles applied (ugly hack but it works) @@ -80,16 +95,16 @@ const Ripples = (($) => { // Turn on the ripple animation this.rippleOn() - // Call the rippleEnd function when the transition "on" ends + // Call the rippleEnd function when the transition 'on' ends setTimeout(() => { this.rippleEnd() }, this.config.duration) // Detect when the user leaves the element (attach only when necessary for performance) this.element.on(this.config.triggerEnd, () => { - this.rippleElement.data("mousedown", "off") + this.decoratorElement.data('mousedown', 'off') - if (this.rippleElement.data("animating") === "off") { + if (this.decoratorElement.data('animating') === 'off') { this.rippleOut() } }) @@ -97,18 +112,18 @@ const Ripples = (($) => { _findOrCreateContainer() { if (!this.containerElement || !this.containerElement.length > 0) { - this.element.append(this.config.containerTemplate) - this.containerElement = this.element.find(this.config.containerSelector) + this.element.append(this.config.container.template) + this.containerElement = this.element.find(Selector.CONTAINER) } // always add the rippleElement, it is always removed - this.containerElement.append(this.config.rippleTemplate) - this.rippleElement = this.containerElement.find(this.config.rippleSelector) + this.containerElement.append(this.config.element.template) + this.decoratorElement = this.containerElement.find(Selector.DECORATOR) } // Make sure the ripple has the styles applied (ugly hack but it works) _forceStyleApplication() { - return window.getComputedStyle(this.rippleElement[0]).opacity + return window.getComputedStyle(this.decoratorElement[0]).opacity } /** @@ -169,7 +184,7 @@ const Ripples = (($) => { * Get the ripple color */ _getRipplesColor() { - let color = this.element.data("ripple-color") ? this.element.data("ripple-color") : window.getComputedStyle(this.element[0]).color + let color = this.element.data('ripple-color') ? this.element.data('ripple-color') : window.getComputedStyle(this.element[0]).color return color } @@ -184,10 +199,10 @@ const Ripples = (($) => { * End the animation of the ripple */ rippleEnd() { - this.rippleElement.data("animating", "off") + this.decoratorElement.data('animating', 'off') - if (this.rippleElement.data("mousedown") === "off") { - this.rippleOut(this.rippleElement) + if (this.decoratorElement.data('mousedown') === 'off') { + this.rippleOut(this.decoratorElement) } } @@ -195,19 +210,19 @@ const Ripples = (($) => { * Turn off the ripple effect */ rippleOut() { - this.rippleElement.off() + this.decoratorElement.off() if (Util.transitionEndSupported()) { - this.rippleElement.addClass("ripple-out") + this.decoratorElement.addClass('ripple-out') } else { - this.rippleElement.animate({ "opacity": 0 }, 100, () => { - this.rippleElement.triggerStart("transitionend") + this.decoratorElement.animate({ 'opacity': 0 }, 100, () => { + this.decoratorElement.triggerStart('transitionend') }) } - this.rippleElement.on(Util.transitionEndSelector(), () => { - this.rippleElement.remove() - this.rippleElement = null + this.decoratorElement.on(Util.transitionEndSelector(), () => { + this.decoratorElement.remove() + this.decoratorElement = null }) } @@ -218,25 +233,25 @@ const Ripples = (($) => { let size = this._getNewSize() if (Util.transitionEndSupported()) { - this.rippleElement + this.decoratorElement .css({ - "-ms-transform": `scale(${size})`, - "-moz-transform": `scale(${size})`, - "-webkit-transform": `scale(${size})`, - "transform": `scale(${size})` + '-ms-transform': `scale(${size})`, + '-moz-transform': `scale(${size})`, + '-webkit-transform': `scale(${size})`, + 'transform': `scale(${size})` }) - .addClass("ripple-on") - .data("animating", "on") - .data("mousedown", "on") + .addClass('ripple-on') + .data('animating', 'on') + .data('mousedown', 'on') } else { - this.rippleElement.animate({ - "width": Math.max(this.element.outerWidth(), this.element.outerHeight()) * 2, - "height": Math.max(this.element.outerWidth(), this.element.outerHeight()) * 2, - "margin-left": Math.max(this.element.outerWidth(), this.element.outerHeight()) * (-1), - "margin-top": Math.max(this.element.outerWidth(), this.element.outerHeight()) * (-1), - "opacity": 0.2 + this.decoratorElement.animate({ + 'width': Math.max(this.element.outerWidth(), this.element.outerHeight()) * 2, + 'height': Math.max(this.element.outerWidth(), this.element.outerHeight()) * 2, + 'margin-left': Math.max(this.element.outerWidth(), this.element.outerHeight()) * (-1), + 'margin-top': Math.max(this.element.outerWidth(), this.element.outerHeight()) * (-1), + 'opacity': 0.2 }, this.config.duration, () => { - this.rippleElement.triggerStart("transitionend") + this.decoratorElement.triggerStart('transitionend') }) } } @@ -245,7 +260,7 @@ const Ripples = (($) => { * Get the new size based on the element height/width and the ripple width */ _getNewSize() { - return (Math.max(this.element.outerWidth(), this.element.outerHeight()) / this.rippleElement.outerWidth()) * 2.5 + return (Math.max(this.element.outerWidth(), this.element.outerHeight()) / this.decoratorElement.outerWidth()) * 2.5 } // ------------------------------------------------------------------------ diff --git a/js/src/util.js b/js/src/util.js index 16b3bcd8..29ea6c78 100644 --- a/js/src/util.js +++ b/js/src/util.js @@ -7,7 +7,7 @@ const Util = (($) => { */ let transitionEnd = false - let transitionEndSelector = "" + let transitionEndSelector = '' const TransitionEndEvent = { WebkitTransition: 'webkitTransitionEnd', @@ -67,9 +67,9 @@ const Util = (($) => { }, isChar(event) { - if (typeof event.which === "undefined") { + if (typeof event.which === 'undefined') { return true - } else if (typeof event.which === "number" && event.which > 0) { + } else if (typeof event.which === 'number' && event.which > 0) { return !event.ctrlKey && !event.metaKey && !event.altKey && event.which !== 8 && event.which !== 9 } return false diff --git a/scss/includes/ripples.scss b/scss/includes/ripples.scss index 7555ff71..7d78123f 100644 --- a/scss/includes/ripples.scss +++ b/scss/includes/ripples.scss @@ -1,20 +1,21 @@ - -.withripple { - position: relative; -} -.ripple-container { - position: absolute; - top: 0; - left: 0; - z-index: 1; - width: 100%; - height: 100%; - overflow: hidden; - border-radius: inherit; - pointer-events: none; -} +// marker class (used as a selector) to add ripple to something .ripple { + position: relative; +} + +.ripple-container { + position: absolute; + top: 0; + left: 0; + z-index: 1; + width: 100%; + height: 100%; + overflow: hidden; + border-radius: inherit; + pointer-events: none; + + .ripple-decorator { position: absolute; width: 20px; height: 20px; @@ -22,17 +23,20 @@ margin-top: -10px; border-radius: 100%; background-color: #000; // fallback color - background-color: rgba(0,0,0,0.05); + background-color: rgba(0, 0, 0, 0.05); transform: scale(1); transform-origin: 50%; opacity: 0; pointer-events: none; -} -.ripple.ripple-on { - transition: opacity 0.15s ease-in 0s, transform 0.5s cubic-bezier(0.4, 0, 0.2, 1) 0.1s; - opacity: 0.1; -} -.ripple.ripple-out { - transition: opacity 0.1s linear 0s !important; - opacity: 0; + + &.ripple-on { + transition: opacity 0.15s ease-in 0s, transform 0.5s cubic-bezier(0.4, 0, 0.2, 1) 0.1s; + opacity: 0.1; + } + + &.ripple-out { + transition: opacity 0.1s linear 0s !important; + opacity: 0; + } + } }