그냥 폼검증말고 왜 틀렸는지 이런걸 알려주는 폼검증을 만들어보자!

js 라이브러리 등등 이런게 많지만 내입맛에 맞게 수정하기 편하기 위해서는 결국 직접 만드는것이 가장 좋겠다고 판단했다
https://codepen.io/trending

 

CodePen

An online code editor, learning environment, and community for front-end web development using HTML, CSS and JavaScript code snippets, projects, and web applications.

codepen.io

여기 사이트에서 적당한 예시를 찾아서 수정했는데 정확한 url을 다시 찾으려고하니 못찾겠다...

<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="UTF-8" />
		<meta name="viewport" content="width=device-width, initial-scale=1.0" />
		<title>Document</title>
		<style>
			* {
				box-sizing: border-box;
			}

			body {
				background-color: blueviolet;
			}

			.title {
				margin-bottom: 2rem;
			}

			.hidden {
				display: none;
			}

			.icon {
				width: 24px;
				height: 24px;
				position: absolute;
				top: 32px;
				right: 5px;
				pointer-events: none;
				z-index: 2;

				&.icon-success {
					fill: green;
				}

				&.icon-error {
					fill: red;
				}
			}

			.container {
				max-width: 460px;
				margin: 3rem auto;
				padding: 3rem;
				border: 1px solid #ddd;
				border-radius: 0.25rem;
				background-color: white;
				box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
			}

			.label {
				font-weight: bold;
				display: block;
				color: #333;
				margin-bottom: 0.25rem;
				color: #2d3748;
			}

			.input {
				appearance: none;
				display: block;
				width: 100%;
				color: #2d3748;
				border: 1px solid #cbd5e0;
				line-height: 1.25;
				background-color: white;
				padding: 0.65rem 0.75rem;
				border-radius: 0.25rem;
				box-shadow: inset 0 2px 4px 0 rgba(0, 0, 0, 0.06);

				&::placeholder {
					color: #a0aec0;
				}

				&.input-error {
					border: 1px solid red;

					&:focus {
						border: 1px solid red;
					}
				}

				&:focus {
					outline: none;
					border: 1px solid #a0aec0;
					box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
					background-clip: padding-box;
				}
			}

			.input-group {
				margin-bottom: 2rem;
				position: relative;
			}

			.error-message {
				font-size: 0.85rem;
				color: red;
        display: block;
			}

			.button {
				background-color: blueviolet;
				padding: 1rem 2rem;
				border: none;
				border-radius: 0.25rem;
				color: white;
				font-weight: bold;
				display: block;
				width: 100%;
				text-align: center;
				cursor: pointer;

				&:hover {
					filter: brightness(110%);
				}
			}

			.promo {
				color: white;
				opacity: 0.75;
				margin: 1rem auto;
				max-width: 460px;
				background: rgba(255, 255, 255, 0.2);
				padding: 20px;
				border-radius: 0.25rem;

				a {
					color: white;
				}
			}
		</style>
	</head>
	<body>
		<div class="container">
			<h2 class="title">Create a new account</h2>
			<form action="#" class="form">
				<div class="input-group">
					<label for="username" class="label">이름</label>
					<input id="username" placeholder="webcrunch" type="text" class="input" name="username" formval_min="5" formval_max="15" />

					<span class="error-message"></span>
					<svg class="icon icon-success hidden" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
						<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd" />
					</svg>
					<svg class="icon icon-error hidden" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
						<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z" clip-rule="evenodd" />
					</svg>
				</div>

				<div class="input-group">
					<label for="username" class="label">셀렉트 박스</label>
					<span class="error-message"></span>

					<select name="selectBox0" id="selectBox0">
						<option value="">선택x</option>
						<option value="1">선택1</option>
						<option value="2">선택2</option>
						<option value="3">선택3</option>
						<option value="4">선택4</option>
					</select>

					<svg class="icon icon-success hidden" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
						<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd" />
					</svg>
					<svg class="icon icon-error hidden" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
						<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z" clip-rule="evenodd" />
					</svg>
				</div>

				<div class="input-group">
					<label class="label">체크박스</label>
					<span class="error-message"></span>

					<label for="checkbox1" class="label">체크박스선택1</label>
					<input id="checkbox1" name="checkbox00" type="checkbox" class="formValCheckBox" value="1" formval_min="1" formval_max="2" />
					<label for="checkbox2" class="label">체크박스선택2</label>
					<input id="checkbox2" name="checkbox00" type="checkbox" class="formValCheckBox" value="2" />
					<label for="checkbox2-1" class="label">체크박스선택2.1</label>
					<input id="checkbox2-1" name="checkbox00" type="checkbox" class="formValCheckBox" value="2.1" />

					<svg class="icon icon-success hidden" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
						<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd" />
					</svg>
					<svg class="icon icon-error hidden" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
						<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z" clip-rule="evenodd" />
					</svg>
				</div>

				<div class="input-group">
					<label class="label">체크박스</label>
					<span class="error-message"></span>

					<div>
						<label for="checkbox3" class="label">체크박스선택3</label>
						<input id="checkbox3" name="checkbox01" type="checkbox" class="" value="3" />
					</div>
					<div>
						<label for="checkbox4" class="label">체크박스선택4</label>
						<input id="checkbox4" name="checkbox01" type="checkbox" class="" value="4" />
					</div>

					<svg class="icon icon-success hidden" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
						<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd" />
					</svg>
					<svg class="icon icon-error hidden" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
						<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z" clip-rule="evenodd" />
					</svg>
				</div>

				<div class="input-group">
					<label for="email" class="label">Email</label>
					<input id="email" type="email" class="input" name="email" autocomplete placeholder="andy@web-crunch.com" />

					<span class="error-message"></span>
					<svg class="icon icon-success hidden" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
						<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd" />
					</svg>
					<svg class="icon icon-error hidden" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
						<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z" clip-rule="evenodd" />
					</svg>
				</div>

				<div class="input-group">
					<label for="password" class="label">Password</label>
					<input id="password" type="password" class="input" name="password" />

					<span class="error-message"></span>
					<svg class="icon icon-success hidden" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
						<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd" />
					</svg>
					<svg class="icon icon-error hidden" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
						<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z" clip-rule="evenodd" />
					</svg>
				</div>

				<div class="input-group">
					<label for="password_confirmation" class="label">Password Confirmation</label>
					<input id="password_confirmation" type="password" class="input" name="password_confirmation" />

					<span class="error-message"></span>
					<svg class="icon icon-success hidden" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
						<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd" />
					</svg>
					<svg class="icon icon-error hidden" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
						<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z" clip-rule="evenodd" />
					</svg>
				</div>

				<input type="submit" class="button" value="Create account" />
			</form>
		</div>

	</body>

	<script>
		class FormValidator {
			constructor(form, fields) {
				this.form = form;
				this.fields = fields;
			}

			initialize() {
				// this.validateOnEntry(); // 실시간 감지
				this.validateOnSubmit();
			}

			validateOnSubmit() {
				let self = this;

				this.form.addEventListener("submit", (e) => {
					e.preventDefault();
					self.fields.forEach((field) => {
						const input = document.getElementsByName(`${field}`);

						if (input[0].type == "checkbox") {
							self.validateFields_Checkbox(input[0], field);
						} else if (input[0].type == "select-one") {
							self.validateFields_Selectbox(input[0]);
						} else {
							self.validateFields(input);
						}
					});
				});
			}

			validateOnSubmit2() {
				// e.preventDefault();
				let self = this;
				self.fields.forEach((field) => {
					const input = document.getElementsByName(`${field}`);
					self.validateFields(input);
					if (input[0].type == "checkbox") {
						self.validateFields_Checkbox(input[0], field);
					} else if (input[0].type == "select-one") {
						self.validateFields_Selectbox(input[0]);
					} else {
						self.validateFields(input);
					}
				});
			}

			validateOnEntry() {
				let self = this;
				this.fields.forEach((field) => {
					const input = document.getElementsByName(`${field}`);

					input.addEventListener("input", (event) => {
						self.validateFields(input);
					});
				});
			}

			validateFields(fields) {
				let formval_min = fields[0].getAttribute("formval_min");
				let formval_max = fields[0].getAttribute("formval_max");

				fields.forEach((field) => {
					if (field.value.trim() === "") {
						this.setStatus(field, `${field.previousElementSibling.innerText} 을/를 입력해주세요`, "error");
					} else {
						if (field.value.trim() !== "") {
							this.setStatus(field, null, "success");
						}

						if (field.type === "text") {
							if (formval_max && formval_min) {
								if (formval_max < field.value.trim().length || formval_min > field.value.trim().length) {
									this.setStatus(field, formval_min + "자 이상 " + formval_max + "자 이하로 입력해주세요.", "error");
								}
							} else {
								if (formval_min) {
									if (formval_min > field.value.trim().length) {
										this.setStatus(field, formval_min + "자 이상 입력해주세요.", "error");
									}
								}
								if (formval_max) {
									if (formval_max < field.value.trim().length) {
										this.setStatus(field, formval_max + "자 이하로 입력해주세요.", "error");
									}
								}
							}
						}

						if (field.type === "email") {
							const re = /\S+@\S+\.\S+/;
							if (re.test(field.value)) {
								this.setStatus(field, null, "success");
							} else {
								this.setStatus(field, "이메일 형식이 맞지 않습니다.", "error");
							}
						}

						if (field.id === "password_confirmation") {
							const passwordField = this.form.querySelector("#password");

							if (field.value.trim() == "") {
								this.setStatus(field, "비밀번호를 입력해주세요.", "error");
							} else if (field.value != passwordField.value) {
								this.setStatus(field, "비밀번호 두개가 일치하지 않습니다.", "error");
							} else {
								this.setStatus(field, null, "success");
							}
						}
					}
				});
			}

			validateFields_Checkbox(field, checkboxName) {
				const selectedCheckboxLength = document.querySelectorAll(`input[name="${checkboxName}"]:checked`).length;
				let formval_min = field.getAttribute("formval_min");
				let formval_max = field.getAttribute("formval_max");

				if (selectedCheckboxLength == 0) {
					this.setStatus(field, `${field.parentElement.parentElement.querySelector("label").innerText} 을/를 선택해주세요`, "error");
				} else {
					this.setStatus(field, null, "success");
				}

				if (formval_max && formval_min) {
					if (formval_max < selectedCheckboxLength || formval_min > selectedCheckboxLength) {
						this.setStatus(field, formval_min + "개 이상 " + formval_max + "개 이하로 선택해주세요.", "error");
					}
				} else {
					if (formval_min) {
						if (formval_min > selectedCheckboxLength) {
							this.setStatus(field, formval_min + "개 이상 선택해주세요.", "error");
						}
					}
					if (formval_max) {
						if (formval_max < selectedCheckboxLength) {
							this.setStatus(field, formval_max + "개 이하로 선택해주세요.", "error");
						}
					}
				}
			}

			validateFields_Selectbox(field) {
				if (field.value.trim() === "") {
					this.setStatus(field, `${field.previousElementSibling.previousElementSibling.innerText} 을/를 선택해주세요`, "error");
				} else {
          this.setStatus(field, null, "success");
        }
			}

			setStatus(field, message, status) {
				const successIcon = field.parentElement.querySelector(".icon-success") ? field.parentElement.querySelector(".icon-success") : field.parentElement.parentElement.querySelector(".icon-success");
				const errorIcon = field.parentElement.querySelector(".icon-error") ? field.parentElement.querySelector(".icon-error") : field.parentElement.parentElement.querySelector(".icon-error");
				const errorMessage = field.parentElement.querySelector(".error-message")
					? field.parentElement.querySelector(".error-message")
					: field.parentElement.parentElement.querySelector(".error-message");

				if (status === "success") {
					if (errorIcon) {
						errorIcon.classList.add("hidden");
					}
					if (errorMessage) {
						errorMessage.innerText = "";
					}
					successIcon.classList.remove("hidden");
					field.classList.remove("input-error");
				}

				if (status === "error") {
					if (successIcon) {
						successIcon.classList.add("hidden");
					}
					errorMessage.innerText = message;
					errorIcon.classList.remove("hidden");
					field.classList.add("input-error");
				}
			}
		}

		const form = document.querySelector(".form");

		// name 명
		const fields = ["username", "email", "password", "password_confirmation", "checkbox00", "checkbox01", "selectBox0"];

		const validator = new FormValidator(form, fields);
		validator.initialize();
	</script>
</html>

이렇게 만들었는데 여기에서 입맛에 맞게 추가수정을 하면 될거같다

+ Recent posts