import { IgniteTemplate } from "../ignite-html/ignite-template.js"; import { IgniteProperty } from "../ignite-html/ignite-html.js"; import { IgniteElement } from "../ignite-html/ignite-element.js"; /** * Creates and shows a notification element on a target element. * @param {HTMLElement} target Target element to show the notification on. * @param {string} type Type of notification to show (Error|Warning|Success|Info) * @param {string|Function|IgniteProperty} msg The message to show * @param {Number} duration The number of miliseconds to show the notification for. * @returns {HTMLElement} The created notification HTMLElement. */ function notify(target, type, msg, duration) { var color = ""; if (type == "error") { color = "#ff7979"; } else if (type == "warning") { color = "#ffc107"; } else if (type == "info") { color = "#2980b9"; } else if (type == "success") { color = "#10c469"; } target.scrollIntoView({ behavior: "smooth" }); if (target._validation && target._validation.isConnected) { target._validation.remove(); } var originalPosition = window.getComputedStyle(target.parentNode).position; target.parentNode.style.setProperty("position", "relative"); var notification = document.createElement("div"); notification.classList.add("ignite-html-validate"); notification.classList.add(type); notification.style.setProperty("left", "50%"); notification.style.setProperty("top", "100%"); notification.style.setProperty("position", "absolute"); notification.style.setProperty("transform", "translate(-50%, 0)"); notification.style.setProperty("display", "flex"); notification.style.setProperty("align-items", "center"); notification.style.setProperty("flex-direction", "column"); notification.style.setProperty("z-index", 999999); notification.style.setProperty("width", "100%"); var pointer = document.createElement("div"); pointer.classList.add("pointer"); pointer.style.setProperty("width", 0); pointer.style.setProperty("height", 0); pointer.style.setProperty("background-color", "transparent"); pointer.style.setProperty("border-left", "0.5em solid transparent"); pointer.style.setProperty("border-right", "0.5em solid transparent"); pointer.style.setProperty("border-bottom", `0.5em solid ${color}`); var content = document.createElement("div"); content.classList.add("content"); content.style.setProperty("background-color", color); content.style.setProperty("padding", "0.5em"); content.style.setProperty("border-radius", "0.5em"); content.style.setProperty("box-shadow", "0 2px 2px rgba(0,0,0,0.4)"); content.style.setProperty("color", "#fff"); content.style.setProperty("text-align", "center"); if (msg instanceof Function) { content.innerHTML = msg(); } else if (msg instanceof IgniteProperty) { content.innerHTML = msg.value; } else { content.innerHTML = msg.toString(); } notification.appendChild(pointer); notification.appendChild(content); if (target.nextSibling) { target.parentNode.insertBefore(notification, target.nextSibling); } else { target.parentNode.appendChild(notification); } target._validation = notification; if (duration != -1) { setTimeout(() => { if (notification && notification.isConnected) { notification.remove(); target._validation = null; } target.parentNode.style.setProperty("position", originalPosition); }, duration); } return notification; } /** * Shows an error message on a given HTMLElement. * @param {string|Function} msg The error message to show. * @param {Number} duration The number of miliseconds to show the error. Default is 4000. -1 is infinite. * @returns {HTMLElement} The created notification HTMLElement. */ HTMLElement.prototype.error = function (msg, duration = 4000) { return notify(this, "error", msg, duration); } /** * Shows a warning message on a given HTMLElement. * @param {string|Function} msg The warning message to show. * @param {Number} duration The number of miliseconds to show the warning. Default is 4000. -1 is infinite. * @returns {HTMLElement} The created notification HTMLElement. */ HTMLElement.prototype.warning = function (msg, duration = 4000) { return notify(this, "warning", msg, duration); } /** * Shows a success message on a given HTMLElement. * @param {string|Function} msg The success message to show. * @param {Number} duration The number of miliseconds to show the notification. Default is 4000. -1 is infinite. * @returns {HTMLElement} The created notification HTMLElement. */ HTMLElement.prototype.success = function (msg, duration = 4000) { return notify(this, "success", msg, duration); } /** * Shows a info message on a given HTMLElement. * @param {string|Function} msg The info message to show. * @param {Number} duration The number of miliseconds to show the notification. Default is 4000. -1 is infinite. * @returns {HTMLElement} The created notification HTMLElement. */ HTMLElement.prototype.info = function (msg, duration = 4000) { notify(this, "info", msg, duration); } /** * Validates an input when changed. * @param {Function|IgniteProperty} callback The callback function to invoke in order to validate changes to an input. * @example * .validate((value, error, warning, success, info) => { * if (value == null) { * error("Value cannot be null"); * } * }) * @returns {IgniteTemplate} This ignite template. */ IgniteTemplate.prototype.validate = function (callback) { //Setup the validators array if it doesn't exist. if (!this._validators) { this._validators = []; //Setup an event for change to run the validators. this.on("change", () => { for (var i = 0; i < this._validators.length; i++) { if (!this._validators[i]()) { return false; } } }); } //Register a new validator this._validators.push(() => { //Get the elements current value var type = this.element.hasAttribute("type") ? this.element.getAttribute("type").toLowerCase().trim() : null; //Get the value from the element. var value = null; if (type == "checkbox") { value = this.element.checked; } else if (type == "radio") { value = this.element.checked; } else if (type == "number") { value = Number(this.element.value); } else if (type == "file") { value = this.element.files; } else if (this.element.hasAttribute("contenteditable") && this.element.getAttribute("contenteditable").toLowerCase().trim() == "true") { value = this.element.textContent; } else { value = this.element.value; } //If the element already has a validation element remove it. if (this.element._validation && this.element._validation.isConnected) { this.element._validation.remove(); this.element._validation = null; } //Get the target from the callback. var target = callback; if (target instanceof IgniteProperty) { target = target.value; } //Run the target to see if we passed validation. var error = false; target( value, (msg, duration = 4000) => { notify(this.element, "error", msg, duration); error = true; }, (msg, duration = 4000) => { notify(this.element, "warning", msg, duration); }, (msg, duration = 4000) => { notify(this.element, "success", msg, duration); }, (msg, duration = 4000) => { notify(this.element, "info", msg, duration); } ); return !error; }); return this; } /** * Validates an input to make sure it contains a valid email address. * @param {string|Function|IgniteProperty} msg The message to display when the email is incorrect. If null a default one will show. * @returns {IgniteTemplate} This ignite template. */ IgniteTemplate.prototype.validateEmail = function (msg) { return this.validate((value, error) => { if (!value || value.length == 0) { return error(msg ? msg : 'Please enter an email address.'); } else if (value.trim().length < 5) { return error(msg ? msg : 'Email address too short.'); } else if (!value.includes('@')) { return error(msg ? msg : 'Email address missing @ symbol.'); } else if (!value.includes('.')) { return error(msg ? msg : 'Email address missing domain.'); } else if (value.length > 64) { return error(msg ? msg : 'Email address too long.'); } else if (value.includes(' ')) { return error(msg ? msg : 'Email address cannot contain spaces.'); } else if (value.split('@').length > 2) { return error(msg ? msg : 'Email address has too many @ symbols.') } }); } /** * Validates an input to make sure it contains a valid password. * @param {string|Function|IgniteProperty} msg The message to display when the email is incorrect. If null a default one will show. * @returns {IgniteTemplate} This ignite template. */ IgniteTemplate.prototype.validatePassword = function (msg) { return this.validate((value, error) => { if (!value || value.trim().length < 5) { return error(msg ? msg : `Password must be at least 5 characters.`); } else if (value.includes(' ')) { return error(msg ? msg : 'Password cannot contain spaces.'); } else if (value.length > 64) { return error(msg ? msg : 'Password too long.'); } }); } /** * Validates an input to make sure it contains a min number of characters. * @param {Number} min The min number of characters the input can have. * @param {string|Function|IgniteProperty} msg The message to display when the input length is too short. If null a default one will show. * @returns {IgniteTemplate} This ignite template. */ IgniteTemplate.prototype.validateMin = function (min, msg) { return this.validate((value, error) => { if (!value || value.toString().trim().length < min) { return error(msg ? msg : `Input must contain at least ${min} character(s).`); } }); } /** * Validates an input to make sure it contains a max number of characters. * @param {Number} max The max number of characters the input can have. * @param {string|Function|IgniteProperty} msg The message to display when the input length is too long. If null a default one will show. * @returns {IgniteTemplate} This ignite template. */ IgniteTemplate.prototype.validateMax = function (max, msg) { return this.validate((value, error) => { if (value && value.toString().trim().length > max) { return error(msg ? msg : `Input must contain less than ${max} character(s).`); } }); } /** * Validates an input to make sure it it's value is not the given value. * @param {Any} expected The value the input cannot have in order to be considered valid. * @param {string|Function|IgniteProperty} msg The message to display when the input length is too long. If null a default one will show. * @returns {IgniteTemplate} This ignite template. */ IgniteTemplate.prototype.validateNot = function (expected, msg) { return this.validate((value, error) => { if (!value || value == expected) { return error(msg ? msg : `Input cannot be ${expected}.`); } }); } /** * Validates an input to make sure it includes a string. * @param {string} str The string that the input must include. * @param {string|Function|IgniteProperty} msg The message to display when the input doesn't contain the string. If null a default one will show. * @returns {IgniteTemplate} This ignite template. */ IgniteTemplate.prototype.validateIncludes = function (str, msg) { return this.validate((value, error) => { if (!value || !value.toString().includes(str)) { return error(msg ? msg : `Input must contain ${str}.`); } }); } /** * Validates an input to make sure it matches a given value. * @param {string|Function|IgniteProperty} compare The value to compare against the input. * @param {string|Function|IgniteProperty} msg The message to display when the input doesn't match. If null a default one will show. * @returns {IgniteTemplate} This ignite template. */ IgniteTemplate.prototype.validateMatches = function (compare, msg) { return this.validate((value, error) => { if (compare instanceof Function) { if (compare() != value) { return error(msg ? msg : `Input does not match: ${compare()}.`); } } else if (compare instanceof IgniteProperty) { if (compare.value != value) { return error(msg ? msg : `Input does not match: ${compare.value}.`); } } else if (value != compare) { return error(msg ? msg : `Input does not match: ${str}.`); } }); } /** * Validates an input to make sure it contains at least one file file. * @param {string|Function|IgniteProperty} msg The message to display when a file has not been selected. * @returns {IgniteTemplate} This ignite template. */ IgniteTemplate.prototype.validateFile = function(msg) { return this.validate((value, error) => { if (!value || value.length == 0) { return error(msg ? msg : 'Must select a file to upload.'); } }); } /** * Validates this element and all the children within the element. * @returns {Boolean} Whether or not this element and all children in this element are valid. */ HTMLElement.prototype.validate = function () { //If this element has a template with validators run them. if (this.template && this.template._validators) { for (var i = 0; i < this.template._validators.length; i++) { if (!this.template._validators[i]()) { return false; } } } else { //Look for elements that we can try to validate. var elements = this.getElementsByTagName("*"); for (var i = 0; i < elements.length; i++) { var element = elements[i]; //Check if the element is visible. if (!element.offsetParent) { continue; } //If this element has a template, and has validators, run them, if any are false, return false. if (element.template && element.template._validators) { for (var v = 0; v < element.template._validators.length; v++) { if (!element.template._validators[v]()) { return false; } } } } } return true; }