To style when raw Form elements alone are not sufficient.
<form action="" class="c-form c-form--login">
<h3 class="c-form__title">
<img src="https://www.tacc.utexas.edu/static/site_cms/img/org_logos/tacc-formal.svg" alt="TACC Logo" />
<span>Log In</span>
</h3>
<p class="c-form__desc">to continue to the TACC User Portal</p>
<ul class="c-form__errors" style="display:none;">
<li>
Sample <strong>form</strong>
error. <a href="https://example.com" target="_blank">Link.</a>
</li>
</ul>
<div class="c-form__field has-required">
<label for="username">
User Name
<span class="c-form__star">Required</span>
</label>
<input name="username" type="text" autocomplete="username" id="username" required>
</div>
<div class="c-form__field has-required">
<label for="password">
Password
<span class="c-form__star">Required</span>
</label>
<input name="password" type="password" autocomplete="current-password" id="password" required>
</div>
<footer class="c-form__buttons">
<a href="https://accounts.tacc.utexas.edu/register" rel="noreferrer" target="_blank">
Create Account
</a>
<button class="c-form__button" type="submit">
Log In
</button>
</footer>
<nav class="c-form__nav">
<p>Having trouble logging in?</p>
<a href="https://example.com" rel="noreferrer" target="_blank">
Reset Password
</a>
<a href="https://example.com" rel="noreferrer" target="_blank">
Account Help
</a>
</nav>
</form>
<script type="module">
import toggleFieldErrors from '../raw/c-form/toggle-field-errors.js';
const form = document.querySelector('form');
const selector = '.c-form__errors';
form.addEventListener('submit', event => {
event.preventDefault();
console.log({
form,
selector
});
toggleFieldErrors(form, selector);
});
</script>
<hr />
<form action="" class="c-form c-form--login">
<h3 class="c-form__title">
<figure>
<img src="https://www.tacc.utexas.edu/static/site_cms/img/org_logos/ut-primary.svg" alt="UT Logo" />
<img src="https://www.tacc.utexas.edu/static/site_cms/img/org_logos/tacc-formal.svg" alt="TACC Logo" />
</figure>
<span>Log In</span>
</h3>
<p class="c-form__desc">to continue to the TACC User Portal</p>
<ul class="c-form__errors" style="display:none;">
<li>
Sample <strong>form</strong>
error. <a href="https://example.com" target="_blank">Link.</a>
</li>
</ul>
<div class="c-form__field has-required">
<label for="username">
User Name
<span class="c-form__star">Required</span>
</label>
<input name="username" type="text" autocomplete="username" id="username" required>
</div>
<div class="c-form__field has-required">
<label for="password">
Password
<span class="c-form__star">Required</span>
</label>
<input name="password" type="password" autocomplete="current-password" id="password" required>
</div>
<footer class="c-form__buttons">
<a href="https://accounts.tacc.utexas.edu/register" rel="noreferrer" target="_blank">
Create Account
</a>
<button class="c-form__button" type="submit">
Log In
</button>
</footer>
<nav class="c-form__nav">
<p>Having trouble logging in?</p>
<a href="https://example.com" rel="noreferrer" target="_blank">
Reset Password
</a>
<a href="https://example.com" rel="noreferrer" target="_blank">
Account Help
</a>
</nav>
</form>
<script type="module">
import toggleFieldErrors from '../raw/c-form/toggle-field-errors.js';
const form = document.querySelector('form');
const selector = '.c-form__errors';
form.addEventListener('submit', event => {
event.preventDefault();
console.log({
form,
selector
});
toggleFieldErrors(form, selector);
});
</script>
/** To toggle error list visibility */
/**
* @constant
* @default
* @type {object.<string,*>}
*/
const DEFAULT_CLASS_NAMES = {
jsShow: 'js-show-errors',
jsHide: 'js-hide-errors'
};
/**
* @constant
* @default
* @type {object.<string,*>}
*/
const DEFAULT_CALLBACKS = {
hideEach: function ( list ) {
list.style.display = 'none';
},
showEach: function ( list ) {
list.style.display = '';
},
showAll: ( lists ) => {
lists[0].scrollIntoView( false );
}
};
/**
* On each error list hide/show
* @callback onEach
* @param {HTMLElement} list
*/
/**
* After all error lists are hidden/shown
* @callback afterAll
* @param {array.<HTMLElement>} lists
*/
/**
* HIDE an error list
* @param {HTMLInputElement|HTMLSelectElement|HTMLTextAreaElement} field
* @param {string} errorListClass - class on field's wrapper
*/
function hideErrorList( list, classNames, callback ) {
list.classList.add( classNames.jsHide );
list.classList.remove( classNames.jsShow );
if ( typeof callback === 'function' ) {
callback( list );
}
}
/**
* SHOW an error list
* @param {HTMLInputElement|HTMLSelectElement|HTMLTextAreaElement} field
* @param {string} errorListClass - class on field's wrapper
*/
function showErrorList( list, classNames, callback ) {
list.classList.add( classNames.jsShow );
list.classList.remove( classNames.jsHide );
if ( typeof callback === 'function' ) {
callback( list );
}
}
/**
* Toggle error lists' visibility
* @param {Document|HTMLElement} [scope=document] - where to search for lists
* @param {string} [selector] - selector to find error lists
* @param {object.<string,function>} [optCallbacks]
* @param {onEach} [optCallbacks.showEach] - on each list show
* @param {onEach} [optCallbacks.hideEach] - on each list hide
* @param {afterAll} [optCallbacks.showAll] - on all lists shown
* @param {afterAll} [optCallbacks.hideAll] - on all lists hidden
*/
export default function toggleErrorLists( scope, selector, optCallbacks ) {
const classNames = DEFAULT_CLASS_NAMES;
const callbacks = Object.assign( DEFAULT_CALLBACKS, optCallbacks );
const lists = scope.querySelectorAll( selector );
let didHide = false;
let didShow = false;
[ ...lists ].forEach( list => {
if ( list.classList.contains( classNames.jsShow ) ) {
hideErrorList( list, classNames, callbacks.hideEach );
didHide = true;
} else {
showErrorList( list, classNames, callbacks.showEach );
didShow = true;
}
});
if ( didHide && typeof callbacks.hideAll === 'function' ) {
callbacks.hideAll( lists );
}
if ( didShow && typeof callbacks.showAll === 'function' ) {
callbacks.showAll( lists );
}
}
/** To toggle required attribute (and classes) of fields (and field wrappers) */
/**
* @constant
* @default
* @type {object.<string,*>}
*/
const DEFAULT_CLASS_NAMES = {
/* The class added when required state has been changed and is active */
jsIs: 'js-is-required',
/* The class added when required state has been changed and is not active */
jsNot: 'js-not-required',
/* The class for the wrapper of a field that is required */
wrapRequired: undefined,
};
/**
* @constant
* @default
* @type {object.<*,*>}
*/
const DEFAULT_OPTIONS = {
/* @type {boolean} - should scroll to the first required field */
shouldScrollToFirst: false,
/* @type {DEFAULT_CLASS_NAMES} */
classNames: {}
};
/**
* @constant
* @default
* @type {object.<string,*>}
*/
const DEFAULT_SELECTORS = {
/* The wrapper of a field */
wrap: 'div'
};
/**
* Make a field NOT required (and update field wrapper accordingly)
* @param {HTMLInputElement|HTMLSelectElement|HTMLTextAreaElement} field
* @param {DEFAULT_CLASS_NAMES} classNames
* @param {DEFAULT_SELECTORS} selectors
*/
function unRequireField( field, classNames, selectors ) {
const wrap = field.closest( selectors.wrap );
field.required = false;
field.classList.add( classNames.jsNot );
field.classList.remove( classNames.jsIs );
if ( wrap && classNames.wrapRequired ) {
wrap.classList.remove( classNames.wrapRequired );
}
}
/**
* Make a field REQUIRED (and update field wrapper accordingly)
* @param {HTMLInputElement|HTMLSelectElement|HTMLTextAreaElement} field
* @param {DEFAULT_CLASS_NAMES} classNames
* @param {DEFAULT_SELECTORS} selectors
*/
function requireField( field, classNames, selectors ) {
const wrap = field.closest( selectors.wrap );
field.required = true;
field.classList.add( classNames.jsIs );
field.classList.remove( classNames.jsNot );
if ( wrap && classNames.wrapRequired ) {
wrap.classList.add( classNames.wrapRequired );
}
}
/**
* Toggle required attribute (and classes) of fields (and field wrappers)
* @param {Document|HTMLElement} [scope=document] - where to search for fields
* @param {object.<string,*>} [opts]
* @param {DEFAULT_CLASS_NAMES} [opts.classNames] - to scroll to first such field
* @param {DEFAULT_SELECTORS} [opts.selectors] - to scroll to first such field
* @param {boolean} [opts.shouldScrollToFirst] - to scroll to first such field
*/
export default function toggleRequiredFields( scope = document, opts ) {
const classNames = Object.assign( DEFAULT_CLASS_NAMES, opts.classNames );
const selectors = Object.assign( DEFAULT_SELECTORS, opts.selectors );
const options = Object.assign( DEFAULT_OPTIONS, opts );
const requiredFields = scope.querySelectorAll(`
[required]:is(input, select, textarea),
.` + classNames.jsNot + `
`);
[ ...requiredFields ].forEach( field => {
if ( field.hasAttribute('required') ) {
unRequireField( field, classNames, selectors );
} else {
requireField( field, classNames, selectors );
}
});
if ( options.shouldScrollToFirst ) {
requiredFields[0].scrollIntoView( false );
}
}
{
"shouldSkipPattern": true,
"bootstrap4Styles": [
{
"isInternal": true,
"layer": "foundation",
"path": "/assets/core-styles.bootstrap4.css"
}
],
"globalStyles": [
{
"isInternal": true,
"layer": "base",
"path": "/assets/core-styles.demo.css"
},
{
"isInternal": true,
"layer": "base",
"path": "/assets/core-styles.base.css"
}
],
"cmsStyles": [
{
"isInternal": true,
"layer": "base",
"path": "/assets/core-styles.cms.css"
}
],
"docsStyles": [
{
"isInternal": true,
"layer": "base",
"path": "/assets/core-styles.docs.css"
}
],
"portalStyles": [
{
"isInternal": true,
"layer": "base",
"path": "/assets/core-styles.portal.css"
}
],
"subdir": "components",
"helpTextTag": "div",
"inputListTag": "menu",
"selectors": {
"errorList": ".c-form__errors",
"wrap": ".c-form__field"
},
"classNames": {
"root": "c-form",
"title": "c-form__title",
"desc": "c-form__desc",
"errors": "c-form__errors",
"field": "c-form__field",
"badge": "c-form__star",
"hint": "c-form__help",
"has_required": "has-required",
"has_checkbox": "has-type-check",
"has_spam_filter": "has-spam-check",
"buttons": "c-form__buttons",
"button": "c-form__button",
"nav": "c-form__nav",
"modifier": "c-form--login"
},
"shouldLoadPortal": true,
"supportStyles": [
"../../assets/components/c-form--login.css"
]
}