1/*
2 * RapidContext <https://www.rapidcontext.com/>
3 * Copyright (c) 2007-2024 Per Cederberg. All rights reserved.
4 *
5 * This program is free software: you can redistribute it and/or
6 * modify it under the terms of the BSD license.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11 *
12 * See the RapidContext LICENSE for more details.
13 */
14
15// Namespace initialization
16if (typeof(RapidContext) == "undefined") {
17 RapidContext = {};
18}
19RapidContext.Widget = RapidContext.Widget || { Classes: {} };
20
21/**
22 * Creates a new form validator widget.
23 *
24 * @constructor
25 * @param {Object} attrs the widget and node attributes
26 * @param {string} attrs.name the form field name to validate
27 * @param {boolean} [attrs.mandatory] the mandatory field flag,
28 * defaults to `true`
29 * @param {string|RegExp} [attrs.regex] the regular expression to
30 * match the field value against, defaults to `null`
31 * @param {string} [attrs.display] the validator display setting
32 * (either "none", "icon", "text" or "both"), defaults
33 * to "both"
34 * @param {string} [attrs.message] the message to display, defaults
35 * to the validator function error message
36 * @param {function} [attrs.validator] the validator function
37 * @param {boolean} [attrs.hidden] the hidden widget flag, defaults to `false`
38 *
39 * @return {Widget} the widget DOM node
40 *
41 * @class The form validator widget class. Provides visual feedback on form
42 * validation failures, using a `<span>` HTML element. It is normally
43 * hidden by default and may be configured to only modify its related form
44 * field.
45 * @property {string} name The form field name to validate.
46 * @property {string} message The default validation message.
47 * @property {function} validator The validator function in use.
48 * @extends RapidContext.Widget
49 *
50 * @example <caption>JavaScript</caption>
51 * let field = RapidContext.Widget.TextField({ name: "name", placeholder: "Your Name Here" });
52 * let attrs = { name: "name", message: "Please enter your name to proceed." };
53 * let valid = RapidContext.Widget.FormValidator(attrs);
54 * let exampleForm = RapidContext.Widget.Form({}, field, valid);
55 *
56 * @example <caption>User Interface XML</caption>
57 * <Form id="exampleForm">
58 * <TextField name="name" placeholder="Your Name Here" />
59 * <FormValidator name="name" message="Please enter your name to proceed." />
60 * </Form>
61 */
62RapidContext.Widget.FormValidator = function (attrs) {
63 let o = document.createElement("span");
64 RapidContext.Widget._widgetMixin(o, RapidContext.Widget.FormValidator);
65 o.addClass("widgetFormValidator");
66 let defaults = { name: "", mandatory: true, display: "both", message: null, validator: null };
67 o.setAttrs(Object.assign(defaults, attrs));
68 o.fields = [];
69 o.hide();
70 return o;
71};
72
73// Register widget class
74RapidContext.Widget.Classes.FormValidator = RapidContext.Widget.FormValidator;
75
76/**
77 * Updates the widget or HTML DOM node attributes.
78 *
79 * @param {Object} attrs the widget and node attributes to set
80 * @param {string} [attrs.name] the form field name to validate
81 * @param {boolean} [attrs.mandatory] the mandatory field flag
82 * @param {string|RegExp} [attrs.regex] the regular expression to
83 * match the field value against
84 * @param {string} [attrs.display] the validator display setting
85 * (either "none", "icon", "text" or "both")
86 * @param {string} [attrs.message] the message to display
87 * @param {function} [attrs.validator] the validator function
88 * @param {boolean} [attrs.hidden] the hidden widget flag
89 */
90RapidContext.Widget.FormValidator.prototype.setAttrs = function (attrs) {
91 attrs = Object.assign({}, attrs);
92 if ("mandatory" in attrs) {
93 attrs.mandatory = RapidContext.Data.bool(attrs.mandatory);
94 }
95 if ("regex" in attrs && attrs.regex && !(attrs.regex instanceof RegExp)) {
96 if (!attrs.regex.startsWith("^")) {
97 attrs.regex = "^" + attrs.regex;
98 }
99 if (!attrs.regex.endsWith("$")) {
100 attrs.regex += "$";
101 }
102 attrs.regex = new RegExp(attrs.regex);
103 }
104 if ("validator" in attrs) {
105 let valid = typeof(attrs.validator) == "function";
106 attrs.validator = valid ? attrs.validator : null;
107 }
108 this.__setAttrs(attrs);
109};
110
111/**
112 * Resets this form validator. This will hide any error messages and mark all
113 * invalidated fields as valid.
114 *
115 * Note that this method is normally not called directly, instead the
116 * validation is reset by the `RapidContext.Widget.Form` widget.
117 *
118 * @see RapidContext.Widget.Form#validateReset
119 */
120RapidContext.Widget.FormValidator.prototype.reset = function () {
121 this.fields.forEach(function (field) {
122 field.classList.remove("invalid");
123 });
124 this.fields = [];
125 this.hide();
126 this.removeAll();
127};
128
129/**
130 * Verifies a form field with this validator. If the form field value doesn't
131 * match this validator, the field will be invalidated until this validator is
132 * reset.
133 *
134 * Note that this method is normally not called directly, instead the
135 * validation is performed by the `RapidContext.Widget.Form` widget.
136 *
137 * @param {Widget|Node} field the form field DOM node
138 * @param {string} [value] the form field value to check
139 *
140 * @return {boolean} `true` if the form validated successfully, or
141 * `false` if the validation failed
142 *
143 * @see RapidContext.Widget.Form#validate
144 */
145RapidContext.Widget.FormValidator.prototype.verify = function (field, value) {
146 if (!field.disabled) {
147 if (arguments.length == 1 && typeof(field.getValue) == "function") {
148 value = field.getValue();
149 } else if (arguments.length == 1) {
150 value = field.value;
151 }
152 let str = String(value).trim();
153 if (field.validationMessage) {
154 this.addError(field, field.validationMessage);
155 return false;
156 } else if (this.mandatory && str == "") {
157 this.addError(field, "This field is required");
158 return false;
159 } else if (this.regex && str && !this.regex.test(str)) {
160 this.addError(field, "The field format is incorrect");
161 return false;
162 } else if (typeof(this.validator) == "function") {
163 let res = this.validator(value);
164 if (res !== true) {
165 this.addError(field, res || "Field validation failed");
166 return false;
167 }
168 }
169 }
170 return true;
171};
172
173/**
174 * Adds a validation error message for the specified field. If the field is
175 * already invalid, this method will not do anything.
176 *
177 * Note that this method is normally not called directly, instead the
178 * validation is performed by the `RapidContext.Widget.Form` widget.
179 *
180 * @param {Widget|Node} field the field DOM node
181 * @param {string} message the validation error message
182 *
183 * @see RapidContext.Widget.Form#validate
184 */
185RapidContext.Widget.FormValidator.prototype.addError = function (field, message) {
186 if (!field.classList.contains("invalid")) {
187 this.fields.push(field);
188 field.classList.add("invalid");
189 if (this.display !== "none") {
190 message = this.message || message;
191 let span = null;
192 let icon = null;
193 if (!this.display || this.display === "both") {
194 this.addClass("block");
195 }
196 if (this.display !== "icon") {
197 span = document.createElement("span");
198 span.append(message);
199 }
200 if (this.display !== "text") {
201 icon = RapidContext.Widget.Icon({ ref: "ERROR", tooltip: message });
202 }
203 if (!this.childNodes.length) {
204 this.addAll(icon, span);
205 }
206 this.show();
207 }
208 }
209};
210
RapidContext
Access · Discovery · Insight
www.rapidcontext.com