diff --git a/Gruntfile.js b/Gruntfile.js index 23ea2f8d..3d65a720 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -103,11 +103,11 @@ module.exports = function (grunt) { 'js/dist/util.js' : 'js/src/util.js', 'js/dist/ripples.js' : 'js/src/ripples.js', 'js/dist/autofill.js' : 'js/src/autofill.js', + 'js/dist/input.js' : 'js/src/input.js', '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/dropdown.js' : 'js/src/dropdown.js', - //'js/dist/modal.js' : 'js/src/modal.js', + 'js/dist/fileinput.js' : 'js/src/fileinput.js', //'js/dist/scrollspy.js' : 'js/src/scrollspy.js', //'js/dist/tab.js' : 'js/src/tab.js', //'js/dist/tooltip.js' : 'js/src/tooltip.js', @@ -130,11 +130,11 @@ module.exports = function (grunt) { '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/input.js', '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/collapse.js' : 'js/src/collapse.js', - //'dist/js/umd/dropdown.js' : 'js/src/dropdown.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/tab.js' : 'js/src/tab.js', @@ -193,11 +193,11 @@ module.exports = function (grunt) { 'js/src/util.js', 'js/src/ripples.js', 'js/src/autofill.js', + 'js/src/input.js', 'js/src/checkbox.js', 'js/src/togglebutton.js', 'js/src/radio.js', - //'js/src/dropdown.js', - //'js/src/modal.js', + 'js/src/fileinput.js', //'js/src/scrollspy.js', //'js/src/tab.js', //'js/src/tooltip.js', diff --git a/js/src/checkbox.js b/js/src/checkbox.js index 2a49f47c..f0779948 100644 --- a/js/src/checkbox.js +++ b/js/src/checkbox.js @@ -1,5 +1,6 @@ -//import Util from './util' +import Util from './util' +// Checkbox decorator, to be called after Input const Checkbox = (($) => { /** @@ -27,17 +28,32 @@ const Checkbox = (($) => { this.config = $.extend({}, Default, config) this.element.after(this.config.template) + this.formGroup = Util.findFormGroup(this.element) + + this._bindEventListeners() } dispose() { $.removeData(this.element, DATA_KEY) this.element = null + this.formGroup = null this.config = null } // ------------------------------------------------------------------------ // private + _bindEventListeners() { + // 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) + }) + this.element.change(() => { + this.element.blur() + }) + } // ------------------------------------------------------------------------ // static diff --git a/js/src/fileinput.js b/js/src/fileinput.js new file mode 100644 index 00000000..80f57a92 --- /dev/null +++ b/js/src/fileinput.js @@ -0,0 +1,112 @@ +import Util from './util' + +// FileInput decorator, to be called after Input +const FileInput = (($) => { + + /** + * ------------------------------------------------------------------------ + * Constants + * ------------------------------------------------------------------------ + */ + const NAME = 'fileInput' + const DATA_KEY = `mdb.${NAME}` + const JQUERY_NO_CONFLICT = $.fn[NAME] + + const Default = {} + + const ClassName = { + IS_FILEINPUT: 'is-fileinput', + IS_EMPTY: 'is-empty' + } + + + /** + * ------------------------------------------------------------------------ + * Class Definition + * ------------------------------------------------------------------------ + */ + class FileInput { + + constructor(element, config) { + this.element = element + this.config = $.extend({}, Default, config) + this.formGroup = Util.findFormGroup(this.element) + + this.formGroup.addClass(ClassName.IS_FILEINPUT) + + this._bindEventListeners() + } + + dispose() { + $.removeData(this.element, DATA_KEY) + this.element = null + this.formGroup = null + this.config = null + } + + // ------------------------------------------------------------------------ + // private + _bindEventListeners() { + this.formGroup.on('focus', () => { + Util.addFormGroupFocus(this.formGroup) + }) + .on('blur', () => { + Util.removeFormGroupFocus(this.formGroup) + }) + + // set the fileinput readonly field with the name of the file + this.element.on('change', () => { + let value = '' + $.each(this.element.files, (i, file) => { + value += `${file.name} , ` + }) + value = value.substring(0, value.length - 2) + if (value) { + this._removeIsEmpty() + } else { + this._addIsEmpty() + } + 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 _jQueryInterface(config) { + return this.each(function () { + let $element = $(this) + let data = $element.data(DATA_KEY) + + if (!data) { + data = new FileInput(this, config) + $element.data(DATA_KEY, data) + } + }) + } + } + + /** + * ------------------------------------------------------------------------ + * jQuery + * ------------------------------------------------------------------------ + */ + $.fn[NAME] = FileInput._jQueryInterface + $.fn[NAME].Constructor = FileInput + $.fn[NAME].noConflict = () => { + $.fn[NAME] = JQUERY_NO_CONFLICT + return FileInput._jQueryInterface + } + + return FileInput + +})(jQuery) + +export default FileInput diff --git a/js/src/input.js b/js/src/input.js new file mode 100644 index 00000000..c435a326 --- /dev/null +++ b/js/src/input.js @@ -0,0 +1,199 @@ +import Util from './util' + +const Input = (($) => { + + /** + * ------------------------------------------------------------------------ + * Constants + * ------------------------------------------------------------------------ + */ + const NAME = 'input' + const DATA_KEY = `mdb.${NAME}` + const JQUERY_NO_CONFLICT = $.fn[NAME] + + const Default = { + convertInputSizeVariations: true, + template: ``, + formGroup: { + template: `"
` + } + } + + const InputSizeConversions = { + "input-lg": "form-group-lg", + "input-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}` //, + } + + /** + * ------------------------------------------------------------------------ + * Class Definition + * ------------------------------------------------------------------------ + */ + class Input { + + constructor(element, config) { + this.element = element + this.config = $.extend({}, Default, config) + + // Requires form-group standard markup (will add it if necessary) + this.formGroup = this._findOrCreateFormGroup() + + this._convertInputSizeVariations() + + // Initially mark as empty + if (this._isEmpty()) { + this.formGroup.addClass(ClassName.IS_EMPTY) + } + + // Add marker div the end of the form-group + this.formGroup.append(this.config.template) + + this._bindEventListeners() + } + + dispose() { + $.removeData(this.element, DATA_KEY) + this.element = null + this.formGroup = null + this.config = null + } + + // ------------------------------------------------------------------------ + // private + + _bindEventListeners() { + + this.element + .on('keydown paste', (event) => { + if (Util.isChar(event)) { + this._removeIsEmpty() + } + }) + .on('keyup change', (event) => { + let isValid = (typeof this.element[0].checkValidity === "undefined" || this.element[0].checkValidity()) + + if (this.element.val() === "" && isValid) { + this._addIsEmpty() + } else { + this._removeIsEmpty() + } + + // Validation events do not bubble, so they must be attached directly to the input: http://jsfiddle.net/PEpRM/1/ + // Further, even the bind method is being caught, but since we are already calling #checkValidity here, just alter + // 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. + // BUT, I've left it here for backwards compatibility. + if (isValid) { + this._removeHasError() + } else { + 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) + } + + _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() { + if (!this.config.convertInputSizeVariations) { + return + } + + // Modification - Change input-sm/lg to form-group-sm/lg instead (preferred standard and simpler css/less variants) + for (let inputSize in InputSizeConversions) { + if (this.element.hasClass(inputSize)) { + this.element.removeClass(inputSize) + this.formGroup.addClass(InputSizeConversions[inputSize]) + } + } + } + + _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.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 _jQueryInterface(config) { + return this.each(function () { + let $element = $(this) + let data = $element.data(DATA_KEY) + + if (!data) { + data = new Input(this, config) + $element.data(DATA_KEY, data) + } + }) + } + } + + /** + * ------------------------------------------------------------------------ + * jQuery + * ------------------------------------------------------------------------ + */ + $.fn[NAME] = Input._jQueryInterface + $.fn[NAME].Constructor = Input + $.fn[NAME].noConflict = () => { + $.fn[NAME] = JQUERY_NO_CONFLICT + return Input._jQueryInterface + } + + return Input + +})(jQuery) + +export default Input diff --git a/js/src/old/material.js b/js/src/old/material.js index 78267ef5..3983dbb6 100644 --- a/js/src/old/material.js +++ b/js/src/old/material.js @@ -2,15 +2,15 @@ (function($) { // Selector to select only not already processed elements - $.expr[":"].notmdproc = function(obj){ - if ($(obj).data("mdproc")) { - return false; - } else { - return true; - } - }; - - //function _isChar(evt) { + //$.expr[":"].notmdproc = function(obj){ + // if ($(obj).data("mdproc")) { + // return false; + // } else { + // return true; + // } + //}; + // + //function isChar(evt) { // if (typeof evt.which == "undefined") { // return true; // } else if (typeof evt.which == "number" && evt.which > 0) { @@ -18,14 +18,14 @@ // } // return false; //} - - function _addFormGroupFocus(element){ - $(element).closest(".form-group").addClass("is-focused"); - } - - function _removeFormGroupFocus(element){ - $(element).closest(".form-group").removeClass("is-focused"); // remove class from form-group - } + // + //function addFormGroupFocus(formGroup){ + // formGroup.addClass("is-focused"); + //} + // + //function removeFormGroupFocus(formGroup){ + // formGroup.removeClass("is-focused"); // remove class from form-group + //} $.material = { "options": { @@ -50,7 +50,8 @@ "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]" + "radioElements": ".radio > label > input[type=radio]" , + "fileInputElements": 'input[type=file]' }, //"checkbox": function(selector) { // // Add fake-checkbox to material checkboxes @@ -72,133 +73,133 @@ // .data("mdproc", true) // .after(""); //}, - "input": function(selector) { - $((selector) ? selector : this.options.inputElements) - .filter(":notmdproc") - .data("mdproc", true) - .each( function() { - var $input = $(this); - - // Requires form-group standard markup (will add it if necessary) - var $formGroup = $input.closest(".form-group"); // note that form-group may be grandparent in the case of an input-group - if($formGroup.length === 0){ - $input.wrap("
"); - $formGroup = $input.closest(".form-group"); // find node after attached (otherwise additional attachments don't work) - } - - // Modification - Change input-sm/lg to form-group-sm/lg instead (preferred standard and simpler css/less variants) - var legacySizes = { - "input-lg": "form-group-lg", - "input-sm": "form-group-sm" - }; - $.each( legacySizes, function( legacySize, standardSize ) { - if ($input.hasClass(legacySize)) { - $input.removeClass(legacySize); - $formGroup.addClass(standardSize); - } - }); // TODO: determine if we want to keep meddling this much. - - // Set as empty if is empty (damn I must improve this...) - if ($input.val() === null || $input.val() == "undefined" || $input.val() === "") { - $formGroup.addClass("is-empty"); - } - - // Add at the end of the form-group - $formGroup.append(""); - - // Support for file input - if ($formGroup.find("input[type=file]").length > 0) { - $formGroup.addClass("is-fileinput"); - } - }); - }, - "attachInputEventHandlers": function() { - - // checkboxes didn't appear to bubble to the document, so we'll bind these directly - $(".form-group .checkbox label").hover(function() { - _addFormGroupFocus(this); - }, function() { - _removeFormGroupFocus(this); - }); - - $(document) - .on("change", ".checkbox input[type=checkbox]", function() { $(this).blur(); }) - .on("keydown paste", ".form-control", function(e) { - if(_isChar(e)) { - $(this).closest(".form-group").removeClass("is-empty"); - } - }) - .on("keyup change", ".form-control", function() { - var $input = $(this); - var $formGroup = $input.closest(".form-group"); - var isValid = (typeof $input[0].checkValidity === "undefined" || $input[0].checkValidity()); - - if ($input.val() === "" && isValid) { - $formGroup.addClass("is-empty"); - } - else { - $formGroup.removeClass("is-empty"); - } - - // Validation events do not bubble, so they must be attached directly to the input: http://jsfiddle.net/PEpRM/1/ - // Further, even the bind method is being caught, but since we are already calling #checkValidity here, just alter - // 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. - // BUT, I've left it here for backwards compatibility. - if(isValid){ - $formGroup.removeClass("has-error"); - } - else{ - $formGroup.addClass("has-error"); - } - }) - .on("focus", ".form-control, .form-group.is-fileinput", function() { - _addFormGroupFocus(this); - }) - .on("blur", ".form-control, .form-group.is-fileinput", function() { - _removeFormGroupFocus(this); - }) - // 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", ".form-group input", function() { - var $input = $(this); - if($input.attr("type") == "file") { - return; - } - - var $formGroup = $input.closest(".form-group"); - var value = $input.val(); - if (value) { - $formGroup.removeClass("is-empty"); - } else { - $formGroup.addClass("is-empty"); - } - }) - // set the fileinput readonly field with the name of the file - .on("change", ".form-group.is-fileinput input[type='file']", function() { - var $input = $(this); - var $formGroup = $input.closest(".form-group"); - var value = ""; - $.each(this.files, function(i, file) { - value += file.name + ", "; - }); - value = value.substring(0, value.length - 2); - if (value) { - $formGroup.removeClass("is-empty"); - } else { - $formGroup.addClass("is-empty"); - } - $formGroup.find("input.form-control[readonly]").val(value); - }); - }, - "ripples": function(selector) { - $((selector) ? selector : this.options.withRipples).ripples(); - }, - //"autofill": function() { + //"input": function(selector) { + // $((selector) ? selector : this.options.inputElements) + // .filter(":notmdproc") + // .data("mdproc", true) + // .each( () => { + // var $input = $(this); + // + // // Requires form-group standard markup (will add it if necessary) + // var $formGroup = $input.closest(".form-group"); // note that form-group may be grandparent in the case of an input-group + // if($formGroup.length === 0){ + // $input.wrap("
"); + // $formGroup = $input.closest(".form-group"); // find node after attached (otherwise additional attachments don't work) + // } + // + // // Modification - Change input-sm/lg to form-group-sm/lg instead (preferred standard and simpler css/less variants) + // var legacySizes = { + // "input-lg": "form-group-lg", + // "input-sm": "form-group-sm" + // }; + // $.each( legacySizes, function( legacySize, standardSize ) { + // if ($input.hasClass(legacySize)) { + // $input.removeClass(legacySize); + // $formGroup.addClass(standardSize); + // } + // }); // TODO: determine if we want to keep meddling this much. + // + // // Set as empty if is empty (damn I must improve this...) + // if ($input.val() === null || $input.val() == "undefined" || $input.val() === "") { + // $formGroup.addClass("is-empty"); + // } + // + // // Add at the end of the form-group + // $formGroup.append(""); + // + // // Support for file input + // if ($formGroup.find("input[type=file]").length > 0) { + // $formGroup.addClass("is-fileinput"); + // } + // }); + //}, + //"attachInputEventHandlers": () => { + // + //// checkboxes didn't appear to bubble to the document, so we'll bind these directly + //$(".form-group .checkbox label").hover(() => { + // Util.addFormGroupFocus(this); + //}, () => { + // Util.removeFormGroupFocus(this); + //}); + // + //$(document) + //.on("change", ".checkbox input[type=checkbox]", () => { $(this).blur(); }) + //.on("keydown paste", ".form-control", function(e) { + // if(Util.isChar(e)) { + // $(this).closest(".form-group").removeClass("is-empty"); + // } + //}) + //.on("keyup change", ".form-control", () => { + // var $input = $(this); + // var $formGroup = $input.closest(".form-group"); + // var isValid = (typeof $input[0].checkValidity === "undefined" || $input[0].checkValidity()); + // + // if ($input.val() === "" && isValid) { + // $formGroup.addClass("is-empty"); + // } + // else { + // $formGroup.removeClass("is-empty"); + // } + // + // // Validation events do not bubble, so they must be attached directly to the input: http://jsfiddle.net/PEpRM/1/ + // // Further, even the bind method is being caught, but since we are already calling #checkValidity here, just alter + // // 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. + // // BUT, I've left it here for backwards compatibility. + // if(isValid){ + // $formGroup.removeClass("has-error"); + // } + // else{ + // $formGroup.addClass("has-error"); + // } + //}) + //.on("focus", ".form-control, .form-group.is-fileinput", () => { + // Util.addFormGroupFocus(this.formGroup); + //}) + //.on("blur", ".form-control, .form-group.is-fileinput", () => { + // 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", ".form-group input", () => { + // var $input = $(this); + // if($input.attr("type") == "file") { + // return; + // } + // + // var $formGroup = $input.closest(".form-group"); + // var value = $input.val(); + // if (value) { + // $formGroup.removeClass("is-empty"); + // } else { + // $formGroup.addClass("is-empty"); + // } + //}) + //// set the fileinput readonly field with the name of the file + //.on("change", ".form-group.is-fileinput input[type='file']", () => { + // var $input = $(this); + // var $formGroup = $input.closest(".form-group"); + // var value = ""; + // $.each(this.files, function(i, file) { + // value += file.name + ", "; + // }); + // value = value.substring(0, value.length - 2); + // if (value) { + // $formGroup.removeClass("is-empty"); + // } else { + // $formGroup.addClass("is-empty"); + // } + // $formGroup.find("input.form-control[readonly]").val(value); + //}); + //}, + //"ripples": function(selector) { + // $((selector) ? selector : this.options.withRipples).ripples(); + //}, + //"autofill": () => { // // This part of code will detect autofill when the page is loading (username and password inputs for example) - // var loading = setInterval(function() { - // $("input[type!=checkbox]").each(function() { + // var loading = setInterval(() => { + // $("input[type!=checkbox]").each(() => { // var $this = $(this); // if ($this.val() && $this.val() !== $this.attr("value")) { // $this.triggerStart("change"); @@ -207,18 +208,18 @@ // }, 100); // // // After 10 seconds we are quite sure all the needed inputs are autofilled then we can stop checking them - // setTimeout(function() { + // setTimeout(() => { // clearInterval(loading); // }, 10000); //}, - //"attachAutofillEventHandlers": function() { + //"attachAutofillEventHandlers": () => { // // Listen on inputs of the focused form (because user can select from the autofill dropdown only when the input has focus) // var focused; // $(document) - // .on("focus", "input", function() { + // .on("focus", "input", () => { // var $inputs = $(this).parents("form").find("input").not("[type=file]"); - // focused = setInterval(function() { - // $inputs.each(function() { + // focused = setInterval(() => { + // $inputs.each(() => { // var $this = $(this); // if ($this.val() !== $this.attr("value")) { // $this.triggerStart("change"); @@ -226,19 +227,22 @@ // }); // }, 100); // }) - // .on("blur", ".form-group input", function() { + // .on("blur", ".form-group input", () => { // clearInterval(focused); // }); //}, - "init": function() { + "init": () => { var $document = $(document); if ($.fn.ripples && this.options.ripples) { - this.ripples(); + //this.ripples(); + $(this.options.withRipples).ripples() } if (this.options.input) { this.input(); this.attachInputEventHandlers(); + + $(this.options.inputElements).input() } if (this.options.checkbox) { //this.checkbox(); @@ -255,36 +259,41 @@ if (this.options.autofill) { //this.autofill(); //this.attachAutofillEventHandlers(); - new Autofill() + new Autofill() // FIXME: if this is the best way to invoke, perhaps it shouldn't be a jquery fn as well? } + $(this.options.fileInputElements).fileInput() + if (document.arrive && this.options.arrive) { if ($.fn.ripples && this.options.ripples) { - $document.arrive(this.options.withRipples, function() { - $.material.ripples($(this)); + $document.arrive(this.options.withRipples, () => { + $(this).ripples() }); } if (this.options.input) { - $document.arrive(this.options.inputElements, function() { - $.material.input($(this)); + $document.arrive(this.options.inputElements, () => { + $(this).input() }); } if (this.options.checkbox) { - $document.arrive(this.options.checkboxElements, function() { + $document.arrive(this.options.checkboxElements, () => { $(this).checkbox(); }); } if (this.options.radio) { - $document.arrive(this.options.radioElements, function() { + $document.arrive(this.options.radioElements, () => { $(this).radio(); }); } if (this.options.togglebutton) { - $document.arrive(this.options.togglebuttonElements, function() { + $document.arrive(this.options.togglebuttonElements, () => { $(this).togglebutton(); }); } + $document.arrive(this.options.fileInputElements, () => { + $(this).fileInput(); + }); } } }; diff --git a/js/src/radio.js b/js/src/radio.js index cf59372c..dc81c456 100644 --- a/js/src/radio.js +++ b/js/src/radio.js @@ -1,5 +1,6 @@ //import Util from './util' +// Radio decorator, to be called after Input const Radio = (($) => { /** diff --git a/js/src/togglebutton.js b/js/src/togglebutton.js index 433fccb1..044cc123 100644 --- a/js/src/togglebutton.js +++ b/js/src/togglebutton.js @@ -1,5 +1,6 @@ //import Util from './util' +// Togglebutton decorator, to be called after Input const Togglebutton = (($) => { /** diff --git a/js/src/util.js b/js/src/util.js index 443fecbd..16b3bcd8 100644 --- a/js/src/util.js +++ b/js/src/util.js @@ -16,6 +16,15 @@ const Util = (($) => { transition: 'transitionend' } + const ClassName = { + IS_FOCUSED: 'is-focused', + FORM_GROUP: 'form-group' + } + + const Selector = { + FORM_GROUP: `.${ClassName.FORM_GROUP}` //, + } + function transitionEndTest() { if (window.QUnit) { return false @@ -57,13 +66,32 @@ const Util = (($) => { return transitionEndSelector }, - isChar(evt) { - if (typeof evt.which === "undefined") { + isChar(event) { + if (typeof event.which === "undefined") { return true - } else if (typeof evt.which === "number" && evt.which > 0) { - return !evt.ctrlKey && !evt.metaKey && !evt.altKey && evt.which !== 8 && evt.which !== 9 + } else if (typeof event.which === "number" && event.which > 0) { + return !event.ctrlKey && !event.metaKey && !event.altKey && event.which !== 8 && event.which !== 9 } return false + }, + + addFormGroupFocus(formGroup) { + formGroup.addClass(ClassName.IS_FOCUSED) + }, + + removeFormGroupFocus(formGroup) { + formGroup.removeClass(ClassName.IS_FOCUSED) + }, + + /** + Find expected form-group + */ + findFormGroup(element) { + 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) { + $.error(`Failed to find form-group for ${element}`) + } + return fg } }