Made the login more modular

Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com>
This commit is contained in:
Erik Jan de Wit 2024-07-31 10:46:50 +02:00 committed by Stan Silvert
parent d82ca2ed41
commit e410a83c3c
11 changed files with 182 additions and 187 deletions

View file

@ -47,8 +47,11 @@ public class LoginPage extends LanguageComboboxAwarePage {
@FindBy(id = "password") @FindBy(id = "password")
private WebElement passwordInput; private WebElement passwordInput;
@FindBy(id = "input-error") @FindBy(id = "input-error-username")
private WebElement inputError; private WebElement userNameInputError;
@FindBy(id = "input-error-password")
private WebElement passwordInputError;
@FindBy(id = "rememberMe") @FindBy(id = "rememberMe")
private WebElement rememberMe; private WebElement rememberMe;
@ -161,9 +164,13 @@ public class LoginPage extends LanguageComboboxAwarePage {
public String getInputError() { public String getInputError() {
try { try {
return getTextFromElement(inputError); return getTextFromElement(userNameInputError);
} catch (NoSuchElementException e) { } catch (NoSuchElementException ex) {
return null; try {
return getTextFromElement(passwordInputError);
} catch (NoSuchElementException e) {
return null;
}
} }
} }
@ -246,7 +253,7 @@ public class LoginPage extends LanguageComboboxAwarePage {
assertCurrent(); assertCurrent();
} }
public void open(String realm){ public void open(String realm) {
oauth.realm(realm); oauth.realm(realm);
oauth.openLoginForm(); oauth.openLoginForm();
assertCurrent(realm); assertCurrent(realm);

View file

@ -148,11 +148,11 @@ public class RequiredActionUpdateProfileWithUserProfileTest extends AbstractTest
//assert field names //assert field names
// i18n replaced // i18n replaced
Assert.assertEquals("First name", updateProfilePage.getLabelForField("firstName")); Assert.assertEquals("First name *", updateProfilePage.getLabelForField("firstName"));
// attribute name used if no display name set // attribute name used if no display name set
Assert.assertEquals("lastName", updateProfilePage.getLabelForField("lastName")); Assert.assertEquals("lastName", updateProfilePage.getLabelForField("lastName"));
// direct value in display name // direct value in display name
Assert.assertEquals("Department", updateProfilePage.getLabelForField("department")); Assert.assertEquals("Department *", updateProfilePage.getLabelForField("department"));
} }

View file

@ -465,11 +465,11 @@ public class KcOidcFirstBrokerLoginTest extends AbstractFirstBrokerLoginTest {
//assert field names //assert field names
// i18n replaced // i18n replaced
org.junit.Assert.assertEquals("First name", updateAccountInformationPage.getLabelForField("firstName")); org.junit.Assert.assertEquals("First name *", updateAccountInformationPage.getLabelForField("firstName"));
// attribute name used if no display name set // attribute name used if no display name set
org.junit.Assert.assertEquals("lastName", updateAccountInformationPage.getLabelForField("lastName")); org.junit.Assert.assertEquals("lastName", updateAccountInformationPage.getLabelForField("lastName"));
// direct value in display name // direct value in display name
org.junit.Assert.assertEquals("Department", updateAccountInformationPage.getLabelForField("department")); org.junit.Assert.assertEquals("Department *", updateAccountInformationPage.getLabelForField("department"));
} }
@Test @Test

View file

@ -265,11 +265,11 @@ public class RegisterWithUserProfileTest extends AbstractTestRealmKeycloakTest {
//assert field names //assert field names
// i18n replaced // i18n replaced
Assert.assertEquals("First name",registerPage.getLabelForField("firstName")); Assert.assertEquals("First name *",registerPage.getLabelForField("firstName"));
// attribute name used if no display name set // attribute name used if no display name set
Assert.assertEquals("lastName",registerPage.getLabelForField("lastName")); Assert.assertEquals("lastName",registerPage.getLabelForField("lastName"));
// direct value in display name // direct value in display name
Assert.assertEquals("Department",registerPage.getLabelForField("department")); Assert.assertEquals("Department *",registerPage.getLabelForField("department"));
} }
@Test @Test

View file

@ -191,11 +191,11 @@ public class VerifyProfileTest extends AbstractTestRealmKeycloakTest {
//assert field names //assert field names
// i18n replaced // i18n replaced
Assert.assertEquals("First name",verifyProfilePage.getLabelForField("firstName")); Assert.assertEquals("First name *",verifyProfilePage.getLabelForField("firstName"));
// attribute name used if no display name set // attribute name used if no display name set
Assert.assertEquals("lastName",verifyProfilePage.getLabelForField("lastName")); Assert.assertEquals("lastName",verifyProfilePage.getLabelForField("lastName"));
// direct value in display name // direct value in display name
Assert.assertEquals("Department",verifyProfilePage.getLabelForField("department")); Assert.assertEquals("Department *",verifyProfilePage.getLabelForField("department"));
} }
@Test @Test

View file

@ -0,0 +1,105 @@
<#macro group name label error="" required=false>
<div class="${properties.kcFormGroupClass}">
<div class="${properties.kcFormGroupLabelClass}">
<label for="${name}" class="pf-v5-c-form__label">
<span class="pf-v5-c-form__label-text">
${label}
</span>
<#if required>
<span class="pf-v5-c-form__label-required" aria-hidden="true">&#42;</span>
</#if>
</label>
</div>
<#nested>
<#if error?has_content>
<div class="${properties.kcFormHelperTextClass}" aria-live="polite">
<div class="${properties.kcInputHelperTextClass}">
<div
class="${properties.kcInputHelperTextItemClass} ${properties.kcError}"
id="input-error-${name}">
<span class="${properties.kcInputErrorMessageClass}">
${error}
</span>
</div>
</div>
</div>
</#if>
</div>
</#macro>
<#macro errorIcon error="">
<#if error?has_content>
<span class="pf-v5-c-form-control__utilities">
<span class="pf-v5-c-form-control__icon pf-m-status">
<i class="fas fa-exclamation-circle" aria-hidden="true"></i>
</span>
</span>
</#if>
</#macro>
<#macro input name label value="" required=false>
<#assign error=kcSanitize(messagesPerField.get(name))?no_esc>
<@field.group name=name label=label error=error required=required>
<span class="${properties.kcInputClass} <#if error?has_content>${properties.kcError}</#if>">
<input id="${name}" name="${name}" value="${value}" type="text" autocomplete="off"
aria-invalid="<#if error?has_content>true</#if>"/>
<@errorIcon error=error/>
</span>
</@field.group>
</#macro>
<#macro password name label value="" required=false forgotPassword=false>
<#assign error=kcSanitize(messagesPerField.get(name))?no_esc>
<@field.group name=name label=label error=error required=required>
<div class="${properties.kcInputGroup}">
<div class="${properties.kcInputGroupItemClass} ${properties.kcFill}">
<span class="${properties.kcInputClass} <#if error?has_content>${properties.kcError}</#if>">
<input id="${name}" name="${name}" value="${value}" type="password" autocomplete="off"
aria-invalid="<#if error?has_content>true</#if>"/>
<@errorIcon error=error/>
</span>
</div>
<div class="${properties.kcInputGroupItemClass}">
<button class="pf-v5-c-button pf-m-control" type="button" aria-label="${msg('showPassword')}"
aria-controls="${name}" data-password-toggle
data-icon-show="fa-eye fas" data-icon-hide="fa-eye-slash fas"
data-label-show="${msg('showPassword')}" data-label-hide="${msg('hidePassword')}">
<i class="fa-eye fas" aria-hidden="true"></i>
</button>
</div>
</div>
<#if forgotPassword>
<div class="${properties.kcFormHelperTextClass}" aria-live="polite">
<div class="${properties.kcInputHelperTextClass}">
<div class="${properties.kcInputHelperTextItemClass}">
<span class="${properties.kcInputHelperTextItemTextClass}">
<a tabindex="3" href="${url.loginResetCredentialsUrl}">${msg("doForgotPassword")}</a>
</span>
</div>
</div>
</div>
</#if>
</@field.group>
</#macro>
<#macro checkbox name label value=false required=false>
<div class="${properties.kcCheckboxClass}">
<label for="${name}" class="${properties.kcCheckboxClass}">
<input
class="${properties.kcCheckboxInputClass}"
type="checkbox"
id="${name}"
name="${name}"
<#if value>checked</#if>
/>
<span class="${properties.kcCheckboxLabelClass}">${label}</span>
<#if required>
<span class="${properties.kcCheckboxLabelRequiredClass}" aria-hidden="true">&#42;</span>
</#if>
</label>
</div>
</#macro>

View file

@ -1,4 +1,5 @@
<#import "template.ftl" as layout> <#import "template.ftl" as layout>
<#import "field.ftl" as field>
<@layout.registrationLayout displayMessage=!messagesPerField.existsError('username','password') displayInfo=realm.password && realm.registrationAllowed && !registrationDisabled??; section> <@layout.registrationLayout displayMessage=!messagesPerField.existsError('username','password') displayInfo=realm.password && realm.registrationAllowed && !registrationDisabled??; section>
<#if section = "header"> <#if section = "header">
${msg("loginAccountTitle")} ${msg("loginAccountTitle")}
@ -6,93 +7,26 @@
<div id="kc-form"> <div id="kc-form">
<div id="kc-form-wrapper"> <div id="kc-form-wrapper">
<#if realm.password> <#if realm.password>
<form id="kc-form-login" class="${properties.kcFormClass!} onsubmit="login.disabled = true; return true;" action="${url.loginAction}" method="post" novalidate="novalidate"> <form id="kc-form-login" class="pf-v5-c-form" onsubmit="login.disabled = true; return true;" action="${url.loginAction}" method="post" novalidate="novalidate">
<#if !usernameHidden??> <#if !usernameHidden??>
<div class="${properties.kcFormGroupClass!}"> <#assign label>
<label for="username" class="${properties.kcLabelClass!}"> <#if !realm.loginWithEmailAllowed>${msg("username")}<#elseif !realm.registrationEmailAsUsername>${msg("usernameOrEmail")}<#else>${msg("email")}</#if>
<span class="pf-v5-c-form__label-text"> </#assign>
<#if !realm.loginWithEmailAllowed>${msg("username")}<#elseif !realm.registrationEmailAsUsername>${msg("usernameOrEmail")}<#else>${msg("email")}</#if> <@field.input name="username" label=label value="${(login.username!'')}" />
</span>
</label>
<span class="${properties.kcInputClass!} ${messagesPerField.existsError('username','password')?then('pf-m-error', '')}">
<input tabindex="1" id="username" name="username" value="${(login.username!'')}" type="text" autofocus autocomplete="off"
aria-invalid="<#if messagesPerField.existsError('username','password')>true</#if>"
dir="ltr"
/>
<#if messagesPerField.existsError('username','password')>
<span class="pf-v5-c-form-control__utilities">
<span class="pf-v5-c-form-control__icon pf-m-status">
<i class="fas fa-exclamation-circle" aria-hidden="true"></i>
</span>
</span>
</#if>
</span>
<#if messagesPerField.existsError('username','password')>
<span id="input-error" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
${kcSanitize(messagesPerField.getFirstError('username','password'))?no_esc}
</span>
</#if>
</div>
</#if> </#if>
<div class="${properties.kcFormGroupClass!}"> <@field.password name="password" label=msg("password") forgotPassword=realm.resetPasswordAllowed/>
<label for="password" class="${properties.kcLabelClass!}">
<span class="pf-v5-c-form__label-text">${msg("password")}</span>
</label>
<div class="${properties.kcInputGroup!}" dir="ltr"> <div class="pf-v5-c-form__group">
<span class="${properties.kcInputClass!}"> <#if realm.rememberMe && !usernameHidden??>
<input tabindex="2" id="password" name="password" type="password" autocomplete="off" <@field.checkbox name="rememberMe" label=msg("rememberMe") value=login.rememberMe?? />
aria-invalid="<#if messagesPerField.existsError('username','password')>true</#if>"
/>
</span>
<button class="${properties.kcFormPasswordVisibilityButtonClass!}" type="button" aria-label="${msg('showPassword')}"
aria-controls="password" data-password-toggle
data-icon-show="${properties.kcFormPasswordVisibilityIconShow!}" data-icon-hide="${properties.kcFormPasswordVisibilityIconHide!}"
data-label-show="${msg('showPassword')}" data-label-hide="${msg('hidePassword')}">
<i class="${properties.kcFormPasswordVisibilityIconShow!}" aria-hidden="true"></i>
</button>
</div>
<#if usernameHidden?? && messagesPerField.existsError('username','password')>
<span id="input-error" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
${kcSanitize(messagesPerField.getFirstError('username','password'))?no_esc}
</span>
</#if> </#if>
</div> </div>
<div class="${properties.kcFormGroupClass!} ${properties.kcFormSettingClass!}"> <div id="kc-form-buttons" class="pf-v5-c-form__group">
<div id="kc-form-options"> <input type="hidden" id="id-hidden-input" name="credentialId" <#if auth.selectedCredential?has_content>value="${auth.selectedCredential}"</#if>/>
<#if realm.rememberMe && !usernameHidden??> <input tabindex="4" class="pf-v5-c-button pf-m-primary pf-m-block" name="login" id="kc-login" type="submit" value="${msg("doLogIn")}"/>
<div class="checkbox"> </div>
<label>
<span class="pf-v5-c-form__label-text">
<#if login.rememberMe??>
<input tabindex="3" id="rememberMe" name="rememberMe" type="checkbox" checked> ${msg("rememberMe")}
<#else>
<input tabindex="3" id="rememberMe" name="rememberMe" type="checkbox"> ${msg("rememberMe")}
</#if>
</span>
</label>
</div>
</#if>
</div>
<div class="${properties.kcFormOptionsWrapperClass!}">
<#if realm.resetPasswordAllowed>
<span><a tabindex="5" href="${url.loginResetCredentialsUrl}">${msg("doForgotPassword")}</a></span>
</#if>
</div>
</div>
<div id="kc-form-buttons" class="${properties.kcFormGroupClass!}">
<input type="hidden" id="id-hidden-input" name="credentialId" <#if auth.selectedCredential?has_content>value="${auth.selectedCredential}"</#if>/>
<input tabindex="4" class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonBlockClass!} ${properties.kcButtonLargeClass!}" name="login" id="kc-login" type="submit" value="${msg("doLogIn")}"/>
</div>
</form> </form>
</#if> </#if>
</div> </div>

View file

@ -1,4 +1,5 @@
<#import "template.ftl" as layout> <#import "template.ftl" as layout>
<#import "field.ftl" as field>
<#import "user-profile-commons.ftl" as userProfileCommons> <#import "user-profile-commons.ftl" as userProfileCommons>
<#import "register-commons.ftl" as registerCommons> <#import "register-commons.ftl" as registerCommons>
<@layout.registrationLayout displayMessage=messagesPerField.exists('global') displayRequiredFields=true; section> <@layout.registrationLayout displayMessage=messagesPerField.exists('global') displayRequiredFields=true; section>
@ -15,65 +16,8 @@
<#if callback = "afterField"> <#if callback = "afterField">
<#-- render password fields just under the username or email (if used as username) --> <#-- render password fields just under the username or email (if used as username) -->
<#if passwordRequired?? && (attribute.name == 'username' || (attribute.name == 'email' && realm.registrationEmailAsUsername))> <#if passwordRequired?? && (attribute.name == 'username' || (attribute.name == 'email' && realm.registrationEmailAsUsername))>
<div class="${properties.kcFormGroupClass!}"> <@field.password name="password" label=msg("password") />
<label for="password" class="${properties.kcLabelClass!}"> <@field.password name="password-confirm" label=msg("passwordConfirm") />
<span class="pf-v5-c-form__label-text">
${msg("password")}
<span class="pf-v5-c-form__label-required" aria-hidden="true">&#42;</span>
</span>
</label>
<span class="${properties.kcInputGroup!}">
<span class="${properties.kcInputClass!}" dir="ltr">
<input type="password" id="password" name="password"
autocomplete="new-password"
aria-invalid="<#if messagesPerField.existsError('password','password-confirm')>true</#if>"
/>
</span>
<button class="${properties.kcFormPasswordVisibilityButtonClass!}" type="button" aria-label="${msg('showPassword')}"
aria-controls="password" data-password-toggle
data-icon-show="${properties.kcFormPasswordVisibilityIconShow!}" data-icon-hide="${properties.kcFormPasswordVisibilityIconHide!}"
data-label-show="${msg('showPassword')}" data-label-hide="${msg('hidePassword')}">
<i class="${properties.kcFormPasswordVisibilityIconShow!}" aria-hidden="true"></i>
</button>
</span>
<#if messagesPerField.existsError('password')>
<span id="input-error-password" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
${kcSanitize(messagesPerField.get('password'))?no_esc}
</span>
</#if>
</div>
<div class="${properties.kcFormGroupClass!}">
<div class="${properties.kcLabelWrapperClass!}">
<label for="password-confirm" class="${properties.kcLabelClass!}">
<span class="pf-v5-c-form__label-text">
${msg("passwordConfirm")}
<span class="pf-v5-c-form__label-required" aria-hidden="true">&#42;</span>
</span>
</label>
</div>
<div class="${properties.kcInputGroup!}" dir="ltr">
<span class="${properties.kcInputClass!}">
<input type="password" id="password-confirm"
name="password-confirm"
aria-invalid="<#if messagesPerField.existsError('password-confirm')>true</#if>"
/>
</span>
<button class="${properties.kcFormPasswordVisibilityButtonClass!}" type="button" aria-label="${msg('showPassword')}"
aria-controls="password-confirm" data-password-toggle
data-icon-show="${properties.kcFormPasswordVisibilityIconShow!}" data-icon-hide="${properties.kcFormPasswordVisibilityIconHide!}"
data-label-show="${msg('showPassword')}" data-label-hide="${msg('hidePassword')}">
<i class="${properties.kcFormPasswordVisibilityIconShow!}" aria-hidden="true"></i>
</button>
</div>
<#if messagesPerField.existsError('password-confirm')>
<span id="input-error-password-confirm" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
${kcSanitize(messagesPerField.get('password-confirm'))?no_esc}
</span>
</#if>
</div>
</#if> </#if>
</#if> </#if>
</@userProfileCommons.userProfileFormFields> </@userProfileCommons.userProfileFormFields>

View file

@ -1,7 +1,6 @@
<#import "footer.ftl" as loginFooter>
<#macro registrationLayout bodyClass="" displayInfo=false displayMessage=true displayRequiredFields=false> <#macro registrationLayout bodyClass="" displayInfo=false displayMessage=true displayRequiredFields=false>
<!DOCTYPE html> <!DOCTYPE html>
<html class="${properties.kcHtmlClass!}"<#if realm.internationalizationEnabled> lang="${locale.currentLanguageTag}" dir="${(locale.rtl)?then('rtl','ltr')}"</#if>> <html class="${properties.kcHtmlClass!}"<#if realm.internationalizationEnabled> lang="${locale.currentLanguageTag}"</#if>>
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
@ -60,7 +59,7 @@
class="pf-v5-c-brand">${kcSanitize(msg("loginTitleHtml",(realm.displayNameHtml!'')))?no_esc}</div> class="pf-v5-c-brand">${kcSanitize(msg("loginTitleHtml",(realm.displayNameHtml!'')))?no_esc}</div>
</header> </header>
<main class="pf-v5-c-login__main"> <main class="pf-v5-c-login__main">
<header class="pf-v5-c-login__main-header"> <div class="pf-v5-c-login__main-header">
<h1 class="pf-v5-c-title pf-m-3xl" id="kc-page-title"><#nested "header"></h1> <h1 class="pf-v5-c-title pf-m-3xl" id="kc-page-title"><#nested "header"></h1>
<#if realm.internationalizationEnabled && locale.supported?size gt 1> <#if realm.internationalizationEnabled && locale.supported?size gt 1>
<div class="pf-v5-c-login__main-header-utilities"> <div class="pf-v5-c-login__main-header-utilities">
@ -100,7 +99,7 @@
</div> </div>
</div> </div>
</#if> </#if>
</header> </div>
<div class="pf-v5-c-login__main-body"> <div class="pf-v5-c-login__main-body">
<#if !(auth?has_content && auth.showUsername() && !auth.showResetCredentials())> <#if !(auth?has_content && auth.showUsername() && !auth.showResetCredentials())>
<#if displayRequiredFields> <#if displayRequiredFields>
@ -180,8 +179,6 @@
<footer class="pf-v5-c-login__main-footer"> <footer class="pf-v5-c-login__main-footer">
<#nested "socialProviders"> <#nested "socialProviders">
</footer> </footer>
<@loginFooter.content/>
</main> </main>
</div> </div>
</div> </div>

View file

@ -5,9 +5,22 @@ styles=css/styles.css
stylesCommon=vendor/patternfly-v5/patternfly.min.css vendor/patternfly-v5/patternfly-addons.css stylesCommon=vendor/patternfly-v5/patternfly.min.css vendor/patternfly-v5/patternfly-addons.css
kcFormGroupClass=pf-v5-c-form__group kcFormGroupClass=pf-v5-c-form__group
kcFormGroupLabelClass=pf-v5-c-form__group-label
kcLabelClass=pf-v5-c-form__label kcLabelClass=pf-v5-c-form__label
kcInputClass=pf-v5-c-form-control kcInputClass=pf-v5-c-form-control
kcInputGroup=pf-v5-c-input-group kcInputGroup=pf-v5-c-input-group
kcFormHelperTextClass=pf-v5-c-form__helper-text
kcInputHelperTextClass=pf-v5-c-helper-text
kcInputHelperTextItemClass=pf-v5-c-helper-text__item
kcInputHelperTextItemTextClass=pf-v5-c-helper-text__item-text
kcInputGroupItemClass=pf-v5-c-input-group__item
kcFill=pf-m-fill
kcError=pf-m-error
kcCheckboxClass=pf-v5-c-check
kcCheckboxInputClass=pf-v5-c-check__input
kcCheckboxLabelClass=pf-v5-c-check__label
kcCheckboxLabelRequiredClass=pf-v5-c-check__label-required
kcInputErrorMessageClass=pf-v5-c-helper-text__item pf-m-error pf-v5-c-form__label-required kc-feedback-text kcInputErrorMessageClass=pf-v5-c-helper-text__item pf-m-error pf-v5-c-form__label-required kc-feedback-text
kcAlertClass=pf-v5-c-alert pf-m-inline kcAlertClass=pf-v5-c-alert pf-m-inline

View file

@ -1,3 +1,4 @@
<#import "field.ftl" as field>
<#macro userProfileFormFields> <#macro userProfileFormFields>
<#assign currentGroup=""> <#assign currentGroup="">
@ -35,26 +36,18 @@
</#if> </#if>
<#nested "beforeField" attribute> <#nested "beforeField" attribute>
<div class="${properties.kcFormGroupClass!}">
<div class="${properties.kcLabelWrapperClass!}"> <@field.group name=attribute.name label=advancedMsg(attribute.displayName!'') error=kcSanitize(messagesPerField.get('${attribute.name}'))?no_esc required=attribute.required>
<label for="${attribute.name}" class="${properties.kcLabelClass!}">${advancedMsg(attribute.displayName!'')}</label>
<#if attribute.required>*</#if>
</div>
<div class="${properties.kcInputWrapperClass!}"> <div class="${properties.kcInputWrapperClass!}">
<#if attribute.annotations.inputHelperTextBefore??> <#if attribute.annotations.inputHelperTextBefore??>
<div class="${properties.kcInputHelperTextBeforeClass!}" id="form-help-text-before-${attribute.name}" aria-live="polite">${kcSanitize(advancedMsg(attribute.annotations.inputHelperTextBefore))?no_esc}</div> <div class="${properties.kcInputHelperTextBeforeClass!}" id="form-help-text-before-${attribute.name}" aria-live="polite">${kcSanitize(advancedMsg(attribute.annotations.inputHelperTextBefore))?no_esc}</div>
</#if> </#if>
<@inputFieldByType attribute=attribute/> <@inputFieldByType attribute=attribute/>
<#if messagesPerField.existsError('${attribute.name}')>
<span id="input-error-${attribute.name}" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
${kcSanitize(messagesPerField.get('${attribute.name}'))?no_esc}
</span>
</#if>
<#if attribute.annotations.inputHelperTextAfter??> <#if attribute.annotations.inputHelperTextAfter??>
<div class="${properties.kcInputHelperTextAfterClass!}" id="form-help-text-after-${attribute.name}" aria-live="polite">${kcSanitize(advancedMsg(attribute.annotations.inputHelperTextAfter))?no_esc}</div> <div class="${properties.kcInputHelperTextAfterClass!}" id="form-help-text-after-${attribute.name}" aria-live="polite">${kcSanitize(advancedMsg(attribute.annotations.inputHelperTextAfter))?no_esc}</div>
</#if> </#if>
</div> </div>
</div> </@field.group>
<#nested "afterField" attribute> <#nested "afterField" attribute>
</#list> </#list>
@ -88,23 +81,25 @@
</#macro> </#macro>
<#macro inputTag attribute value> <#macro inputTag attribute value>
<input type="<@inputTagType attribute=attribute/>" id="${attribute.name}" name="${attribute.name}" value="${(value!'')}" class="${properties.kcInputClass!}" <span class="${properties.kcInputClass} <#if error?has_content>${properties.kcError}</#if>">
aria-invalid="<#if messagesPerField.existsError('${attribute.name}')>true</#if>" <input type="<@inputTagType attribute=attribute/>" id="${attribute.name}" name="${attribute.name}" value="${(value!'')}" class="${properties.kcInputClass!}"
<#if attribute.readOnly>disabled</#if> aria-invalid="<#if messagesPerField.existsError('${attribute.name}')>true</#if>"
<#if attribute.autocomplete??>autocomplete="${attribute.autocomplete}"</#if> <#if attribute.readOnly>disabled</#if>
<#if attribute.annotations.inputTypePlaceholder??>placeholder="${advancedMsg(attribute.annotations.inputTypePlaceholder)}"</#if> <#if attribute.autocomplete??>autocomplete="${attribute.autocomplete}"</#if>
<#if attribute.annotations.inputTypePattern??>pattern="${attribute.annotations.inputTypePattern}"</#if> <#if attribute.annotations.inputTypePlaceholder??>placeholder="${advancedMsg(attribute.annotations.inputTypePlaceholder)}"</#if>
<#if attribute.annotations.inputTypeSize??>size="${attribute.annotations.inputTypeSize}"</#if> <#if attribute.annotations.inputTypePattern??>pattern="${attribute.annotations.inputTypePattern}"</#if>
<#if attribute.annotations.inputTypeMaxlength??>maxlength="${attribute.annotations.inputTypeMaxlength}"</#if> <#if attribute.annotations.inputTypeSize??>size="${attribute.annotations.inputTypeSize}"</#if>
<#if attribute.annotations.inputTypeMinlength??>minlength="${attribute.annotations.inputTypeMinlength}"</#if> <#if attribute.annotations.inputTypeMaxlength??>maxlength="${attribute.annotations.inputTypeMaxlength}"</#if>
<#if attribute.annotations.inputTypeMax??>max="${attribute.annotations.inputTypeMax}"</#if> <#if attribute.annotations.inputTypeMinlength??>minlength="${attribute.annotations.inputTypeMinlength}"</#if>
<#if attribute.annotations.inputTypeMin??>min="${attribute.annotations.inputTypeMin}"</#if> <#if attribute.annotations.inputTypeMax??>max="${attribute.annotations.inputTypeMax}"</#if>
<#if attribute.annotations.inputTypeStep??>step="${attribute.annotations.inputTypeStep}"</#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> <#if attribute.annotations.inputTypeStep??>step="${attribute.annotations.inputTypeStep}"</#if>
data-${key}="${value}" <#list attribute.html5DataAnnotations as key, value>
</#list> data-${key}="${value}"
/> </#list>
/>
</span>
</#macro> </#macro>
<#macro inputTagType attribute> <#macro inputTagType attribute>