Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/vue_shared/directives/validation.js')
-rw-r--r--app/assets/javascripts/vue_shared/directives/validation.js132
1 files changed, 132 insertions, 0 deletions
diff --git a/app/assets/javascripts/vue_shared/directives/validation.js b/app/assets/javascripts/vue_shared/directives/validation.js
new file mode 100644
index 00000000000..09bec78edcc
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/directives/validation.js
@@ -0,0 +1,132 @@
+import { merge } from 'lodash';
+import { s__ } from '~/locale';
+
+/**
+ * Validation messages will take priority based on the property order.
+ *
+ * For example:
+ * { valueMissing: {...}, urlTypeMismatch: {...} }
+ *
+ * `valueMissing` will be displayed the user has entered a value
+ * after that, if the input is not a valid URL then `urlTypeMismatch` will show
+ */
+const defaultFeedbackMap = {
+ valueMissing: {
+ isInvalid: el => el.validity?.valueMissing,
+ message: s__('Please fill out this field.'),
+ },
+ urlTypeMismatch: {
+ isInvalid: el => el.type === 'url' && el.validity?.typeMismatch,
+ message: s__('Please enter a valid URL format, ex: http://www.example.com/home'),
+ },
+};
+
+const getFeedbackForElement = (feedbackMap, el) =>
+ Object.values(feedbackMap).find(f => f.isInvalid(el))?.message || el.validationMessage;
+
+const focusFirstInvalidInput = e => {
+ const { target: formEl } = e;
+ const invalidInput = formEl.querySelector('input:invalid');
+
+ if (invalidInput) {
+ invalidInput.focus();
+ }
+};
+
+const isEveryFieldValid = form => Object.values(form.fields).every(({ state }) => state === true);
+
+const createValidator = (context, feedbackMap) => ({ el, reportInvalidInput = false }) => {
+ const { form } = context;
+ const { name } = el;
+
+ if (!name) {
+ if (process.env.NODE_ENV === 'development') {
+ // eslint-disable-next-line no-console
+ console.warn(
+ '[gitlab] the validation directive requires the given input to have "name" attribute',
+ );
+ }
+ return;
+ }
+
+ const formField = form.fields[name];
+ const isValid = el.checkValidity();
+
+ // This makes sure we always report valid fields - this can be useful for cases where the consuming
+ // component's logic depends on certain fields being in a valid state.
+ // Invalid input, on the other hand, should only be reported once we want to display feedback to the user.
+ // (eg.: After a field has been touched and moved away from, a submit-button has been clicked, ...)
+ formField.state = reportInvalidInput ? isValid : isValid || null;
+ formField.feedback = reportInvalidInput ? getFeedbackForElement(feedbackMap, el) : '';
+
+ form.state = isEveryFieldValid(form);
+};
+
+/**
+ * Takes an object that allows to add or change custom feedback messages.
+ *
+ * The passed in object will be merged with the built-in feedback
+ * so it is possible to override a built-in message.
+ *
+ * @example
+ * validate({
+ * tooLong: {
+ * check: el => el.validity.tooLong === true,
+ * message: 'Your custom feedback'
+ * }
+ * })
+ *
+ * @example
+ * validate({
+ * valueMissing: {
+ * message: 'Your custom feedback'
+ * }
+ * })
+ *
+ * @param {Object<string, { message: string, isValid: ?function}>} customFeedbackMap
+ * @returns {{ inserted: function, update: function }} validateDirective
+ */
+export default function(customFeedbackMap = {}) {
+ const feedbackMap = merge(defaultFeedbackMap, customFeedbackMap);
+ const elDataMap = new WeakMap();
+
+ return {
+ inserted(el, binding, { context }) {
+ const { arg: showGlobalValidation } = binding;
+ const { form: formEl } = el;
+
+ const validate = createValidator(context, feedbackMap);
+ const elData = { validate, isTouched: false, isBlurred: false };
+
+ elDataMap.set(el, elData);
+
+ el.addEventListener('input', function markAsTouched() {
+ elData.isTouched = true;
+ // once the element has been marked as touched we can stop listening on the 'input' event
+ el.removeEventListener('input', markAsTouched);
+ });
+
+ el.addEventListener('blur', function markAsBlurred({ target }) {
+ if (elData.isTouched) {
+ elData.isBlurred = true;
+ validate({ el: target, reportInvalidInput: true });
+ // this event handler can be removed, since the live-feedback in `update` takes over
+ el.removeEventListener('blur', markAsBlurred);
+ }
+ });
+
+ if (formEl) {
+ formEl.addEventListener('submit', focusFirstInvalidInput);
+ }
+
+ validate({ el, reportInvalidInput: showGlobalValidation });
+ },
+ update(el, binding) {
+ const { arg: showGlobalValidation } = binding;
+ const { validate, isTouched, isBlurred } = elDataMap.get(el);
+ const showValidationFeedback = showGlobalValidation || (isTouched && isBlurred);
+
+ validate({ el, reportInvalidInput: showValidationFeedback });
+ },
+ };
+}