UNPKG

@atlassian/aui

Version:

Atlassian User Interface library

238 lines (195 loc) 7.25 kB
import $ from './jquery'; import amdify from './internal/amdify'; import skate from './internal/skate'; import './spinner'; import { getMessageLogger } from './internal/deprecation'; const CLASS_NOTIFICATION_INITIALISED = '_aui-form-notification-initialised'; const ARIA_INFO_ATTRIBUTE = 'aria-describedby'; const ARIA_DESCRIPTION_POSTFIX = 'description'; const ARIA_ERROR_POSTFIX = 'errors'; const ATTRIBUTE_NOTIFICATION_PREFIX = 'data-aui-notification-'; const ATTRIBUTE_NOTIFICATION_WAIT = ATTRIBUTE_NOTIFICATION_PREFIX + 'wait'; const ATTRIBUTE_NOTIFICATION_INFO = ATTRIBUTE_NOTIFICATION_PREFIX + 'info'; const ATTRIBUTE_NOTIFICATION_ERROR = ATTRIBUTE_NOTIFICATION_PREFIX + 'error'; const ATTRIBUTE_NOTIFICATION_SUCCESS = ATTRIBUTE_NOTIFICATION_PREFIX + 'success'; const NOTIFICATION_PRIORITY = [ ATTRIBUTE_NOTIFICATION_ERROR, ATTRIBUTE_NOTIFICATION_SUCCESS, ATTRIBUTE_NOTIFICATION_WAIT, ATTRIBUTE_NOTIFICATION_INFO, ]; function initialiseNotification($field) { if (!isFieldInitialised($field)) { prepareFieldMarkup($field); synchroniseNotificationDisplay($field); } } function isFieldInitialised($field) { return $field.hasClass(CLASS_NOTIFICATION_INITIALISED); } function prepareFieldMarkup($field) { $field.addClass(CLASS_NOTIFICATION_INITIALISED); appendDescription($field); } function appendDescription($field, message) { message = message ? message : getNotificationMessage($field); if (getFieldNotificationType($field) === ATTRIBUTE_NOTIFICATION_INFO) { const existingDescription = $field.parent().find('.description'); if (!existingDescription.length) { $field.after(descriptionTemplate($field, message)); } } updateAriaInfo($field); } function getNotificationMessage($field) { var notificationType = getFieldNotificationType($field); var message = notificationType ? $field.attr(notificationType) : ''; return message === '' ? message : jsonToArray(message); } function jsonToArray(jsonOrString) { var jsonArray; try { jsonArray = JSON.parse(jsonOrString); // eslint-disable-next-line no-unused-vars } catch (exception) { jsonArray = [jsonOrString]; } return jsonArray; } function getFieldNotificationType($field) { let fieldNotificationType; NOTIFICATION_PRIORITY.some(function (prioritisedNotification) { if ($field.is('[' + prioritisedNotification + ']')) { fieldNotificationType = prioritisedNotification; return true; } }); return fieldNotificationType; } function synchroniseNotificationDisplay(field) { const $field = $(field); if (!isFieldInitialised($field)) { return; } const type = getFieldNotificationType($field); const showSpinner = type === ATTRIBUTE_NOTIFICATION_WAIT; setFieldSpinner($field, showSpinner); const message = getNotificationMessage($field); if (message && type === ATTRIBUTE_NOTIFICATION_ERROR) { appendErrorMessages($field, message); return; } // the first call of this method is executed on init with jQuery wrapped object // subsequent ones are the ones we care about and those are executed with DOM objects if (!isJqueryObject(field) && !field.hasAttribute(ATTRIBUTE_NOTIFICATION_ERROR)) { $field.parent().find('.error').remove(); } } function updateAriaInfo($field) { const labels = []; const id = $field.attr('id') || $field.attr('name'); const type = getFieldNotificationType($field); const message = getNotificationMessage($field); if (message && type === ATTRIBUTE_NOTIFICATION_ERROR) { labels.push(`${id}-${ARIA_ERROR_POSTFIX}`); } if (getMessageContainer($field, 'description').length) { labels.push(`${id}-${ARIA_DESCRIPTION_POSTFIX}`); } const $ariaTarget = conditionallyGetFieldTarget($field); $ariaTarget.attr(ARIA_INFO_ATTRIBUTE, !!labels.length ? labels.join(' ') : null); } /** * During the process of improving A11Y in the project, * we discovered that for some form elements we need to * make a shift of the target where aria-* attributes * are to be applied. * * This function contains the mapping and returns * the desired target, if it was found. * * If not - the same $field is returned back. */ function conditionallyGetFieldTarget($field) { const modifiers = { 'aui-select': 'input[type="text"][role="combobox"]', }; for (let [source, selector] of Object.entries(modifiers)) { if ($field.is(source)) { const $target = $field.find(selector); if ($target.length) { return $target; } } } return $field; } function isJqueryObject(el) { return el.constructor.prototype.hasOwnProperty('jquery'); } function errorMessageTemplate($field, messages) { const id = $field.attr('id') || $field.attr('name'); const list = messages .map( (message) => `<li><span class="aui-icon aui-icon-small aui-iconfont-error aui-icon-notification"></span>${message}</li>` ) .join(''); return `<div class="error" role="alert" id="${id}-${ARIA_ERROR_POSTFIX}"><ul>${list}</ul></div>`; } function descriptionTemplate($field, messages) { const id = $field.attr('id') || $field.attr('name'); if (messages.length > 1) { let list = messages.map((message) => `<li>${message}</li>`).join(''); return `<div class="description" id="${id}-${ARIA_DESCRIPTION_POSTFIX}"><ul>${list}</ul></div>`; } return `<div class="description" id="${id}-${ARIA_DESCRIPTION_POSTFIX}">${messages}</div>`; } function appendErrorMessages($field, messages) { let previousErrors = getMessageContainer($field, 'error'); if (previousErrors.length > 0) { previousErrors.remove(); } $field.after(errorMessageTemplate($field, messages)); updateAriaInfo($field); } function getMessageContainer($field, type) { return $field.parent().find(`.${type}`); } function isSpinnerForFieldAlreadyExisting($field) { return $field.next('aui-spinner').length > 0; } function setFieldSpinner($field, isSpinnerVisible) { if (isSpinnerVisible && !isSpinnerForFieldAlreadyExisting($field)) { $field.after('<aui-spinner class="form-notification-spinner" size="small"></aui-spinner>'); } else { $field.parent().find('aui-spinner').remove(); } } const deprecationLogger = getMessageLogger('data-aui-notification-field attribute', { deprecationType: 'ATTRIBUTE', alternativeName: 'HTML markup', }); skate('data-aui-notification-field', { attached: function (element) { deprecationLogger(); initialiseNotification($(element)); }, attributes: (function () { const attrs = {}; NOTIFICATION_PRIORITY.forEach(function (type) { attrs[type] = synchroniseNotificationDisplay; }); return attrs; })(), type: skate.type.ATTRIBUTE, }); amdify('aui/form-notification'); export { getMessageContainer, appendErrorMessages, appendDescription, updateAriaInfo, errorMessageTemplate, setFieldSpinner, };