summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md1
-rw-r--r--app/assets/javascripts/dispatcher.js.es62
-rw-r--r--app/assets/javascripts/gl_field_error.js.es6164
-rw-r--r--app/assets/javascripts/gl_field_errors.js.es6130
-rw-r--r--app/assets/stylesheets/framework/forms.scss32
-rw-r--r--app/assets/stylesheets/pages/login.scss30
-rw-r--r--app/views/admin/appearances/preview.html.haml2
-rw-r--r--app/views/devise/confirmations/new.html.haml2
-rw-r--r--app/views/devise/passwords/edit.html.haml2
-rw-r--r--app/views/devise/passwords/new.html.haml2
-rw-r--r--app/views/devise/sessions/_new_base.html.haml2
-rw-r--r--app/views/devise/sessions/_new_crowd.html.haml2
-rw-r--r--app/views/devise/sessions/_new_ldap.html.haml2
-rw-r--r--app/views/devise/sessions/two_factor.html.haml2
-rw-r--r--app/views/devise/shared/_signup_box.html.haml4
-rw-r--r--app/views/devise/unlocks/new.html.haml2
-rw-r--r--app/views/groups/edit.html.haml2
-rw-r--r--app/views/groups/new.html.haml2
-rw-r--r--app/views/shared/_group_form.html.haml6
-rw-r--r--spec/javascripts/fixtures/gl_field_errors.html.haml4
-rw-r--r--spec/javascripts/gl_field_errors_spec.js.es64
21 files changed, 225 insertions, 174 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c9e4d24e569..7efa9efa4f8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -57,6 +57,7 @@ entry.
- Initialize Sidekiq with the list of queues used by GitLab
- Refactor email, use setter method instead AR callbacks for email attribute (Semyon Pupkov)
- Shortened merge request modal to let clipboard button not overlap
+- Adds JavaScript validation for group path editing field
- In all filterable drop downs, put input field in focus only after load is complete (Ido @leibo)
- Improve search query parameter naming in /admin/users !7115 (YarNayar)
- Fix table pagination to be responsive
diff --git a/app/assets/javascripts/dispatcher.js.es6 b/app/assets/javascripts/dispatcher.js.es6
index ff8b8f6d0ae..8e4fd1f19ba 100644
--- a/app/assets/javascripts/dispatcher.js.es6
+++ b/app/assets/javascripts/dispatcher.js.es6
@@ -299,7 +299,7 @@
};
Dispatcher.prototype.initFieldErrors = function() {
- $('.show-gl-field-errors').each((i, form) => {
+ $('.gl-show-field-errors').each((i, form) => {
new gl.GlFieldErrors(form);
});
};
diff --git a/app/assets/javascripts/gl_field_error.js.es6 b/app/assets/javascripts/gl_field_error.js.es6
new file mode 100644
index 00000000000..f7cbecc0385
--- /dev/null
+++ b/app/assets/javascripts/gl_field_error.js.es6
@@ -0,0 +1,164 @@
+/* eslint-disable no-param-reassign */
+((global) => {
+ /*
+ * This class overrides the browser's validation error bubbles, displaying custom
+ * error messages for invalid fields instead. To begin validating any form, add the
+ * class `gl-show-field-errors` to the form element, and ensure error messages are
+ * declared in each inputs' `title` attribute. If no title is declared for an invalid
+ * field the user attempts to submit, "This field is required." will be shown by default.
+ *
+ * Opt not to validate certain fields by adding the class `gl-field-error-ignore` to the input.
+ *
+ * Set a custom error anchor for error message to be injected after with the
+ * class `gl-field-error-anchor`
+ *
+ * Examples:
+ *
+ * Basic:
+ *
+ * <form class='gl-show-field-errors'>
+ * <input type='text' name='username' title='Username is required.'/>
+ * </form>
+ *
+ * Ignore specific inputs (e.g. UsernameValidator):
+ *
+ * <form class='gl-show-field-errors'>
+ * <div class="form-group>
+ * <input type='text' class='gl-field-errors-ignore' pattern='[a-zA-Z0-9-_]+'/>
+ * </div>
+ * <div class="form-group">
+ * <input type='text' name='username' title='Username is required.'/>
+ * </div>
+ * </form>
+ *
+ * Custom Error Anchor (allows error message to be injected after specified element):
+ *
+ * <form class='gl-show-field-errors'>
+ * <div class="form-group gl-field-error-anchor">
+ * <input type='text' name='username' title='Username is required.'/>
+ * // Error message typically injected here
+ * </div>
+ * // Error message now injected here
+ * </form>
+ *
+ * */
+
+ /*
+ * Regex Patterns in use:
+ *
+ * Only alphanumeric: : "[a-zA-Z0-9]+"
+ * No special characters : "[a-zA-Z0-9-_]+",
+ *
+ * */
+
+ const errorMessageClass = 'gl-field-error';
+ const inputErrorClass = 'gl-field-error-outline';
+ const errorAnchorSelector = '.gl-field-error-anchor';
+ const ignoreInputSelector = '.gl-field-error-ignore';
+
+ class GlFieldError {
+ constructor({ input, formErrors }) {
+ this.inputElement = $(input);
+ this.inputDomElement = this.inputElement.get(0);
+ this.form = formErrors;
+ this.errorMessage = this.inputElement.attr('title') || 'This field is required.';
+ this.fieldErrorElement = $(`<p class='${errorMessageClass} hide'>${this.errorMessage}</p>`);
+
+ this.state = {
+ valid: false,
+ empty: true,
+ };
+
+ this.initFieldValidation();
+ }
+
+ initFieldValidation() {
+ const customErrorAnchor = this.inputElement.parents(errorAnchorSelector);
+ const errorAnchor = customErrorAnchor.length ? customErrorAnchor : this.inputElement;
+
+ // hidden when injected into DOM
+ errorAnchor.after(this.fieldErrorElement);
+ this.inputElement.off('invalid').on('invalid', this.handleInvalidSubmit.bind(this));
+ this.scopedSiblings = this.safelySelectSiblings();
+ }
+
+ safelySelectSiblings() {
+ // Apply `ignoreSelector` in markup to siblings whose visibility should not be toggled
+ const unignoredSiblings = this.inputElement.siblings(`p:not(${ignoreInputSelector})`);
+ const parentContainer = this.inputElement.parent('.form-group');
+
+ // Only select siblings when they're scoped within a form-group with one input
+ const safelyScoped = parentContainer.length && parentContainer.find('input').length === 1;
+
+ return safelyScoped ? unignoredSiblings : this.fieldErrorElement;
+ }
+
+ renderValidity() {
+ this.renderClear();
+
+ if (this.state.valid) {
+ this.renderValid();
+ } else if (this.state.empty) {
+ this.renderEmpty();
+ } else if (!this.state.valid) {
+ this.renderInvalid();
+ }
+ }
+
+ handleInvalidSubmit(event) {
+ event.preventDefault();
+ const currentValue = this.accessCurrentValue();
+ this.state.valid = false;
+ this.state.empty = currentValue === '';
+
+ this.renderValidity();
+ this.form.focusOnFirstInvalid.apply(this.form);
+ // For UX, wait til after first invalid submission to check each keyup
+ this.inputElement.off('keyup.fieldValidator')
+ .on('keyup.fieldValidator', this.updateValidity.bind(this));
+ }
+
+ /* Get or set current input value */
+ accessCurrentValue(newVal) {
+ return newVal ? this.inputElement.val(newVal) : this.inputElement.val();
+ }
+
+ getInputValidity() {
+ return this.inputDomElement.validity.valid;
+ }
+
+ updateValidity() {
+ const inputVal = this.accessCurrentValue();
+ this.state.empty = !inputVal.length;
+ this.state.valid = this.getInputValidity();
+ this.renderValidity();
+ }
+
+ renderValid() {
+ return this.renderClear();
+ }
+
+ renderEmpty() {
+ return this.renderInvalid();
+ }
+
+ renderInvalid() {
+ this.inputElement.addClass(inputErrorClass);
+ this.scopedSiblings.hide();
+ return this.fieldErrorElement.show();
+ }
+
+ renderClear() {
+ const inputVal = this.accessCurrentValue();
+ if (!inputVal.split(' ').length) {
+ const trimmedInput = inputVal.trim();
+ this.accessCurrentValue(trimmedInput);
+ }
+ this.inputElement.removeClass(inputErrorClass);
+ this.scopedSiblings.hide();
+ this.fieldErrorElement.hide();
+ }
+ }
+
+ global.GlFieldError = GlFieldError;
+})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/gl_field_errors.js.es6 b/app/assets/javascripts/gl_field_errors.js.es6
index be6c3ec274f..6ce392d2a5b 100644
--- a/app/assets/javascripts/gl_field_errors.js.es6
+++ b/app/assets/javascripts/gl_field_errors.js.es6
@@ -1,131 +1,9 @@
/* eslint-disable */
-((global) => {
- /*
- * This class overrides the browser's validation error bubbles, displaying custom
- * error messages for invalid fields instead. To begin validating any form, add the
- * class `show-gl-field-errors` to the form element, and ensure error messages are
- * declared in each inputs' title attribute.
- *
- * Example:
- *
- * <form class='show-gl-field-errors'>
- * <input type='text' name='username' title='Username is required.'/>
- *</form>
- *
- * */
-
- const errorMessageClass = 'gl-field-error';
- const inputErrorClass = 'gl-field-error-outline';
-
- class GlFieldError {
- constructor({ input, formErrors }) {
- this.inputElement = $(input);
- this.inputDomElement = this.inputElement.get(0);
- this.form = formErrors;
- this.errorMessage = this.inputElement.attr('title') || 'This field is required.';
- this.fieldErrorElement = $(`<p class='${errorMessageClass} hide'>${ this.errorMessage }</p>`);
-
- this.state = {
- valid: false,
- empty: true
- };
-
- this.initFieldValidation();
- }
-
- initFieldValidation() {
- // hidden when injected into DOM
- this.inputElement.after(this.fieldErrorElement);
- this.inputElement.off('invalid').on('invalid', this.handleInvalidSubmit.bind(this));
- this.scopedSiblings = this.safelySelectSiblings();
- }
-
- safelySelectSiblings() {
- // Apply `ignoreSelector` in markup to siblings whose visibility should not be toggled with input validity
- const ignoreSelector = '.validation-ignore';
- const unignoredSiblings = this.inputElement.siblings(`p:not(${ignoreSelector})`);
- const parentContainer = this.inputElement.parent('.form-group');
- // Only select siblings when they're scoped within a form-group with one input
- const safelyScoped = parentContainer.length && parentContainer.find('input').length === 1;
-
- return safelyScoped ? unignoredSiblings : this.fieldErrorElement;
- }
-
- renderValidity() {
- this.renderClear();
-
- if (this.state.valid) {
- return this.renderValid();
- }
-
- if (this.state.empty) {
- return this.renderEmpty();
- }
-
- if (!this.state.valid) {
- return this.renderInvalid();
- }
-
- }
+//= require gl_field_error
- handleInvalidSubmit(event) {
- event.preventDefault();
- const currentValue = this.accessCurrentValue();
- this.state.valid = false;
- this.state.empty = currentValue === '';
-
- this.renderValidity();
- this.form.focusOnFirstInvalid.apply(this.form);
- // For UX, wait til after first invalid submission to check each keyup
- this.inputElement.off('keyup.field_validator')
- .on('keyup.field_validator', this.updateValidity.bind(this));
-
- }
-
- /* Get or set current input value */
- accessCurrentValue(newVal) {
- return newVal ? this.inputElement.val(newVal) : this.inputElement.val();
- }
-
- getInputValidity() {
- return this.inputDomElement.validity.valid;
- }
-
- updateValidity() {
- const inputVal = this.accessCurrentValue();
- this.state.empty = !inputVal.length;
- this.state.valid = this.getInputValidity();
- this.renderValidity();
- }
-
- renderValid() {
- return this.renderClear();
- }
-
- renderEmpty() {
- return this.renderInvalid();
- }
-
- renderInvalid() {
- this.inputElement.addClass(inputErrorClass);
- this.scopedSiblings.hide();
- return this.fieldErrorElement.show();
- }
-
- renderClear() {
- const inputVal = this.accessCurrentValue();
- if (!inputVal.split(' ').length) {
- const trimmedInput = inputVal.trim();
- this.accessCurrentValue(trimmedInput);
- }
- this.inputElement.removeClass(inputErrorClass);
- this.scopedSiblings.hide();
- this.fieldErrorElement.hide();
- }
- }
-
- const customValidationFlag = 'no-gl-field-errors';
+((global) => {
+ const customValidationFlag = 'gl-field-error-ignore';
class GlFieldErrors {
constructor(form) {
@@ -144,7 +22,7 @@
this.state.inputs = this.form.find(validateSelectors).toArray()
.filter((input) => !input.classList.contains(customValidationFlag))
- .map((input) => new GlFieldError({ input, formErrors: this }));
+ .map((input) => new global.GlFieldError({ input, formErrors: this }));
this.form.on('submit', this.catchInvalidFormSubmit);
}
diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss
index 761c07384f4..f0727e9688a 100644
--- a/app/assets/stylesheets/framework/forms.scss
+++ b/app/assets/stylesheets/framework/forms.scss
@@ -136,3 +136,35 @@ label {
color: $red-normal;
}
+.gl-show-field-errors {
+ .gl-field-success-outline {
+ border: 1px solid $green-normal;
+
+ &:focus {
+ box-shadow: 0 0 0 1px $green-normal inset, 0 1px 1px rgba(0, 0, 0, 0.075) inset, 0 0 4px 0 $green-normal;
+ border: 0 none;
+ }
+ }
+
+ .gl-field-error-outline {
+ border: 1px solid $red-normal;
+
+ &:focus {
+ box-shadow: 0 0 0 1px $red-normal inset, 0 1px 1px rgba(0, 0, 0, 0.075) inset, 0 0 4px 0 rgba(210, 40, 82, 0.6);
+ border: 0 none;
+ }
+ }
+
+ .gl-field-success-message {
+ color: $green-normal;
+ }
+
+ .gl-field-error-message {
+ color: $red-normal;
+ }
+
+ .gl-field-hint {
+ color: $gl-text-color;
+ }
+}
+
diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss
index a2f5c6c6bd3..10f67b47998 100644
--- a/app/assets/stylesheets/pages/login.scss
+++ b/app/assets/stylesheets/pages/login.scss
@@ -75,43 +75,17 @@
.login-body {
font-size: 13px;
-
input + p {
margin-top: 5px;
}
- .gl-field-success-outline {
- border: 1px solid $green-normal;
-
- &:focus {
- box-shadow: 0 0 0 1px $green-normal inset, 0 1px 1px rgba(0, 0, 0, 0.075) inset, 0 0 4px 0 $green-normal;
- border: 0 none;
- }
- }
-
- .gl-field-error-outline {
- border: 1px solid $red-normal;
-
- &:focus {
- box-shadow: 0 0 0 1px $red-normal inset, 0 1px 1px rgba(0, 0, 0, 0.075) inset, 0 0 4px 0 rgba(210, 40, 82, 0.6);
- border: 0 none;
- }
- }
-
- .username .validation-success,
- .gl-field-success-message {
+ .username .validation-success {
color: $green-normal;
}
- .username .validation-error,
- .gl-field-error-message {
+ .username .validation-error {
color: $red-normal;
}
-
- .gl-field-hint {
- color: $gl-text-color;
- }
-
}
}
diff --git a/app/views/admin/appearances/preview.html.haml b/app/views/admin/appearances/preview.html.haml
index acbe17036f7..1af7dd5bb67 100644
--- a/app/views/admin/appearances/preview.html.haml
+++ b/app/views/admin/appearances/preview.html.haml
@@ -1,6 +1,6 @@
= render 'devise/shared/tab_single', tab_title: 'Sign in preview'
.login-box
- %form.show-gl-field-errors
+ %form.gl-show-field-errors
.form-group
= label_tag :login
= text_field_tag :login, nil, class: "form-control top", title: 'Please provide your username or email address.'
diff --git a/app/views/devise/confirmations/new.html.haml b/app/views/devise/confirmations/new.html.haml
index 5d25dd398d6..73e70dc63e5 100644
--- a/app/views/devise/confirmations/new.html.haml
+++ b/app/views/devise/confirmations/new.html.haml
@@ -1,7 +1,7 @@
= render 'devise/shared/tab_single', tab_title: 'Resend confirmation instructions'
.login-box
.login-body
- = form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post, class: 'show-gl-field-errors' }) do |f|
+ = form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post, class: 'gl-show-field-errors' }) do |f|
.devise-errors
= devise_error_messages!
.form-group
diff --git a/app/views/devise/passwords/edit.html.haml b/app/views/devise/passwords/edit.html.haml
index b518fae7c95..5e189e6dc54 100644
--- a/app/views/devise/passwords/edit.html.haml
+++ b/app/views/devise/passwords/edit.html.haml
@@ -1,7 +1,7 @@
= render 'devise/shared/tab_single', tab_title:'Change your password'
.login-box
.login-body
- = form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put, class: 'show-gl-field-errors' }) do |f|
+ = form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put, class: 'gl-show-field-errors' }) do |f|
.devise-errors
= devise_error_messages!
= f.hidden_field :reset_password_token
diff --git a/app/views/devise/passwords/new.html.haml b/app/views/devise/passwords/new.html.haml
index 1fcfd06419a..99ce13adf74 100644
--- a/app/views/devise/passwords/new.html.haml
+++ b/app/views/devise/passwords/new.html.haml
@@ -1,7 +1,7 @@
= render 'devise/shared/tab_single', tab_title: 'Reset Password'
.login-box
.login-body
- = form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post, class: 'show-gl-field-errors' }) do |f|
+ = form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post, class: 'gl-show-field-errors' }) do |f|
.devise-errors
= devise_error_messages!
.form-group
diff --git a/app/views/devise/sessions/_new_base.html.haml b/app/views/devise/sessions/_new_base.html.haml
index 5fd896f6835..21b89580818 100644
--- a/app/views/devise/sessions/_new_base.html.haml
+++ b/app/views/devise/sessions/_new_base.html.haml
@@ -1,4 +1,4 @@
-= form_for(resource, as: resource_name, url: session_path(resource_name), html: { class: 'new_user show-gl-field-errors', 'aria-live' => 'assertive'}) do |f|
+= form_for(resource, as: resource_name, url: session_path(resource_name), html: { class: 'new_user gl-show-field-errors', 'aria-live' => 'assertive'}) do |f|
%div.form-group
= f.label "Username or email", for: :login
= f.text_field :login, class: "form-control top", autofocus: "autofocus", autocapitalize: "off", autocorrect: "off", required: true, title: "This field is required."
diff --git a/app/views/devise/sessions/_new_crowd.html.haml b/app/views/devise/sessions/_new_crowd.html.haml
index 1d381ad7893..a6cadbcbdff 100644
--- a/app/views/devise/sessions/_new_crowd.html.haml
+++ b/app/views/devise/sessions/_new_crowd.html.haml
@@ -1,4 +1,4 @@
-= form_tag(omniauth_authorize_path(:user, :crowd), id: 'new_crowd_user', class: 'show-gl-field-errors') do
+= form_tag(omniauth_authorize_path(:user, :crowd), id: 'new_crowd_user', class: 'gl-show-field-errors') do
.form-group
= label_tag :username, 'Username or email'
= text_field_tag :username, nil, {class: "form-control top", title: "This field is required", autofocus: "autofocus", required: true }
diff --git a/app/views/devise/sessions/_new_ldap.html.haml b/app/views/devise/sessions/_new_ldap.html.haml
index c18bc2ac413..3ab5461f929 100644
--- a/app/views/devise/sessions/_new_ldap.html.haml
+++ b/app/views/devise/sessions/_new_ldap.html.haml
@@ -1,4 +1,4 @@
-= form_tag(omniauth_callback_path(:user, server['provider_name']), id: 'new_ldap_user', class: "show-gl-field-errors") do
+= form_tag(omniauth_callback_path(:user, server['provider_name']), id: 'new_ldap_user', class: "gl-show-field-errors") do
.form-group
= label_tag :username, "#{server['label']} Username"
= text_field_tag :username, nil, {class: "form-control top", title: "This field is required.", autofocus: "autofocus", required: true }
diff --git a/app/views/devise/sessions/two_factor.html.haml b/app/views/devise/sessions/two_factor.html.haml
index fd77cdbee2e..2cadc424668 100644
--- a/app/views/devise/sessions/two_factor.html.haml
+++ b/app/views/devise/sessions/two_factor.html.haml
@@ -7,7 +7,7 @@
.login-box
.login-body
- if @user.two_factor_otp_enabled?
- = form_for(resource, as: resource_name, url: session_path(resource_name), method: :post, html: { class: 'edit_user show-gl-field-errors' }) do |f|
+ = form_for(resource, as: resource_name, url: session_path(resource_name), method: :post, html: { class: 'edit_user gl-show-field-errors' }) do |f|
- resource_params = params[resource_name].presence || params
= f.hidden_field :remember_me, value: resource_params.fetch(:remember_me, 0)
%div
diff --git a/app/views/devise/shared/_signup_box.html.haml b/app/views/devise/shared/_signup_box.html.haml
index d0bbcf3115e..7c68e3266e5 100644
--- a/app/views/devise/shared/_signup_box.html.haml
+++ b/app/views/devise/shared/_signup_box.html.haml
@@ -1,6 +1,6 @@
#register-pane.login-box{ role: 'tabpanel', class: 'tab-pane' }
.login-body
- = form_for(resource, as: "new_#{resource_name}", url: registration_path(resource_name), html: { class: "new_new_user show-gl-field-errors", "aria-live" => "assertive" }) do |f|
+ = form_for(resource, as: "new_#{resource_name}", url: registration_path(resource_name), html: { class: "new_new_user gl-show-field-errors", "aria-live" => "assertive" }) do |f|
.devise-errors
= devise_error_messages!
%div.form-group
@@ -8,7 +8,7 @@
= f.text_field :name, class: "form-control top", required: true, title: "This field is required."
%div.username.form-group
= f.label :username
- = f.text_field :username, class: "form-control middle no-gl-field-error", pattern: "[a-zA-Z0-9]+", required: true, title: 'Please create a username with only alphanumeric characters.'
+ = f.text_field :username, class: "form-control middle", pattern: "[a-zA-Z0-9]+", required: true, title: 'Please create a username with only alphanumeric characters.'
%p.validation-error.hide Username is already taken.
%p.validation-success.hide Username is available.
%p.validation-pending.hide Checking username availability...
diff --git a/app/views/devise/unlocks/new.html.haml b/app/views/devise/unlocks/new.html.haml
index 49b2f77111f..b2f48a4e0bf 100644
--- a/app/views/devise/unlocks/new.html.haml
+++ b/app/views/devise/unlocks/new.html.haml
@@ -1,7 +1,7 @@
= render 'devise/shared/tab_single', tab_title: 'Resend unlock instructions'
.login-box
.login-body
- = form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post, class: 'show-gl-field-errors' }) do |f|
+ = form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post, class: 'gl-show-field-errors' }) do |f|
.devise-errors
= devise_error_messages!
.form-group.append-bottom-20
diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml
index f84ac37fa8f..2f90c19d4b4 100644
--- a/app/views/groups/edit.html.haml
+++ b/app/views/groups/edit.html.haml
@@ -2,7 +2,7 @@
.panel-heading
Group settings
.panel-body
- = form_for @group, html: { multipart: true, class: "form-horizontal" }, authenticity_token: true do |f|
+ = form_for @group, html: { multipart: true, class: "form-horizontal gl-show-field-errors" }, authenticity_token: true do |f|
= form_errors(@group)
= render 'shared/group_form', f: f
diff --git a/app/views/groups/new.html.haml b/app/views/groups/new.html.haml
index 2b8bc269e64..d19eaa6add9 100644
--- a/app/views/groups/new.html.haml
+++ b/app/views/groups/new.html.haml
@@ -5,7 +5,7 @@
New Group
%hr
-= form_for @group, html: { class: 'group-form form-horizontal' } do |f|
+= form_for @group, html: { class: 'group-form form-horizontal gl-show-field-errors' } do |f|
= form_errors(@group)
= render 'shared/group_form', f: f, autofocus: true
diff --git a/app/views/shared/_group_form.html.haml b/app/views/shared/_group_form.html.haml
index 67072b9fc2a..ba25e09d638 100644
--- a/app/views/shared/_group_form.html.haml
+++ b/app/views/shared/_group_form.html.haml
@@ -9,11 +9,13 @@
= f.label :path, class: 'control-label' do
Group path
.col-sm-10
- .input-group
+ .input-group.gl-field-error-anchor
.input-group-addon
= root_url
= f.text_field :path, placeholder: 'open-source', class: 'form-control',
- autofocus: local_assigns[:autofocus] || false
+ autofocus: local_assigns[:autofocus] || false, pattern: "[a-zA-Z0-9-_]+",
+ required: true, title: 'Please choose a group name with no special characters.'
+
- if @group.persisted?
.alert.alert-warning.prepend-top-10
%ul
diff --git a/spec/javascripts/fixtures/gl_field_errors.html.haml b/spec/javascripts/fixtures/gl_field_errors.html.haml
index 2526e5e33a5..69445b61367 100644
--- a/spec/javascripts/fixtures/gl_field_errors.html.haml
+++ b/spec/javascripts/fixtures/gl_field_errors.html.haml
@@ -1,4 +1,4 @@
-%form.show-gl-field-errors{action: 'submit', method: 'post'}
+%form.gl-show-field-errors{action: 'submit', method: 'post'}
.form-group
%input.required-text{required: true, type: 'text'} Text
.form-group
@@ -10,6 +10,6 @@
.form-group
%input.hidden{ type:'hidden' }
.form-group
- %input.custom.no-gl-field-errors{ type:'text' } Custom, do not validate
+ %input.custom.gl-field-error-ignore{ type:'text' } Custom, do not validate
.form-group
%input.submit{type: 'submit'} Submit
diff --git a/spec/javascripts/gl_field_errors_spec.js.es6 b/spec/javascripts/gl_field_errors_spec.js.es6
index 4bdd72800ea..0713e30e485 100644
--- a/spec/javascripts/gl_field_errors_spec.js.es6
+++ b/spec/javascripts/gl_field_errors_spec.js.es6
@@ -8,7 +8,7 @@
describe('GL Style Field Errors', function() {
beforeEach(function() {
fixture.load('gl_field_errors.html');
- const $form = this.$form = $('form.show-gl-field-errors');
+ const $form = this.$form = $('form.gl-show-field-errors');
this.fieldErrors = new global.GlFieldErrors($form);
});
@@ -21,7 +21,7 @@
});
it('should ignore elements with custom error handling', function() {
- const customErrorFlag = 'no-gl-field-errors';
+ const customErrorFlag = 'gl-field-error-ignore';
const customErrorElem = $(`.${customErrorFlag}`);
expect(customErrorElem.length).toBe(1);