Allow formating numbers when rendering attributes
Closes keycloak#26320 Signed-off-by: Pedro Igor <pigor.craveiro@gmail.com>
This commit is contained in:
parent
93a6d56af3
commit
3a7ce54266
9 changed files with 135 additions and 2 deletions
|
@ -536,6 +536,16 @@ Useful for numeric fields.
|
|||
|inputTypeStep
|
||||
|HTML input `step` attribute applied to the field - Specifies the interval between legal numbers in an input field. Useful for numeric fields.
|
||||
|
||||
|Number Format
|
||||
|If set, the `data-kcNumberFormat` attribute is added to the field to format the value based on a given format. This annotation is targeted for numbers where the format is based on the
|
||||
number of digits expected in a determined position. For instance, a format `(\{2}) \{5}-\{4}` will format the field value to `(00) 00000-0000`.
|
||||
|
||||
|Number UnFormat
|
||||
|If set, the `data-kcNumberUnFormat` attribute is added to the field to format the value based on a given format before submitting the form. This annotation
|
||||
is useful if you do not want to store any format for a specific attribute but only format the value on the client side. For instance, if the current value
|
||||
is `(00) 00000-0000`, the value will change to `00000000000` if you set the value `\{11}` to this annotation or any other format you want by specifying a set of one or ore group of digits.
|
||||
Make sure to add validators to perform server-side validations before storing values.
|
||||
|
||||
|===
|
||||
|
||||
[NOTE]
|
||||
|
@ -710,6 +720,15 @@ provided by built-in `options` validation.
|
|||
.Options provided by custom validator
|
||||
image:images/user-profile-select-options-custom-validator.png[]
|
||||
|
||||
[[_adding-custom-html5-data-attributes]]
|
||||
==== Adding Custom HTML5 Data Attributes
|
||||
|
||||
You can enable additional client-side behavior by using `kc*` annotations. These annotations are going to be added
|
||||
automatically to a field as a HTML5 attribute prefixed with `data-` and a script with the same will be loaded to the dynamic pages.
|
||||
|
||||
For instance, if you add a `kcMyCustomValidation` annotation to a field, the dynamic pages will add a `data-kcMyCustomValidation` HTML5 attribute
|
||||
to the field and load a JS script file from `<THEME TYPE>/resources/js/kcMyCustomValidation.js`. See the {developerguide_link}[{developerguide_name}] for more information about
|
||||
how to deploy a custom JS script file to your theme.
|
||||
|
||||
== Forcing User Profile compliance
|
||||
|
||||
|
|
|
@ -28,3 +28,9 @@ $evaluation.grant()
|
|||
{$v
|
||||
location.origin
|
||||
keycloak.token
|
||||
11
|
||||
1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
|
@ -3045,3 +3045,5 @@ searchClientAuthorizationResource=Search resource
|
|||
searchClientAuthorizationPolicy=Search policy
|
||||
searchClientAuthorizationPermission=Search permission
|
||||
userNotSaved=The user has not been saved\: {{error}}
|
||||
kcNumberFormat=Number Format
|
||||
kcNumberUnFormat=Number UnFormat
|
|
@ -77,6 +77,14 @@ export const AttributeAnnotations = () => {
|
|||
key: "inputTypeStep",
|
||||
label: t("inputTypeStep"),
|
||||
},
|
||||
{
|
||||
key: "kcNumberFormat",
|
||||
label: t("kcNumberFormat"),
|
||||
},
|
||||
{
|
||||
key: "kcNumberUnFormat",
|
||||
label: t("kcNumberUnFormat"),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</GridItem>
|
||||
|
|
|
@ -4,6 +4,7 @@ import java.util.Collections;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
|
@ -78,6 +79,13 @@ public abstract class AbstractUserProfileBean {
|
|||
return attributes;
|
||||
}
|
||||
|
||||
public Map<String, Object> getHtml5DataAnnotations() {
|
||||
return getAttributes().stream().map(Attribute::getHtml5DataAnnotations)
|
||||
.map(Map::entrySet)
|
||||
.flatMap(Set::stream)
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (l, r) -> l));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get map of all attributes where attribute name is key. Useful to render crafted form.
|
||||
*
|
||||
|
@ -168,6 +176,11 @@ public abstract class AbstractUserProfileBean {
|
|||
return annotations;
|
||||
}
|
||||
|
||||
public Map<String, Object> getHtml5DataAnnotations() {
|
||||
return getAnnotations().entrySet().stream()
|
||||
.filter((entry) -> entry.getKey().startsWith("kc")).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get info about validators applied to attribute.
|
||||
*
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
export const formatNumber = (input, format) => {
|
||||
if (!input) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// array holding the patterns for the number of expected digits in each part
|
||||
const digitPattern = format.match(/{\d+}/g);
|
||||
|
||||
if (!digitPattern) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// calculate the maximum size of the given pattern based on the sum of the expected digits
|
||||
const maxSize = digitPattern.reduce((total, p) => total + parseInt(p.replace("{", "").replace("}", "")), 0)
|
||||
|
||||
// keep only digits
|
||||
let rawValue = input.replace(/\D+/g, '');
|
||||
|
||||
// make sure the value is a number
|
||||
if (parseInt(rawValue) != rawValue) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// make sure the number of digits does not exceed the maximum size
|
||||
if (rawValue.length > maxSize) {
|
||||
rawValue = rawValue.substring(0, maxSize);
|
||||
}
|
||||
|
||||
// build the regex based based on the expected digits in each part
|
||||
const formatter = digitPattern.reduce((result, p) => result + `(\\d${p})`, "^");
|
||||
|
||||
// if the current digits match the pattern we have each group of digits in an array
|
||||
let digits = new RegExp(formatter).exec(rawValue);
|
||||
|
||||
// no match, return the raw value without any format
|
||||
if (!digits) {
|
||||
return input;
|
||||
}
|
||||
|
||||
let result = format;
|
||||
|
||||
// finally format the current digits accordingly to the given format
|
||||
for (let i = 0; i < digitPattern.length; i++) {
|
||||
result = result.replace(digitPattern[i], digits[i + 1]);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
import {formatNumber} from "./common.js";
|
||||
|
||||
const DATA_KC_NUMBER_FORMAT = 'data-kcNumberFormat';
|
||||
|
||||
document.querySelectorAll(`[${DATA_KC_NUMBER_FORMAT}]`)
|
||||
.forEach(input => {
|
||||
const format = input.getAttribute(DATA_KC_NUMBER_FORMAT);
|
||||
|
||||
input.addEventListener('keyup', (event) => {
|
||||
input.value = formatNumber(input.value, format);
|
||||
});
|
||||
|
||||
input.value = formatNumber(input.value, format);
|
||||
});
|
|
@ -0,0 +1,15 @@
|
|||
import {formatNumber} from "./common.js";
|
||||
|
||||
const DATA_KC_NUMBER_UNFORMAT = 'data-kcNumberUnFormat';
|
||||
|
||||
document.querySelectorAll(`[${DATA_KC_NUMBER_UNFORMAT}]`)
|
||||
.forEach(input => {
|
||||
for (let form of document.forms) {
|
||||
form.addEventListener('submit', (event) => {
|
||||
const rawFormat = input.getAttribute(DATA_KC_NUMBER_UNFORMAT);
|
||||
if (rawFormat) {
|
||||
input.value = formatNumber(input.value, rawFormat);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
|
@ -53,6 +53,10 @@
|
|||
</div>
|
||||
<#nested "afterField" attribute>
|
||||
</#list>
|
||||
|
||||
<#list profile.html5DataAnnotations?keys as key>
|
||||
<script type="module" src="${url.resourcesPath}/js/${key}.js"></script>
|
||||
</#list>
|
||||
</#macro>
|
||||
|
||||
<#macro inputFieldByType attribute>
|
||||
|
@ -86,6 +90,10 @@
|
|||
<#if attribute.annotations.inputTypeMax??>max="${attribute.annotations.inputTypeMax}"</#if>
|
||||
<#if attribute.annotations.inputTypeMin??>min="${attribute.annotations.inputTypeMin}"</#if>
|
||||
<#if attribute.annotations.inputTypeStep??>step="${attribute.annotations.inputTypeStep}"</#if>
|
||||
<#if attribute.annotations.inputTypeStep??>step="${attribute.annotations.inputTypeStep}"</#if>
|
||||
<#list attribute.html5DataAnnotations as key, value>
|
||||
data-${key}="${value}"
|
||||
</#list>
|
||||
/>
|
||||
</#macro>
|
||||
|
||||
|
|
Loading…
Reference in a new issue