Created
June 13, 2019 19:44
Stimulus validations
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export default class BaseValidator { | |
constructor(value, options, errorMessages) { | |
this.value = value | |
this.options = options | |
this.errorMessages = errorMessages | |
} | |
validate() { | |
return { valid: false, message: 'Implement me!'} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Validation } from "../validators" | |
import { FormValidationController } from "./form_validation_controller" | |
export default class BulmaFormValidationController extends FormValidationController { | |
render(target, validation) { | |
let validator = validation.find((validator) => !validator.result.valid) | |
if (validator) { | |
this.renderValidation(target, validator) | |
} else { | |
this.clearValidation(target, validator) | |
} | |
} | |
clearValidation(target, validator) { | |
target.classList.remove('is-danger') | |
let parent = target.parentNode | |
let help = parent.querySelector('p.help') | |
if (help) { | |
parent.removeChild(help) | |
} | |
} | |
renderValidation(target, validator) { | |
if (target.classList.contains('is-danger')) { | |
return | |
} | |
target.classList.add('is-danger') | |
let help = document.createElement('p') | |
help.setAttribute('class', 'help is-danger') | |
let message = document.createTextNode(validator.result.message) | |
help.appendChild(message) | |
let parent = target.parentNode | |
parent.appendChild(help) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Controller } from "stimulus" | |
import { Validation } from "../validators" | |
export class FormValidationController extends Controller { | |
connect() { | |
this.validation = new Validation() | |
this.validatableInputs = [] | |
this.bindInputs() | |
this.bindForm() | |
this.errors = {} | |
} | |
disconnect() { | |
this.validatableInputs.forEach((input) => input.removeEventListener('blur', this.validate)) | |
this.element.removeEventListener('submit', this.validateAll) | |
} | |
bindForm() { | |
this.element.addEventListener('submit', this.validateAll.bind(this)) | |
} | |
bindInputs() { | |
let inputs = this.element.querySelectorAll('input') | |
inputs.forEach((input) => { | |
let input_name = input.getAttribute('id') | |
if (input_name) { | |
let validate = this.validation.validatable(input_name) | |
if (validate) { | |
input.addEventListener('blur', this.validate.bind(this)) | |
this.validatableInputs.push(input) | |
} | |
} | |
}) | |
} | |
inputValue(input) { | |
return input.value | |
} | |
validForm() { | |
let valid = Object.values(this.errors).some((value) => value) | |
return !valid | |
} | |
validateAll(event) { | |
this.validatableInputs.forEach((input) => this.validate({ target: input })) | |
if (!this.validForm()) { | |
event.preventDefault() | |
event.stopPropagation() | |
let submit = event.target.querySelector('[type="submit"]') | |
if (submit) { | |
submit.disabled = false | |
} | |
} | |
} | |
validate(event) { | |
let inputValue = this.inputValue(event.target) | |
let inputId = event.target.getAttribute('id') | |
let validationResult = this.validation.validate(inputValue, inputId) | |
let validator = validationResult.find((validator) => !validator.result.valid) | |
this.errors[inputId] = !!validator | |
this.render(event.target, validationResult) | |
} | |
render(target, validation) { | |
console.log('Implement me!') | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import BaseValidator from "./base_validator" | |
export default class NumericalityValidator extends BaseValidator { | |
validate() { | |
let valid = true | |
let errorMessage = '' | |
if (!this.value || this.value === '') { | |
valid = false | |
errorMessage = this.errorMessages['not_a_number'] | |
} else { | |
let number = Number(this.value) | |
Object.keys(this.options).some((key) => { | |
let result = this.verifyNumber(number, key, this.options[key]) | |
if (!result) { | |
valid = result | |
errorMessage = this.getErrorMessage(key) | |
return !result | |
} | |
}) | |
} | |
return { valid: valid, message: errorMessage } | |
} | |
verifyNumber(number, option, value) { | |
if (option === 'only_integer' && value) { | |
return Number.isInteger(number) | |
} else if (option === 'greater_than') { | |
return number > value | |
} else { | |
false | |
} | |
} | |
getErrorMessage(option) { | |
let errorKey = option | |
if (option === 'only_integer') { | |
errorKey = 'not_an_integer' | |
} | |
return this.errorMessages[errorKey] | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import BaseValidator from "./base_validator" | |
export default class PresenceValidator extends BaseValidator { | |
validate() { | |
let valid = false | |
let errorMessage = '' | |
if (this.value) { | |
let cleanValue = this.value.trim() | |
valid = cleanValue !== '' | |
} | |
if (!valid) { | |
errorMessage = this.errorMessages['blank'] | |
} | |
return { valid: valid, message: errorMessage } | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class ValidationToJs | |
VALIDATORS = ["PresenceValidator", "NumericalityValidator"].freeze | |
CHECKS = { | |
presence: [:blank], | |
numericality: [:greater_than, :not_an_integer, :not_a_number] | |
}.freeze | |
def model_validations(model) | |
validations = {} | |
model_name = model.to_s.demodulize.downcase | |
model.validators.each do |validator| | |
validator_name = validator.class.to_s.demodulize | |
continue if !VALIDATORS.include?(validator_name) | |
validator.attributes.each do |attribute| | |
validator_clean_name = validator_name.downcase.sub("validator", "") | |
attribute_name = "#{model_name}_#{attribute}" | |
validations[attribute_name] ||= [] | |
validations[attribute_name].push({ | |
validator: validator_clean_name, | |
options: validator.options , | |
error_messages: error_messages(model, attribute, CHECKS[validator_clean_name.to_sym], false, validator.options) | |
}) | |
end | |
end | |
validations | |
end | |
private | |
def error_messages(model, attribute, types, full = false, options = {}) | |
model_name = model.to_s.demodulize.downcase | |
errors = ActiveModel::Errors.new(model.new) | |
messages = {} | |
Array(types).each do |type| | |
value = options[type] | |
messages[type] = error_message(errors, attribute, type, full, value.present? ? { count: value } : {}) | |
end | |
messages | |
end | |
def error_message(errors, attribute, type, full, options = {}) | |
message = errors.generate_message(attribute, type, options) | |
errors.full_message(attribute, message) if full | |
message | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<% models = [Expense] %> | |
<% validation = ValidationToJs.new %> | |
<% validations = {} %> | |
<% models.each do |model| %> | |
<% validations.merge!(validation.model_validations(model)) %> | |
<% end %> | |
let validations = <%= validations.to_json %> | |
export default validations; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Validations from "./validations.js.erb" | |
import BaseValidator from "./base_validator" | |
import PresenceValidator from "./presence_validator" | |
import NumericalityValidator from "./numericality_validator" | |
let validators = { | |
presence: PresenceValidator, | |
numericality: NumericalityValidator | |
} | |
export class Validation { | |
validatable(input_id) { | |
return Validations[input_id] !== undefined | |
} | |
validate(value, id) { | |
let validations = Validations[id] | |
let results = validations.map((validation) => { | |
let validator = new validators[validation.validator](value, validation.options, validation.error_messages) | |
return { validator: validation.validator, result: validator.validate() } | |
}); | |
return results | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment