Skip to content
Snippets Groups Projects
Verified Commit 44d3745e authored by Phil Hughes's avatar Phil Hughes
Browse files

Moves form related JS modules out of global

parent a30417e5
No related branches found
No related tags found
No related merge requests found
Loading
Loading
@@ -79,6 +79,8 @@ import initChangesDropdown from './init_changes_dropdown';
import AbuseReports from './abuse_reports';
import { ajaxGet, convertPermissionToBoolean } from './lib/utils/common_utils';
import AjaxLoadingSpinner from './ajax_loading_spinner';
import GlFieldErrors from './gl_field_errors';
import GLForm from './gl_form';
import U2FAuthenticate from './u2f/authenticate';
 
(function() {
Loading
Loading
@@ -230,7 +232,7 @@ import U2FAuthenticate from './u2f/authenticate';
case 'groups:milestones:update':
new ZenMode();
new gl.DueDateSelectors();
new gl.GLForm($('.milestone-form'), true);
new GLForm($('.milestone-form'), true);
break;
case 'projects:compare:show':
new gl.Diff();
Loading
Loading
@@ -247,7 +249,7 @@ import U2FAuthenticate from './u2f/authenticate';
case 'projects:issues:new':
case 'projects:issues:edit':
shortcut_handler = new ShortcutsNavigation();
new gl.GLForm($('.issue-form'), true);
new GLForm($('.issue-form'), true);
new IssuableForm($('.issue-form'));
new LabelsSelect();
new MilestoneSelect();
Loading
Loading
@@ -271,7 +273,7 @@ import U2FAuthenticate from './u2f/authenticate';
case 'projects:merge_requests:edit':
new gl.Diff();
shortcut_handler = new ShortcutsNavigation();
new gl.GLForm($('.merge-request-form'), true);
new GLForm($('.merge-request-form'), true);
new IssuableForm($('.merge-request-form'));
new LabelsSelect();
new MilestoneSelect();
Loading
Loading
@@ -280,7 +282,7 @@ import U2FAuthenticate from './u2f/authenticate';
break;
case 'projects:tags:new':
new ZenMode();
new gl.GLForm($('.tag-form'), true);
new GLForm($('.tag-form'), true);
new RefSelectDropdown($('.js-branch-select'));
break;
case 'projects:snippets:show':
Loading
Loading
@@ -290,17 +292,17 @@ import U2FAuthenticate from './u2f/authenticate';
case 'projects:snippets:edit':
case 'projects:snippets:create':
case 'projects:snippets:update':
new gl.GLForm($('.snippet-form'), true);
new GLForm($('.snippet-form'), true);
break;
case 'snippets:new':
case 'snippets:edit':
case 'snippets:create':
case 'snippets:update':
new gl.GLForm($('.snippet-form'), false);
new GLForm($('.snippet-form'), false);
break;
case 'projects:releases:edit':
new ZenMode();
new gl.GLForm($('.release-form'), true);
new GLForm($('.release-form'), true);
break;
case 'projects:merge_requests:show':
new gl.Diff();
Loading
Loading
@@ -606,7 +608,7 @@ import U2FAuthenticate from './u2f/authenticate';
new Wikis();
shortcut_handler = new ShortcutsWiki();
new ZenMode();
new gl.GLForm($('.wiki-form'), true);
new GLForm($('.wiki-form'), true);
break;
case 'snippets':
shortcut_handler = new ShortcutsNavigation();
Loading
Loading
@@ -657,7 +659,7 @@ import U2FAuthenticate from './u2f/authenticate';
 
Dispatcher.prototype.initFieldErrors = function() {
$('.gl-show-field-errors').each((i, form) => {
new gl.GlFieldErrors(form);
new GlFieldErrors(form);
});
};
 
Loading
Loading
Loading
Loading
@@ -54,7 +54,7 @@ const inputErrorClass = 'gl-field-error-outline';
const errorAnchorSelector = '.gl-field-error-anchor';
const ignoreInputSelector = '.gl-field-error-ignore';
 
class GlFieldError {
export default class GlFieldError {
constructor({ input, formErrors }) {
this.inputElement = $(input);
this.inputDomElement = this.inputElement.get(0);
Loading
Loading
@@ -159,6 +159,3 @@ class GlFieldError {
this.fieldErrorElement.hide();
}
}
window.gl = window.gl || {};
window.gl.GlFieldError = GlFieldError;
/* eslint-disable comma-dangle, class-methods-use-this, max-len, space-before-function-paren, arrow-parens, no-param-reassign */
import './gl_field_error';
import GlFieldError from './gl_field_error';
 
const customValidationFlag = 'gl-field-error-ignore';
 
class GlFieldErrors {
export default class GlFieldErrors {
constructor(form) {
this.form = $(form);
this.state = {
inputs: [],
valid: false
valid: false,
};
this.initValidators();
}
 
initValidators () {
initValidators() {
// register selectors here as needed
const validateSelectors = [':text', ':password', '[type=email]']
.map((selector) => `input${selector}`).join(',');
.map(selector => `input${selector}`).join(',');
 
this.state.inputs = this.form.find(validateSelectors).toArray()
.filter((input) => !input.classList.contains(customValidationFlag))
.map((input) => new window.gl.GlFieldError({ input, formErrors: this }));
.filter(input => !input.classList.contains(customValidationFlag))
.map(input => new GlFieldError({ input, formErrors: this }));
 
this.form.on('submit', this.catchInvalidFormSubmit);
this.form.on('submit', GlFieldErrors.catchInvalidFormSubmit);
}
 
/* Neccessary to prevent intercept and override invalid form submit
* because Safari & iOS quietly allow form submission when form is invalid
* and prevents disabling of invalid submit button by application.js */
 
catchInvalidFormSubmit (event) {
const $form = $(event.currentTarget);
static catchInvalidFormSubmit(e) {
const $form = $(e.currentTarget);
 
if (!$form.attr('novalidate')) {
if (!event.currentTarget.checkValidity()) {
Loading
Loading
@@ -50,11 +48,9 @@ class GlFieldErrors {
});
}
 
focusOnFirstInvalid () {
const firstInvalid = this.state.inputs.filter((input) => !input.inputDomElement.validity.valid)[0];
focusOnFirstInvalid() {
const firstInvalid = this.state.inputs
.filter(input => !input.inputDomElement.validity.valid)[0];
firstInvalid.inputElement.focus();
}
}
window.gl = window.gl || {};
window.gl.GlFieldErrors = GlFieldErrors;
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-new, max-len */
/* global GitLab */
/* global DropzoneInput */
/* global autosize */
 
import GfmAutoComplete from './gfm_auto_complete';
 
window.gl = window.gl || {};
function GLForm(form, enableGFM = false) {
this.form = form;
this.textarea = this.form.find('textarea.js-gfm-input');
this.enableGFM = enableGFM;
// Before we start, we should clean up any previous data for this form
this.destroy();
// Setup the form
this.setupForm();
this.form.data('gl-form', this);
}
GLForm.prototype.destroy = function() {
// Clean form listeners
this.clearEventListeners();
if (this.autoComplete) {
this.autoComplete.destroy();
export default class GLForm {
constructor(form, enableGFM = false) {
this.form = form;
this.textarea = this.form.find('textarea.js-gfm-input');
this.enableGFM = enableGFM;
// Before we start, we should clean up any previous data for this form
this.destroy();
// Setup the form
this.setupForm();
this.form.data('gl-form', this);
}
return this.form.data('gl-form', null);
};
 
GLForm.prototype.setupForm = function() {
var isNewForm;
isNewForm = this.form.is(':not(.gfm-form)');
this.form.removeClass('js-new-note-form');
if (isNewForm) {
this.form.find('.div-dropzone').remove();
this.form.addClass('gfm-form');
// remove notify commit author checkbox for non-commit notes
gl.utils.disableButtonIfEmptyField(this.form.find('.js-note-text'), this.form.find('.js-comment-button, .js-note-new-discussion'));
this.autoComplete = new GfmAutoComplete(gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources);
this.autoComplete.setup(this.form.find('.js-gfm-input'), {
emojis: true,
members: this.enableGFM,
issues: this.enableGFM,
milestones: this.enableGFM,
mergeRequests: this.enableGFM,
labels: this.enableGFM,
});
new DropzoneInput(this.form);
autosize(this.textarea);
destroy() {
// Clean form listeners
this.clearEventListeners();
if (this.autoComplete) {
this.autoComplete.destroy();
}
this.form.data('gl-form', null);
}
// form and textarea event listeners
this.addEventListeners();
gl.text.init(this.form);
// hide discard button
this.form.find('.js-note-discard').hide();
this.form.show();
if (this.isAutosizeable) this.setupAutosize();
};
 
GLForm.prototype.setupAutosize = function () {
this.textarea.off('autosize:resized')
.on('autosize:resized', this.setHeightData.bind(this));
setupForm() {
const isNewForm = this.form.is(':not(.gfm-form)');
this.form.removeClass('js-new-note-form');
if (isNewForm) {
this.form.find('.div-dropzone').remove();
this.form.addClass('gfm-form');
// remove notify commit author checkbox for non-commit notes
gl.utils.disableButtonIfEmptyField(this.form.find('.js-note-text'), this.form.find('.js-comment-button, .js-note-new-discussion'));
this.autoComplete = new GfmAutoComplete(gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources);
this.autoComplete.setup(this.form.find('.js-gfm-input'), {
emojis: true,
members: this.enableGFM,
issues: this.enableGFM,
milestones: this.enableGFM,
mergeRequests: this.enableGFM,
labels: this.enableGFM,
});
new DropzoneInput(this.form); // eslint-disable-line no-new
autosize(this.textarea);
}
// form and textarea event listeners
this.addEventListeners();
gl.text.init(this.form);
// hide discard button
this.form.find('.js-note-discard').hide();
this.form.show();
if (this.isAutosizeable) this.setupAutosize();
}
 
this.textarea.off('mouseup.autosize')
.on('mouseup.autosize', this.destroyAutosize.bind(this));
setupAutosize() {
this.textarea.off('autosize:resized')
.on('autosize:resized', this.setHeightData.bind(this));
 
setTimeout(() => {
autosize(this.textarea);
this.textarea.css('resize', 'vertical');
}, 0);
};
this.textarea.off('mouseup.autosize')
.on('mouseup.autosize', this.destroyAutosize.bind(this));
 
GLForm.prototype.setHeightData = function () {
this.textarea.data('height', this.textarea.outerHeight());
};
setTimeout(() => {
autosize(this.textarea);
this.textarea.css('resize', 'vertical');
}, 0);
}
 
GLForm.prototype.destroyAutosize = function () {
const outerHeight = this.textarea.outerHeight();
setHeightData() {
this.textarea.data('height', this.textarea.outerHeight());
}
 
if (this.textarea.data('height') === outerHeight) return;
destroyAutosize() {
const outerHeight = this.textarea.outerHeight();
 
autosize.destroy(this.textarea);
if (this.textarea.data('height') === outerHeight) return;
 
this.textarea.data('height', outerHeight);
this.textarea.outerHeight(outerHeight);
this.textarea.css('max-height', window.outerHeight);
};
autosize.destroy(this.textarea);
 
GLForm.prototype.clearEventListeners = function() {
this.textarea.off('focus');
this.textarea.off('blur');
return gl.text.removeListeners(this.form);
};
this.textarea.data('height', outerHeight);
this.textarea.outerHeight(outerHeight);
this.textarea.css('max-height', window.outerHeight);
}
 
GLForm.prototype.addEventListeners = function() {
this.textarea.on('focus', function() {
return $(this).closest('.md-area').addClass('is-focused');
});
return this.textarea.on('blur', function() {
return $(this).closest('.md-area').removeClass('is-focused');
});
};
clearEventListeners() {
this.textarea.off('focus');
this.textarea.off('blur');
gl.text.removeListeners(this.form);
}
 
window.gl.GLForm = GLForm;
addEventListeners() {
this.textarea.on('focus', function focusTextArea() {
$(this).closest('.md-area').addClass('is-focused');
});
this.textarea.on('blur', function blurTextArea() {
$(this).closest('.md-area').removeClass('is-focused');
});
}
}
Loading
Loading
@@ -19,6 +19,7 @@ import 'vendor/jquery.atwho';
import AjaxCache from '~/lib/utils/ajax_cache';
import Flash from './flash';
import CommentTypeToggle from './comment_type_toggle';
import GLForm from './gl_form';
import loadAwardsHandler from './awards_handler';
import './autosave';
import './dropzone_input';
Loading
Loading
@@ -557,7 +558,7 @@ export default class Notes {
*/
setupNoteForm(form) {
var textarea, key;
new gl.GLForm(form, this.enableGFM);
new GLForm(form, this.enableGFM);
textarea = form.find('.js-note-text');
key = [
'Note',
Loading
Loading
@@ -1152,7 +1153,7 @@ export default class Notes {
var targetId = $originalContentEl.data('target-id');
var targetType = $originalContentEl.data('target-type');
 
new gl.GLForm($editForm.find('form'), this.enableGFM);
new GLForm($editForm.find('form'), this.enableGFM);
 
$editForm.find('form')
.attr('action', postUrl)
Loading
Loading
import Vue from 'vue';
import Translate from '../vue_shared/translate';
import GlFieldErrors from '../gl_field_errors';
import intervalPatternInput from './components/interval_pattern_input.vue';
import TimezoneDropdown from './components/timezone_dropdown';
import TargetBranchDropdown from './components/target_branch_dropdown';
Loading
Loading
@@ -39,7 +40,7 @@ document.addEventListener('DOMContentLoaded', () => {
 
gl.timezoneDropdown = new TimezoneDropdown();
gl.targetBranchDropdown = new TargetBranchDropdown();
gl.pipelineScheduleFieldErrors = new gl.GlFieldErrors(formElement);
gl.pipelineScheduleFieldErrors = new GlFieldErrors(formElement);
 
setupPipelineVariableList($('.js-pipeline-variable-list'));
});
<script>
import Flash from '../../../flash';
import GLForm from '../../../gl_form';
import markdownHeader from './header.vue';
import markdownToolbar from './toolbar.vue';
 
Loading
Loading
@@ -85,7 +86,7 @@
/*
GLForm class handles all the toolbar buttons
*/
return new gl.GLForm($(this.$refs['gl-form']), true);
return new GLForm($(this.$refs['gl-form']), true);
},
beforeDestroy() {
const glForm = $(this.$refs['gl-form']).data('gl-form');
Loading
Loading
/* eslint-disable space-before-function-paren, arrow-body-style */
 
import '~/gl_field_errors';
import GlFieldErrors from '~/gl_field_errors';
 
((global) => {
describe('GL Style Field Errors', function() {
preloadFixtures('static/gl_field_errors.html.raw');
 
describe('GL Style Field Errors', function() {
beforeEach(function() {
loadFixtures('static/gl_field_errors.html.raw');
const $form = this.$form = $('form.gl-show-field-errors');
this.fieldErrors = new global.GlFieldErrors($form);
});
beforeEach(function() {
loadFixtures('static/gl_field_errors.html.raw');
const $form = this.$form = $('form.gl-show-field-errors');
this.fieldErrors = new GlFieldErrors($form);
});
 
it('should select the correct input elements', function() {
expect(this.$form).toBeDefined();
expect(this.$form.length).toBe(1);
expect(this.fieldErrors).toBeDefined();
const inputs = this.fieldErrors.state.inputs;
expect(inputs.length).toBe(4);
});
it('should select the correct input elements', function() {
expect(this.$form).toBeDefined();
expect(this.$form.length).toBe(1);
expect(this.fieldErrors).toBeDefined();
const inputs = this.fieldErrors.state.inputs;
expect(inputs.length).toBe(4);
});
 
it('should ignore elements with custom error handling', function() {
const customErrorFlag = 'gl-field-error-ignore';
const customErrorElem = $(`.${customErrorFlag}`);
it('should ignore elements with custom error handling', function() {
const customErrorFlag = 'gl-field-error-ignore';
const customErrorElem = $(`.${customErrorFlag}`);
 
expect(customErrorElem.length).toBe(1);
expect(customErrorElem.length).toBe(1);
 
const customErrors = this.fieldErrors.state.inputs.filter((input) => {
return input.inputElement.hasClass(customErrorFlag);
});
expect(customErrors.length).toBe(0);
const customErrors = this.fieldErrors.state.inputs.filter((input) => {
return input.inputElement.hasClass(customErrorFlag);
});
expect(customErrors.length).toBe(0);
});
 
it('should not show any errors before submit attempt', function() {
this.$form.find('.email').val('not-a-valid-email').keyup();
this.$form.find('.text-required').val('').keyup();
this.$form.find('.alphanumberic').val('?---*').keyup();
it('should not show any errors before submit attempt', function() {
this.$form.find('.email').val('not-a-valid-email').keyup();
this.$form.find('.text-required').val('').keyup();
this.$form.find('.alphanumberic').val('?---*').keyup();
 
const errorsShown = this.$form.find('.gl-field-error-outline');
expect(errorsShown.length).toBe(0);
});
const errorsShown = this.$form.find('.gl-field-error-outline');
expect(errorsShown.length).toBe(0);
});
 
it('should show errors when input valid is submitted', function() {
this.$form.find('.email').val('not-a-valid-email').keyup();
this.$form.find('.text-required').val('').keyup();
this.$form.find('.alphanumberic').val('?---*').keyup();
it('should show errors when input valid is submitted', function() {
this.$form.find('.email').val('not-a-valid-email').keyup();
this.$form.find('.text-required').val('').keyup();
this.$form.find('.alphanumberic').val('?---*').keyup();
 
this.$form.submit();
this.$form.submit();
 
const errorsShown = this.$form.find('.gl-field-error-outline');
expect(errorsShown.length).toBe(4);
});
const errorsShown = this.$form.find('.gl-field-error-outline');
expect(errorsShown.length).toBe(4);
});
 
it('should properly track validity state on input after invalid submission attempt', function() {
this.$form.submit();
const emailInputModel = this.fieldErrors.state.inputs[1];
const fieldState = emailInputModel.state;
const emailInputElement = emailInputModel.inputElement;
// No input
expect(emailInputElement).toHaveClass('gl-field-error-outline');
expect(fieldState.empty).toBe(true);
expect(fieldState.valid).toBe(false);
// Then invalid input
emailInputElement.val('not-a-valid-email').keyup();
expect(emailInputElement).toHaveClass('gl-field-error-outline');
expect(fieldState.empty).toBe(false);
expect(fieldState.valid).toBe(false);
// Then valid input
emailInputElement.val('email@gitlab.com').keyup();
expect(emailInputElement).not.toHaveClass('gl-field-error-outline');
expect(fieldState.empty).toBe(false);
expect(fieldState.valid).toBe(true);
// Then invalid input
emailInputElement.val('not-a-valid-email').keyup();
expect(emailInputElement).toHaveClass('gl-field-error-outline');
expect(fieldState.empty).toBe(false);
expect(fieldState.valid).toBe(false);
// Then empty input
emailInputElement.val('').keyup();
expect(emailInputElement).toHaveClass('gl-field-error-outline');
expect(fieldState.empty).toBe(true);
expect(fieldState.valid).toBe(false);
// Then valid input
emailInputElement.val('email@gitlab.com').keyup();
expect(emailInputElement).not.toHaveClass('gl-field-error-outline');
expect(fieldState.empty).toBe(false);
expect(fieldState.valid).toBe(true);
});
it('should properly track validity state on input after invalid submission attempt', function() {
this.$form.submit();
const emailInputModel = this.fieldErrors.state.inputs[1];
const fieldState = emailInputModel.state;
const emailInputElement = emailInputModel.inputElement;
// No input
expect(emailInputElement).toHaveClass('gl-field-error-outline');
expect(fieldState.empty).toBe(true);
expect(fieldState.valid).toBe(false);
// Then invalid input
emailInputElement.val('not-a-valid-email').keyup();
expect(emailInputElement).toHaveClass('gl-field-error-outline');
expect(fieldState.empty).toBe(false);
expect(fieldState.valid).toBe(false);
// Then valid input
emailInputElement.val('email@gitlab.com').keyup();
expect(emailInputElement).not.toHaveClass('gl-field-error-outline');
expect(fieldState.empty).toBe(false);
expect(fieldState.valid).toBe(true);
// Then invalid input
emailInputElement.val('not-a-valid-email').keyup();
expect(emailInputElement).toHaveClass('gl-field-error-outline');
expect(fieldState.empty).toBe(false);
expect(fieldState.valid).toBe(false);
// Then empty input
emailInputElement.val('').keyup();
expect(emailInputElement).toHaveClass('gl-field-error-outline');
expect(fieldState.empty).toBe(true);
expect(fieldState.valid).toBe(false);
// Then valid input
emailInputElement.val('email@gitlab.com').keyup();
expect(emailInputElement).not.toHaveClass('gl-field-error-outline');
expect(fieldState.empty).toBe(false);
expect(fieldState.valid).toBe(true);
});
 
it('should properly infer error messages', function() {
this.$form.submit();
const trackedInputs = this.fieldErrors.state.inputs;
const inputHasTitle = trackedInputs[1];
const hasTitleErrorElem = inputHasTitle.inputElement.siblings('.gl-field-error');
const inputNoTitle = trackedInputs[2];
const noTitleErrorElem = inputNoTitle.inputElement.siblings('.gl-field-error');
it('should properly infer error messages', function() {
this.$form.submit();
const trackedInputs = this.fieldErrors.state.inputs;
const inputHasTitle = trackedInputs[1];
const hasTitleErrorElem = inputHasTitle.inputElement.siblings('.gl-field-error');
const inputNoTitle = trackedInputs[2];
const noTitleErrorElem = inputNoTitle.inputElement.siblings('.gl-field-error');
 
expect(noTitleErrorElem.text()).toBe('This field is required.');
expect(hasTitleErrorElem.text()).toBe('Please provide a valid email address.');
});
expect(noTitleErrorElem.text()).toBe('This field is required.');
expect(hasTitleErrorElem.text()).toBe('Please provide a valid email address.');
});
})(window.gl || (window.gl = {}));
});
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment