From d42c8b528eb595101954f478e0024e3f079f6fbf Mon Sep 17 00:00:00 2001 From: schneefux Date: Wed, 22 Mar 2017 19:19:26 +0100 Subject: first implementation of fun stats site --- .../bootstrap-material-design/js/autofill.js | 109 +++++++ .../assets/bootstrap-material-design/js/base.js | 88 ++++++ .../js/baseFormControl.js | 36 +++ .../bootstrap-material-design/js/baseInput.js | 321 +++++++++++++++++++++ .../bootstrap-material-design/js/baseLayout.js | 122 ++++++++ .../bootstrap-material-design/js/baseSelection.js | 93 ++++++ .../js/bootstrapMaterialDesign.js | 205 +++++++++++++ .../bootstrap-material-design/js/checkbox.js | 94 ++++++ .../bootstrap-material-design/js/checkboxInline.js | 89 ++++++ .../bootstrap-material-design/js/collapseInline.js | 113 ++++++++ .../assets/bootstrap-material-design/js/drawer.js | 170 +++++++++++ .../assets/bootstrap-material-design/js/file.js | 139 +++++++++ .../assets/bootstrap-material-design/js/index.js | 32 ++ .../assets/bootstrap-material-design/js/radio.js | 95 ++++++ .../bootstrap-material-design/js/radioInline.js | 77 +++++ .../assets/bootstrap-material-design/js/ripples.js | 311 ++++++++++++++++++++ .../assets/bootstrap-material-design/js/select.js | 94 ++++++ .../assets/bootstrap-material-design/js/switch.js | 72 +++++ .../assets/bootstrap-material-design/js/text.js | 89 ++++++ .../bootstrap-material-design/js/textarea.js | 89 ++++++ .../assets/bootstrap-material-design/js/util.js | 105 +++++++ 21 files changed, 2543 insertions(+) create mode 100644 templates/assets/bootstrap-material-design/js/autofill.js create mode 100644 templates/assets/bootstrap-material-design/js/base.js create mode 100644 templates/assets/bootstrap-material-design/js/baseFormControl.js create mode 100644 templates/assets/bootstrap-material-design/js/baseInput.js create mode 100644 templates/assets/bootstrap-material-design/js/baseLayout.js create mode 100644 templates/assets/bootstrap-material-design/js/baseSelection.js create mode 100644 templates/assets/bootstrap-material-design/js/bootstrapMaterialDesign.js create mode 100644 templates/assets/bootstrap-material-design/js/checkbox.js create mode 100644 templates/assets/bootstrap-material-design/js/checkboxInline.js create mode 100644 templates/assets/bootstrap-material-design/js/collapseInline.js create mode 100644 templates/assets/bootstrap-material-design/js/drawer.js create mode 100644 templates/assets/bootstrap-material-design/js/file.js create mode 100644 templates/assets/bootstrap-material-design/js/index.js create mode 100644 templates/assets/bootstrap-material-design/js/radio.js create mode 100644 templates/assets/bootstrap-material-design/js/radioInline.js create mode 100644 templates/assets/bootstrap-material-design/js/ripples.js create mode 100644 templates/assets/bootstrap-material-design/js/select.js create mode 100644 templates/assets/bootstrap-material-design/js/switch.js create mode 100644 templates/assets/bootstrap-material-design/js/text.js create mode 100644 templates/assets/bootstrap-material-design/js/textarea.js create mode 100644 templates/assets/bootstrap-material-design/js/util.js (limited to 'templates/assets/bootstrap-material-design/js') diff --git a/templates/assets/bootstrap-material-design/js/autofill.js b/templates/assets/bootstrap-material-design/js/autofill.js new file mode 100644 index 0000000..2ada9ec --- /dev/null +++ b/templates/assets/bootstrap-material-design/js/autofill.js @@ -0,0 +1,109 @@ +import Base from './base' + +const Autofill = (($) => { + + /** + * ------------------------------------------------------------------------ + * Constants + * ------------------------------------------------------------------------ + */ + const NAME = 'autofill' + const DATA_KEY = `bmd.${NAME}` + const JQUERY_NAME = `bmd${NAME.charAt(0).toUpperCase() + NAME.slice(1)}` + const JQUERY_NO_CONFLICT = $.fn[JQUERY_NAME] + + const Default = {} + + /** + * ------------------------------------------------------------------------ + * Class Definition + * ------------------------------------------------------------------------ + */ + class Autofill extends Base { + + constructor($element, config) { + super($element, $.extend(true, {}, Default, config)) + + this._watchLoading() + this._attachEventHandlers() + } + + dispose() { + super.dispose(DATA_KEY) + } + + // ------------------------------------------------------------------------ + // private + + _watchLoading() { + // After 10 seconds we are quite sure all the needed inputs are autofilled then we can stop checking them + setTimeout(() => { + clearInterval(this._onLoading) + }, 10000) + } + + // 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) => { + let $element = $(element) + if ($element.val() && $element.val() !== $element.attr('value')) { + $element.trigger('change') + } + }) + }, 100) + } + + _attachEventHandlers() { + // Listen on inputs of the focused form + // (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]') + focused = setInterval(() => { + $inputs.each((index, element) => { + let $element = $(element) + if ($element.val() !== $element.attr('value')) { + $element.trigger('change') + } + }) + }, 100) + }) + .on('blur', '.form-group input', () => { + clearInterval(focused) + }) + } + + // ------------------------------------------------------------------------ + // static + static _jQueryInterface(config) { + return this.each(function () { + let $element = $(this) + let data = $element.data(DATA_KEY) + + if (!data) { + data = new Autofill($element, config) + $element.data(DATA_KEY, data) + } + }) + } + } + + /** + * ------------------------------------------------------------------------ + * jQuery + * ------------------------------------------------------------------------ + */ + $.fn[JQUERY_NAME] = Autofill._jQueryInterface + $.fn[JQUERY_NAME].Constructor = Autofill + $.fn[JQUERY_NAME].noConflict = () => { + $.fn[JQUERY_NAME] = JQUERY_NO_CONFLICT + return Autofill._jQueryInterface + } + + return Autofill + +})(jQuery) + +export default Autofill diff --git a/templates/assets/bootstrap-material-design/js/base.js b/templates/assets/bootstrap-material-design/js/base.js new file mode 100644 index 0000000..283b976 --- /dev/null +++ b/templates/assets/bootstrap-material-design/js/base.js @@ -0,0 +1,88 @@ +import Util from './util' + +const Base = (($) => { + + const ClassName = { + BMD_FORM_GROUP: 'bmd-form-group', + IS_FILLED: 'is-filled', + IS_FOCUSED: 'is-focused' + } + + const Selector = { + BMD_FORM_GROUP: `.${ClassName.BMD_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) { + this.$element.data(dataKey, null) + this.$element = null + this.config = null + } + + // ------------------------------------------------------------------------ + // protected + + addFormGroupFocus() { + if (!this.$element.prop('disabled')) { + this.$bmdFormGroup.addClass(ClassName.IS_FOCUSED) + } + } + + removeFormGroupFocus() { + this.$bmdFormGroup.removeClass(ClassName.IS_FOCUSED) + } + + removeIsFilled() { + this.$bmdFormGroup.removeClass(ClassName.IS_FILLED) + } + + addIsFilled() { + this.$bmdFormGroup.addClass(ClassName.IS_FILLED) + } + + // Find bmd-form-group + findMdbFormGroup(raiseError = true) { + let mfg = this.$element.closest(Selector.BMD_FORM_GROUP) + if (mfg.length === 0 && raiseError) { + $.error(`Failed to find ${Selector.BMD_FORM_GROUP} for ${Util.describe(this.$element)}`) + } + return mfg + } + + // ------------------------------------------------------------------------ + // private + + // ------------------------------------------------------------------------ + // static + + } + + return Base + +})(jQuery) + +export default Base diff --git a/templates/assets/bootstrap-material-design/js/baseFormControl.js b/templates/assets/bootstrap-material-design/js/baseFormControl.js new file mode 100644 index 0000000..8481070 --- /dev/null +++ b/templates/assets/bootstrap-material-design/js/baseFormControl.js @@ -0,0 +1,36 @@ +import BaseInput from './baseInput' + +const BaseFormControl = (($) => { + + /** + * ------------------------------------------------------------------------ + * Constants + * ------------------------------------------------------------------------ + */ + const Default = { + requiredClasses: ['form-control'] + } + + /** + * ------------------------------------------------------------------------ + * Class Definition + * ------------------------------------------------------------------------ + */ + class BaseFormControl extends BaseInput { + + constructor($element, config) { + super($element, $.extend(true, Default, config)) + + // Initially mark as empty + if (this.isEmpty()) { + this.removeIsFilled() + } + } + } + + + return BaseFormControl + +})(jQuery) + +export default BaseFormControl diff --git a/templates/assets/bootstrap-material-design/js/baseInput.js b/templates/assets/bootstrap-material-design/js/baseInput.js new file mode 100644 index 0000000..5396ccb --- /dev/null +++ b/templates/assets/bootstrap-material-design/js/baseInput.js @@ -0,0 +1,321 @@ +import Base from './base' +import Util from './util' + +const BaseInput = (($) => { + + const ClassName = { + FORM_GROUP: 'form-group', + BMD_FORM_GROUP: 'bmd-form-group', + BMD_LABEL: 'bmd-label', + BMD_LABEL_STATIC: 'bmd-label-static', + BMD_LABEL_PLACEHOLDER: 'bmd-label-placeholder', + BMD_LABEL_FLOATING: 'bmd-label-floating', + HAS_DANGER: 'has-danger', + IS_FILLED: 'is-filled', + IS_FOCUSED: 'is-focused', + INPUT_GROUP: 'input-group' + } + + const Selector = { + FORM_GROUP: `.${ClassName.FORM_GROUP}`, + BMD_FORM_GROUP: `.${ClassName.BMD_FORM_GROUP}`, + BMD_LABEL_WILDCARD: `label[class^='${ClassName.BMD_LABEL}'], label[class*=' ${ClassName.BMD_LABEL}']` // match any label variant if specified + } + + const Default = { + validate: false, + formGroup: { + required: false + }, + bmdFormGroup: { + template: ``, + create: true, // create a wrapper if form-group not found + required: true // not recommended to turn this off, only used for inline components + }, + label: { + required: false, + + // Prioritized find order for resolving the label to be used as an bmd-label if not specified in the markup + // - a function(thisComponent); or + // - a string selector used like $bmdFormGroup.find(selector) + // + // Note this only runs if $bmdFormGroup.find(Selector.BMD_LABEL_WILDCARD) fails to find a label (as authored in the markup) + // + selectors: [ + `.form-control-label`, // in the case of horizontal or inline forms, this will be marked + `> label` // usual case for text inputs, first child. Deeper would find toggle labels so don't do that. + ], + className: ClassName.BMD_LABEL_STATIC + }, + requiredClasses: [], + invalidComponentMatches: [], + convertInputSizeVariations: true + } + + const FormControlSizeMarkers = { + 'form-control-lg': 'bmd-form-group-lg', + 'form-control-sm': 'bmd-form-group-sm' + } + + /** + * ------------------------------------------------------------------------ + * Class Definition + * ------------------------------------------------------------------------ + */ + class BaseInput extends 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 = {}) { + super($element, $.extend(true, {}, Default, config), properties) + + // Enforce no overlap between components to prevent side effects + this._rejectInvalidComponentMatches() + + // Enforce expected structure (if any) + this.rejectWithoutRequiredStructure() + + // Enforce required classes for a consistent rendering + this._rejectWithoutRequiredClasses() + + // Resolve the form-group first, it will be used for bmd-form-group if possible + // note: different components have different rules + this.$formGroup = this.findFormGroup(this.config.formGroup.required) + + // Will add bmd-form-group to form-group or create an bmd-form-group + // Performance Note: for those forms that are really performance driven, create the markup with the .bmd-form-group to avoid + // rendering changes once added. + this.$bmdFormGroup = this.resolveMdbFormGroup() + + // Resolve and mark the bmdLabel if necessary as defined by the config + this.$bmdLabel = this.resolveMdbLabel() + + // Signal to the bmd-form-group that a form-control-* variation is being used + this.resolveMdbFormGroupSizing() + + this.addFocusListener() + this.addChangeListener() + + if(this.$element.val() != ''){ + this.addIsFilled() + } + } + + dispose(dataKey) { + super.dispose(dataKey) + this.$bmdFormGroup = null + this.$formGroup = null + } + + // ------------------------------------------------------------------------ + // protected + + rejectWithoutRequiredStructure() { + // implement + } + + addFocusListener() { + this.$element + .on('focus', () => { + this.addFormGroupFocus() + }) + .on('blur', () => { + this.removeFormGroupFocus() + }) + } + + addChangeListener() { + this.$element + .on('keydown paste', (event) => { + if (Util.isChar(event)) { + this.addIsFilled() + } + }) + .on('keyup change', () => { + + // 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') + if (this.isEmpty()) { + this.removeIsFilled() + } else { + this.addIsFilled() + } + + if (this.config.validate) { + // Validation events do not bubble, so they must be attached directly to the text: 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. + let isValid = (typeof this.$element[0].checkValidity === 'undefined' || this.$element[0].checkValidity()) + if (isValid) { + this.removeHasDanger() + } else { + this.addHasDanger() + } + } + }) + } + + addHasDanger() { + this.$bmdFormGroup.addClass(ClassName.HAS_DANGER) + } + + removeHasDanger() { + this.$bmdFormGroup.removeClass(ClassName.HAS_DANGER) + } + + isEmpty() { + return (this.$element.val() === null || this.$element.val() === undefined || this.$element.val() === '') + } + + // Will add bmd-form-group to form-group or create a bmd-form-group if necessary + resolveMdbFormGroup() { + let mfg = this.findMdbFormGroup(false) + if (mfg === undefined || mfg.length === 0) { + if (this.config.bmdFormGroup.create && (this.$formGroup === undefined || this.$formGroup.length === 0)) { + // If a form-group doesn't exist (not recommended), take a guess and wrap the element (assuming no label). + // note: it's possible to make this smarter, but I need to see valid cases before adding any complexity. + + // this may be an input-group, wrap that instead + if(this.outerElement().parent().hasClass(ClassName.INPUT_GROUP)){ + this.outerElement().parent().wrap(this.config.bmdFormGroup.template) + } + else{ + this.outerElement().wrap(this.config.bmdFormGroup.template) + } + } else { + // a form-group does exist, add our marker class to it + this.$formGroup.addClass(ClassName.BMD_FORM_GROUP) + + // OLD: may want to implement this after all, see how the styling turns out, but using an existing form-group is less manipulation of the dom and therefore preferable + // A form-group does exist, so add an bmd-form-group wrapping it's internal contents + //fg.wrapInner(this.config.bmdFormGroup.template) + } + + mfg = this.findMdbFormGroup(this.config.bmdFormGroup.required) + } + + return mfg + } + + // Demarcation element (e.g. first child of a form-group) + // Subclasses such as file inputs may have different structures + outerElement() { + return this.$element + } + + // Will add bmd-label to bmd-form-group if not already specified + resolveMdbLabel() { + + let label = this.$bmdFormGroup.find(Selector.BMD_LABEL_WILDCARD) + if (label === undefined || label.length === 0) { + // we need to find it based on the configured selectors + label = this.findMdbLabel(this.config.label.required) + + if (label === undefined || label.length === 0) { + // no label found, and finder did not require one + } else { + // a candidate label was found, add the configured default class name + label.addClass(this.config.label.className) + } + } + + return label + } + + // Find bmd-label variant based on the config selectors + findMdbLabel(raiseError = true) { + let label = null + + // use the specified selector order + for (let selector of this.config.label.selectors) { + if ($.isFunction(selector)) { + label = selector(this) + } else { + label = this.$bmdFormGroup.find(selector) + } + + if (label !== undefined && label.length > 0) { + break + } + } + + if (label.length === 0 && raiseError) { + $.error(`Failed to find ${Selector.BMD_LABEL_WILDCARD} within form-group for ${Util.describe(this.$element)}`) + } + return label + } + + // Find bmd-form-group + findFormGroup(raiseError = true) { + let fg = this.$element.closest(Selector.FORM_GROUP) + if (fg.length === 0 && raiseError) { + $.error(`Failed to find ${Selector.FORM_GROUP} for ${Util.describe(this.$element)}`) + } + return fg + } + + // Due to the interconnected nature of labels/inputs/help-blocks, signal the bmd-form-group-* size variation based on + // a found form-control-* size + resolveMdbFormGroupSizing() { + if (!this.config.convertInputSizeVariations) { + return + } + + // Modification - Change text-sm/lg to form-group-sm/lg instead (preferred standard and simpler css/less variants) + for (let inputSize in FormControlSizeMarkers) { + if (this.$element.hasClass(inputSize)) { + //this.$element.removeClass(inputSize) + this.$bmdFormGroup.addClass(FormControlSizeMarkers[inputSize]) + } + } + } + + // ------------------------------------------------------------------------ + // private + _rejectInvalidComponentMatches() { + for (let otherComponent of this.config.invalidComponentMatches) { + otherComponent.rejectMatch(this.constructor.name, this.$element) + } + } + + _rejectWithoutRequiredClasses() { + for (let requiredClass of this.config.requiredClasses) { + + let found = false + // allow one of several classes to be passed in x||y + if (requiredClass.indexOf('||') !== -1) { + let oneOf = requiredClass.split('||') + for (let requiredClass of oneOf) { + if (this.$element.hasClass(requiredClass)) { + found = true + break + } + } + } else if (this.$element.hasClass(requiredClass)) { + found = true + } + + // error if not found + if (!found) { + $.error(`${this.constructor.name} element: ${Util.describe(this.$element)} requires class: ${requiredClass}`) + } + } + } + + // ------------------------------------------------------------------------ + // static + + } + + return BaseInput + +})(jQuery) + +export default BaseInput diff --git a/templates/assets/bootstrap-material-design/js/baseLayout.js b/templates/assets/bootstrap-material-design/js/baseLayout.js new file mode 100644 index 0000000..8dc87ed --- /dev/null +++ b/templates/assets/bootstrap-material-design/js/baseLayout.js @@ -0,0 +1,122 @@ +import Base from './base' +import Util from './util' + +const BaseLayout = (($) => { + + const ClassName = { + CANVAS: 'bmd-layout-canvas', + CONTAINER: 'bmd-layout-container', + BACKDROP: `bmd-layout-backdrop` + } + + const Selector = { + CANVAS: `.${ClassName.CANVAS}`, + CONTAINER: `.${ClassName.CONTAINER}`, + BACKDROP: `.${ClassName.BACKDROP}` + } + + const Default = { + canvas: { + create: true, + required: true, + template: `
` + }, + backdrop: { + create: true, + required: true, + template: `
` + } + } + + /** + * ------------------------------------------------------------------------ + * Class Definition + * ------------------------------------------------------------------------ + */ + class BaseLayout extends Base { + + constructor($element, config, properties = {}) { + super($element, $.extend(true, {}, Default, config), properties) + + this.$container = this.findContainer(true) + this.$backdrop = this.resolveBackdrop() + this.resolveCanvas(); + } + + dispose(dataKey) { + super.dispose(dataKey) + this.$container = null + this.$backdrop = null + } + + // ------------------------------------------------------------------------ + // protected + + // Will wrap container in bmd-layout-canvas if necessary + resolveCanvas() { + let bd = this.findCanvas(false) + if (bd === undefined || bd.length === 0) { + if (this.config.canvas.create) { + this.$container.wrap(this.config.canvas.template) + } + + bd = this.findCanvas(this.config.canvas.required) + } + + return bd + } + + // Find closest bmd-layout-container based on the given context + findCanvas(raiseError = true, context = this.$container) { + let canvas = context.closest(Selector.CANVAS) + if (canvas.length === 0 && raiseError) { + $.error(`Failed to find ${Selector.CANVAS} for ${Util.describe(context)}`) + } + return canvas + } + + // Will add bmd-layout-backdrop to bmd-layout-container if necessary + resolveBackdrop() { + let bd = this.findBackdrop(false) + if (bd === undefined || bd.length === 0) { + if (this.config.backdrop.create) { + this.$container.append(this.config.backdrop.template) + } + + bd = this.findBackdrop(this.config.backdrop.required) + } + + return bd + } + + // Find closest bmd-layout-container based on the given context + findBackdrop(raiseError = true, context = this.$container) { + let backdrop = context.find(`> ${Selector.BACKDROP}`) + if (backdrop.length === 0 && raiseError) { + $.error(`Failed to find ${Selector.BACKDROP} for ${Util.describe(context)}`) + } + return backdrop + } + + // Find closest bmd-layout-container based on the given context + findContainer(raiseError = true, context = this.$element) { + let container = context.closest(Selector.CONTAINER) + if (container.length === 0 && raiseError) { + $.error(`Failed to find ${Selector.CONTAINER} for ${Util.describe(context)}`) + } + return container + } + + // ------------------------------------------------------------------------ + // private + + // ------------------------------------------------------------------------ + // static + + } + + return BaseLayout + +})(jQuery) + +export default BaseLayout diff --git a/templates/assets/bootstrap-material-design/js/baseSelection.js b/templates/assets/bootstrap-material-design/js/baseSelection.js new file mode 100644 index 0000000..765d1c3 --- /dev/null +++ b/templates/assets/bootstrap-material-design/js/baseSelection.js @@ -0,0 +1,93 @@ +import BaseInput from './baseInput' +import Util from './util' + +const BaseSelection = (($) => { + + /** + * ------------------------------------------------------------------------ + * Constants + * ------------------------------------------------------------------------ + */ + const Default = { + label: { + required: false + + // Prioritized find order for resolving the label to be used as an bmd-label if not specified in the markup + // - a function(thisComponent); or + // - a string selector used like $bmdFormGroup.find(selector) + // + // Note this only runs if $bmdFormGroup.find(Selector.BMD_LABEL_WILDCARD) fails to find a label (as authored in the markup) + // + //selectors: [ + // `.form-control-label`, // in the case of horizontal or inline forms, this will be marked + // `> label` // usual case for text inputs + //] + } + } + + const Selector = { + LABEL: 'label' + } + + /** + * ------------------------------------------------------------------------ + * Class Definition + * ------------------------------------------------------------------------ + */ + class BaseSelection extends BaseInput { + + constructor($element, config, properties) { + // properties = {inputType: checkbox, outerClass: checkbox-inline} + // '.checkbox|switch|radio > label > input[type=checkbox|radio]' + // '.${this.outerClass} > label > input[type=${this.inputType}]' + + super($element, $.extend(true, {}, Default, config), properties) + this.decorateMarkup() + } + + // ------------------------------------------------------------------------ + // protected + + decorateMarkup() { + this.$element.after(this.config.template) + } + + // Demarcation element (e.g. first child of a form-group) + outerElement() { + // .checkbox|switch|radio > label > input[type=checkbox|radio] + // label.checkbox-inline > input[type=checkbox|radio] + // .${this.outerClass} > label > input[type=${this.inputType}] + return this.$element.parent().closest(`.${this.outerClass}`) + } + + rejectWithoutRequiredStructure() { + // '.checkbox|switch|radio > label > input[type=checkbox|radio]' + // '.${this.outerClass} > label > input[type=${this.inputType}]' + Util.assert(this.$element, !this.$element.parent().prop('tagName') === 'label', `${this.constructor.name}'s ${Util.describe(this.$element)} parent element should be