/** * Copyright (c) HashiCorp, Inc. * SPDX-License-Identifier: BUSL-1.1 */ import Modifier from 'ember-modifier'; import { action } from '@ember/object'; import { registerDestructor } from '@ember/destroyable'; class ValidationError extends Error {} function cleanup(instance) { if (instance && instance?.element) { instance?.element?.removeEventListener('input', instance?.listen); instance?.element?.removeEventListener('blur', instance?.reset); } } export default class ValidateModifier extends Modifier { item = null; hash = null; validate(value, validations = {}) { if (Object.keys(validations).length === 0) { return; } const errors = {}; Object.entries(this.hash.validations) // filter out strings, for now these are helps, but ain't great if someone has a item.help .filter(([key, value]) => typeof value !== 'string') .forEach(([key, item]) => { // optionally set things for you if (this.item) { this.item[key] = value; } (item || []).forEach((validation) => { const re = new RegExp(validation.test); if (!re.test(value)) { errors[key] = new ValidationError(validation.error); } }); }); const state = this.hash.chart.state || {}; if (state.context == null) { state.context = {}; } if (Object.keys(errors).length > 0) { state.context.errors = errors; this.hash.chart.dispatch('ERROR', state.context); } else { state.context.errors = null; this.hash.chart.dispatch('RESET', state.context); } } @action reset(e) { if (e.target.value.length === 0) { const state = this.hash.chart.state; if (!state.context) { state.context = {}; } if (!state.context.errors) { state.context.errors = {}; } Object.entries(this.hash.validations) // filter out strings, for now these are helps, but ain't great if someone has a item.help .filter(([key, value]) => typeof value !== 'string') .forEach(([key, item]) => { if (typeof state.context.errors[key] !== 'undefined') { delete state.context.errors[key]; } }); if (Object.keys(state.context.errors).length === 0) { state.context.errors = null; this.hash.chart.dispatch('RESET', state.context); } } } @action listen(e) { this.validate(e.target.value, this.hash.validations); } constructor(owner, args) { super(owner, args); registerDestructor(this, cleanup); } async modify(element, positional, named) { cleanup.call(this); this.element = element; this.hash = named; this.item = positional[0]; if (typeof this.hash.chart === 'undefined') { this.hash.chart = { state: { context: {}, }, dispatch: (state) => { switch (state) { case 'ERROR': this.hash.onchange(this.hash.chart.state.context.errors); break; case 'RESET': this.hash.onchange(); break; } }, }; } this.element.addEventListener('input', this.listen); this.element.addEventListener('blur', this.reset); if (this.element.value.length > 0) { await Promise.resolve(); if (this && this.element) { this.validate(this.element.value, this.hash.validations); } } } }