@atlassian/aui
Version:
Atlassian User Interface library
370 lines (323 loc) • 13.4 kB
JavaScript
import $ from './jquery';
import * as deprecate from './internal/deprecation';
import * as logger from './internal/log';
import globalize from './internal/globalize';
import escapeHtml from './escape-html';
import keyCode from './key-code';
import skate from './internal/skate';
import { CLOSE_BUTTON, CLOSE_BUTTON_CLASS_SELECTOR } from './close-button';
import { I18n } from './i18n';
const DEFAULT_FADEOUT_DURATION = 500;
const DEFAULT_FADEOUT_DELAY = 5000;
const FADEOUT_RESTORE_DURATION = 100;
function createMessageConstructor(type) {
/**
*
* @param context
* @param {Object} obj - message configuration
* @param {String} [obj.id] - ID to add to the message.
* @param {String} [obj.title] - Plain-text title of the message. If provided, will appear above the message body.
* @param {String} [obj.a11yTypeLabel] - Accessibility label that will communicate type of message non-visually
* @param {String} obj.body - Content of the message. Can be HTML content.
* @param {boolean} [obj.closeable] - If true, the message can be manually closed by the end-user via the UI.
* @param {boolean} [obj.removeOnHide] - If true, the message will be removed from the DOM after hide.
* @param {boolean} [obj.fadeout]
* @param {boolean} [obj.duration]
* @param {boolean} [obj.delay]
* @returns {*|HTMLElement}
*/
this[type] = function (context, obj) {
if (!obj) {
obj = context;
context = '#aui-message-bar';
}
// Set up our template options
obj.closeable = obj.closeable !== null && obj.closeable !== false;
// clean the title value
obj.title = (obj.title || '').toString().trim();
let $message = renderMessageElement(obj, type);
insertMessageIntoContext($message, obj.insert, context);
// Attach the optional extra behaviours
if (obj.removeOnHide) {
console.warn &&
console.warn(
'Use of AUI Message `removeOnHide` is deprecated due to accessibility reasons and will be removed in AUI 10.0.0.'
);
makeRemoveOnHide($message, obj.delay, obj.duration);
}
if (obj.closeable) {
makeCloseable($message);
}
if (obj.fadeout) {
console.warn &&
console.warn(
'Use of AUI Message `fadeout` is deprecated due to accessibility reasons and will be removed in AUI 10.0.0.'
);
makeFadeout($message, obj.delay, obj.duration);
}
return $message;
};
}
function makeRemoveOnHide(message, delay, duration) {
$(message || '.aui-message.aui-remove-on-hide').each(function () {
const $this = $(this);
makeFadeout($this, delay, duration);
});
}
function makeCloseable(message) {
$(message || 'div.aui-message.closeable').each(function () {
const $this = $(this);
const $closeIcons = $this.find(CLOSE_BUTTON_CLASS_SELECTOR);
const $icon = $closeIcons.length > 0 ? $closeIcons.first() : $(CLOSE_BUTTON);
$this.addClass('closeable');
$this.append($icon);
initCloseMessageBoxOnClickAndKeypress($this);
});
}
function makeFadeout(message, delay, duration) {
delay = typeof delay !== 'undefined' ? delay : DEFAULT_FADEOUT_DELAY;
duration = typeof duration !== 'undefined' ? duration : DEFAULT_FADEOUT_DURATION;
$(message || 'div.aui-message.fadeout').each(function () {
const $this = $(this);
//Store the component state to avoid collisions between animations
let hasFocus = false;
let isHover = false;
//Small functions to keep the code easier to read and avoid code duplication
function fadeOut() {
//Algorithm:
//1. Stop all running animations (first arg), including any fade animation and delay
// Do not jump to the end of the animation (second arg). This prevents the message to abruptly
// jump to opacity:0 or opacity:1
//2. Wait <delay> ms before starting the fadeout
//3. Start the fadeout with a duration of <duration> ms
//4. Close the message at the end of the animation
$this
.stop(true, false)
.delay(delay)
.fadeOut(duration, function () {
$this.closeMessage();
});
}
function resetFadeOut() {
//Algorithm:
//1. Stop all running animations (first arg), including any fade animation and delay
// Do not jump to the end of the animation (second arg). This prevents the message to abruptly
// jump to opacity:0 or opacity:1
//2. Fast animation to opacity:1
$this.stop(true, false).fadeTo(FADEOUT_RESTORE_DURATION, 1);
}
function shouldStartFadeOut() {
return !hasFocus && !isHover;
}
//Attach handlers for user interactions (focus and hover)
$this
.focusin(function () {
hasFocus = true;
resetFadeOut();
})
.focusout(function () {
hasFocus = false;
if (shouldStartFadeOut()) {
fadeOut();
}
})
.hover(
function () {
//should be called .hoverin(), but jQuery does not implement that method
isHover = true;
resetFadeOut();
},
function () {
//should be called .hoverout(), but jQuery does not implement that method
isHover = false;
if (shouldStartFadeOut()) {
fadeOut();
}
}
);
//Initial animation
fadeOut();
});
}
const messageFunctions = {
setup: function () {
makeRemoveOnHide();
makeCloseable();
makeFadeout();
},
makeRemoveOnHide: makeRemoveOnHide,
makeCloseable: makeCloseable,
makeFadeout: makeFadeout,
createMessage: createMessageConstructor,
};
function initCloseMessageBoxOnClickAndKeypress($message) {
$message.unbind('click.aui-message').unbind('keydown.aui-message');
$message
.on('click.aui-message', CLOSE_BUTTON_CLASS_SELECTOR, function (e) {
$(e.target).closest('.aui-message').closeMessage();
})
.on('keydown.aui-message', CLOSE_BUTTON_CLASS_SELECTOR, function (e) {
if (e.which === keyCode.ENTER || e.which === keyCode.SPACE) {
$(e.target).closest('.aui-message').closeMessage();
e.preventDefault(); // this is especially important when handling the space bar, as we don't want to page down
}
});
}
function insertMessageIntoContext($message, insertWhere, context) {
if (insertWhere === 'prepend') {
$message.prependTo(context);
} else if (insertWhere === 'before') {
$message.insertBefore(context);
} else if (insertWhere === 'after') {
$message.insertAfter(context);
} else {
$message.appendTo(context);
}
}
function getMessageA11yTypeText(obj, type) {
const { a11yTypeLabel } = obj;
if (a11yTypeLabel === '') {
return '';
}
const isA11yTypeLabelNotEmpty = a11yTypeLabel != null;
if (isA11yTypeLabelNotEmpty) {
return a11yTypeLabel;
}
const isA11yTypeLabelNotPassed = a11yTypeLabel == null;
if (isA11yTypeLabelNotPassed) {
switch (type) {
case 'error':
return I18n.getText('aui.messagecomponent.error.label');
case 'warning':
return I18n.getText('aui.messagecomponent.warning.label');
case 'info':
return I18n.getText('aui.messagecomponent.info.label');
case 'confirmation':
return I18n.getText('aui.messagecomponent.confirmation.label');
case 'change':
return I18n.getText('aui.messagecomponent.change.label');
case 'generic':
case 'success':
case 'hint':
return '';
default:
throw Error(`Provide \`a11yTypeLabel\` for AUI message of custom type ${type}`);
}
}
}
function renderMessageElement(obj, type) {
const { id, closeable, removeOnHide, fadeout, title, body } = obj;
const messageTypeText = getMessageA11yTypeText(obj, type);
const messageTypeLabel = messageTypeText ? `<strong hidden>${messageTypeText}: </strong>` : '';
// Convert the options in to template values
const titleId = id ? `${id}-title` : '';
const titleHtml = title
? `<p class="title" ${titleId ? `id="${titleId}" aria-hidden="true"` : ''}>${messageTypeLabel}<strong>${escapeHtml(title)}</strong></p>`
: '';
const html = `<div class="aui-message" role="note" ${titleId ? `aria-labelledby="${titleId}"` : ''}>${titleHtml}</div>`;
// Construct the message element
const $message = $(html)
.append($.parseHTML(body || ''))
.addClass(removeOnHide ? 'aui-remove-on-hide' : '')
.addClass(closeable ? 'closeable' : '')
.addClass(fadeout ? 'fadeout' : '')
.addClass(`aui-message-${type}`);
// Add ID if supplied
if (id) {
if (/[#'".\s]/g.test(id)) {
// reject IDs that don't comply with style guide (ie. they'll break stuff)
logger.warn(
'Messages error: ID rejected, must not include spaces, hashes, dots or quotes.'
);
} else {
$message.attr('id', id);
}
}
return $message;
}
$.fn.closeMessage = function () {
const $message = $(this);
if (
$message.hasClass('aui-message') &&
($message.hasClass('closeable') || $message.hasClass('aui-remove-on-hide'))
) {
$message.stop(true); //Stop any running animation
$message.trigger('messageClose', [this]); //messageClose event Deprecated as of 5.3
$message.remove();
$(document).trigger('aui-message-close', [this]); //must trigger on document since the element has been removed
}
};
/**
* Utility methods to display different message types to the user.
* Usage:
* <pre>
* messages.info("#container", {
* title: "Info",
* body: "You can choose to have messages without Close functionality.",
* closeable: false,
* });
* </pre>
*/
messageFunctions.createMessage('generic'); //Deprecated Oct 2017
messageFunctions.createMessage('error');
messageFunctions.createMessage('warning');
messageFunctions.createMessage('info');
messageFunctions.createMessage('confirmation');
messageFunctions.createMessage('change');
messageFunctions.createMessage('success'); //Deprecated Oct 2017
messageFunctions.createMessage('hint'); //Deprecated Oct 2017
const MessageEl = skate('aui-message', {
created: function (element) {
const body = element.innerHTML;
const type = element.getAttribute('type') || 'info';
element.innerHTML = '';
messageFunctions[type](element, {
body: body,
id: element.getAttribute('id'),
a11yTypeLabel: element.getAttribute('a11yTypeLabel'),
removeOnHide: element.getAttribute('removeOnHide'),
closeable: element.getAttribute('closeable'),
delay: element.getAttribute('delay'),
duration: element.getAttribute('duration'),
fadeout: element.getAttribute('fadeout'),
title: element.getAttribute('title'),
});
element.setAttribute('role', 'none');
},
});
$(function () {
messageFunctions.setup();
});
deprecate.prop(messageFunctions, 'makeCloseable', {
extraInfo:
'Use the "closeable" option in the constructor instead. Docs: https://aui.atlassian.com/latest/docs/messages.html',
});
deprecate.prop(messageFunctions, 'createMessage', {
extraInfo:
'Use the provided convenience methods instead e.g. messages.info(). Docs: https://aui.atlassian.com/latest/docs/messages.html',
});
deprecate.prop(messageFunctions, 'makeRemoveOnHide', {
extraInfo:
'Use of the `makeRemoveOnHide` option is deprecated due to accessibility reasons. Docs: https://aui.atlassian.com/latest/docs/messages.html',
});
deprecate.prop(messageFunctions, 'makeFadeout', {
extraInfo:
'Use of the `fadeout` option is deprecated due to accessibility reasons. Docs: https://aui.atlassian.com/latest/docs/messages.html',
});
deprecate.prop(messageFunctions, 'generic', {
extraInfo:
'use the messages.info() method instead. Docs: https://aui.atlassian.com/latest/docs/messages.html',
});
deprecate.prop(messageFunctions, 'hint', {
extraInfo:
'use the messages.info() method instead. Docs: https://aui.atlassian.com/latest/docs/messages.html',
});
deprecate.prop(messageFunctions, 'success', {
extraInfo:
'use the messages.confirmation() method instead. Docs: https://aui.atlassian.com/latest/docs/messages.html',
});
// Exporting
// ---------
globalize('messages', messageFunctions);
export default messageFunctions;
export { MessageEl };