diff options
| author | Max | 2017-05-18 14:03:27 +0200 |
|---|---|---|
| committer | Max | 2017-05-18 14:03:27 +0200 |
| commit | f597e2fe949a1e18eb778b9a5bd102de88570555 (patch) | |
| tree | 7e42e6f91dd3e764cd2cf0c4f61f48501c2ed98f /js/material.js | |
| download | docs.maxmail.xyz-master.tar.gz | |
Diffstat (limited to 'js/material.js')
| -rw-r--r-- | js/material.js | 352 |
1 files changed, 352 insertions, 0 deletions
diff --git a/js/material.js b/js/material.js new file mode 100644 index 0000000..819152e --- /dev/null +++ b/js/material.js @@ -0,0 +1,352 @@ +/* globals jQuery */ + +(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) { + if (typeof evt.which == "undefined") { + return true; + } else if (typeof evt.which == "number" && evt.which > 0) { + return ( + !evt.ctrlKey + && !evt.metaKey + && !evt.altKey + && evt.which != 8 // backspace + && evt.which != 9 // tab + && evt.which != 13 // enter + && evt.which != 16 // shift + && evt.which != 17 // ctrl + && evt.which != 20 // caps lock + && evt.which != 27 // escape + ); + } + return false; + } + + function _addFormGroupFocus(element) { + var $element = $(element); + if (!$element.prop('disabled')) { // this is showing as undefined on chrome but works fine on firefox?? + $element.closest(".form-group").addClass("is-focused"); + } + } + + function _toggleDisabledState($element, state) { + var $target; + if ($element.hasClass('checkbox-inline') || $element.hasClass('radio-inline')) { + $target = $element; + } else { + $target = $element.closest('.checkbox').length ? $element.closest('.checkbox') : $element.closest('.radio'); + } + return $target.toggleClass('disabled', state); + } + + function _toggleTypeFocus($input) { + var disabledToggleType = false; + if ($input.is($.material.options.checkboxElements) || $input.is($.material.options.radioElements)) { + disabledToggleType = true; + } + $input.closest('label').hover(function () { + var $i = $(this).find('input'); + var isDisabled = $i.prop('disabled'); // hack because the _addFormGroupFocus() wasn't identifying the property on chrome + if (disabledToggleType) { + _toggleDisabledState($(this), isDisabled); + } + if (!isDisabled) { + _addFormGroupFocus($i); // need to find the input so we can check disablement + } + }, + function () { + _removeFormGroupFocus($(this).find('input')); + }); + } + + function _removeFormGroupFocus(element) { + $(element).closest(".form-group").removeClass("is-focused"); // remove class from form-group + } + + $.material = { + "options": { + // These options set what will be started by $.material.init() + "validate": true, + "input": true, + "ripples": true, + "checkbox": true, + "togglebutton": true, + "radio": true, + "arrive": true, + "autofill": false, + + "withRipples": [ + ".btn:not(.btn-link)", + ".card-image", + ".navbar a:not(.withoutripple)", + ".dropdown-menu a", + ".nav-tabs a:not(.withoutripple)", + ".withripple", + ".pagination li:not(.active):not(.disabled) a:not(.withoutripple)" + ].join(","), + "inputElements": "input.form-control, textarea.form-control, select.form-control", + "checkboxElements": ".checkbox > label > input[type=checkbox], label.checkbox-inline > input[type=checkbox]", + "togglebuttonElements": ".togglebutton > label > input[type=checkbox]", + "radioElements": ".radio > label > input[type=radio], label.radio-inline > input[type=radio]" + }, + "checkbox": function (selector) { + // Add fake-checkbox to material checkboxes + var $input = $((selector) ? selector : this.options.checkboxElements) + .filter(":notmdproc") + .data("mdproc", true) + .after("<span class='checkbox-material'><span class='check'></span></span>"); + + _toggleTypeFocus($input); + }, + "togglebutton": function (selector) { + // Add fake-checkbox to material checkboxes + var $input = $((selector) ? selector : this.options.togglebuttonElements) + .filter(":notmdproc") + .data("mdproc", true) + .after("<span class='toggle'></span>"); + + _toggleTypeFocus($input); + }, + "radio": function (selector) { + // Add fake-radio to material radios + var $input = $((selector) ? selector : this.options.radioElements) + .filter(":notmdproc") + .data("mdproc", true) + .after("<span class='circle'></span><span class='check'></span>"); + + _toggleTypeFocus($input); + }, + "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.attr('type') !== "hidden" && !$input.attr('hidden')) { + $input.wrap("<div class='form-group'></div>"); + $formGroup = $input.closest(".form-group"); // find node after attached (otherwise additional attachments don't work) + } + + // Legacy - Add hint label if using the old shorthand data-hint attribute on the input + if ($input.attr("data-hint")) { + $input.after("<p class='help-block'>" + $input.attr("data-hint") + "</p>"); + $input.removeAttr("data-hint"); + } + + // Legacy - 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); + } + }); + + // Legacy - Add label-floating if using old shorthand <input class="floating-label" placeholder="foo"> + if ($input.hasClass("floating-label")) { + var placeholder = $input.attr("placeholder"); + $input.attr("placeholder", null).removeClass("floating-label"); + var id = $input.attr("id"); + var forAttribute = ""; + if (id) { + forAttribute = "for='" + id + "'"; + } + $formGroup.addClass("label-floating"); + $input.after("<label " + forAttribute + "class='control-label'>" + placeholder + "</label>"); + } + + // Set as empty if is empty (damn I must improve this...) + if ($input.val() === null || $input.val() == "undefined" || $input.val() === "") { + $formGroup.addClass("is-empty"); + } + + // Support for file input + if ($formGroup.find("input[type=file]").length > 0) { + $formGroup.addClass("is-fileinput"); + } + }); + }, + "attachInputEventHandlers": function () { + var validate = this.options.validate; + + $(document) + .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() === "") { + $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 (validate) { + 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 () { + // 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 $this = $(this); + if ($this.val() && $this.val() !== $this.attr("value")) { + $this.trigger("change"); + } + }); + }, 100); + + // After 10 seconds we are quite sure all the needed inputs are autofilled then we can stop checking them + setTimeout(function () { + clearInterval(loading); + }, 10000); + }, + "attachAutofillEventHandlers": function () { + // 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 () { + var $inputs = $(this).parents("form").find("input").not("[type=file]"); + focused = setInterval(function () { + $inputs.each(function () { + var $this = $(this); + if ($this.val() !== $this.attr("value")) { + $this.trigger("change"); + } + }); + }, 100); + }) + .on("blur", ".form-group input", function () { + clearInterval(focused); + }); + }, + "init": function (options) { + this.options = $.extend({}, this.options, options); + var $document = $(document); + + if ($.fn.ripples && this.options.ripples) { + this.ripples(); + } + if (this.options.input) { + this.input(); + this.attachInputEventHandlers(); + } + if (this.options.checkbox) { + this.checkbox(); + } + if (this.options.togglebutton) { + this.togglebutton(); + } + if (this.options.radio) { + this.radio(); + } + if (this.options.autofill) { + this.autofill(); + this.attachAutofillEventHandlers(); + } + + if (document.arrive && this.options.arrive) { + if ($.fn.ripples && this.options.ripples) { + $document.arrive(this.options.withRipples, function () { + $.material.ripples($(this)); + }); + } + if (this.options.input) { + $document.arrive(this.options.inputElements, function () { + $.material.input($(this)); + }); + } + if (this.options.checkbox) { + $document.arrive(this.options.checkboxElements, function () { + $.material.checkbox($(this)); + }); + } + if (this.options.radio) { + $document.arrive(this.options.radioElements, function () { + $.material.radio($(this)); + }); + } + if (this.options.togglebutton) { + $document.arrive(this.options.togglebuttonElements, function () { + $.material.togglebutton($(this)); + }); + } + + } + } + }; + +})(jQuery); |
