debian-mirror-gitlab/app/assets/javascripts/vue_shared/directives/validation.js

133 lines
4.3 KiB
JavaScript
Raw Normal View History

2021-01-29 00:20:46 +05:30
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: {
2021-03-08 18:12:59 +05:30
isInvalid: (el) => el.validity?.valueMissing,
2021-01-29 00:20:46 +05:30
message: s__('Please fill out this field.'),
},
urlTypeMismatch: {
2021-03-08 18:12:59 +05:30
isInvalid: (el) => el.type === 'url' && el.validity?.typeMismatch,
2021-01-29 00:20:46 +05:30
message: s__('Please enter a valid URL format, ex: http://www.example.com/home'),
},
};
const getFeedbackForElement = (feedbackMap, el) =>
2021-03-08 18:12:59 +05:30
Object.values(feedbackMap).find((f) => f.isInvalid(el))?.message || el.validationMessage;
2021-01-29 00:20:46 +05:30
2021-03-08 18:12:59 +05:30
const focusFirstInvalidInput = (e) => {
2021-01-29 00:20:46 +05:30
const { target: formEl } = e;
const invalidInput = formEl.querySelector('input:invalid');
if (invalidInput) {
invalidInput.focus();
}
};
2021-03-08 18:12:59 +05:30
const isEveryFieldValid = (form) => Object.values(form.fields).every(({ state }) => state === true);
2021-01-29 00:20:46 +05:30
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
*/
2021-03-11 19:13:27 +05:30
export default function initValidation(customFeedbackMap = {}) {
2021-01-29 00:20:46 +05:30
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 });
},
};
}