@atlassian/aui
Version:
Atlassian User Interface library
293 lines (253 loc) • 10.5 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';
var DEFAULT_FADEOUT_DURATION = 500;
var DEFAULT_FADEOUT_DELAY = 5000;
var 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.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}
*/
messages[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) {
makeRemoveOnHide($message, obj.delay, obj.duration);
}
if (obj.closeable) {
makeCloseable($message);
}
if (obj.fadeout) {
makeFadeout($message, obj.delay, obj.duration);
}
return $message;
};
}
function makeRemoveOnHide(message, delay, duration) {
$(message || '.aui-message.aui-remove-on-hide').each(function () {
var $this = $(this);
makeFadeout($this, delay, duration)
})
}
function makeCloseable(message) {
$(message || 'div.aui-message.closeable').each(function () {
var $this = $(this);
var $closeIcons = $this.find(CLOSE_BUTTON_CLASS_SELECTOR);
var $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 () {
var $this = $(this);
//Store the component state to avoid collisions between animations
var hasFocus = false;
var 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();
});
}
/**
* 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>
*/
var messages = {
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 renderMessageElement ({id, closeable, removeOnHide, fadeout, title, body}, type) {
// Convert the options in to template values
const titleHtml = title ? `<p class="title"><strong>${escapeHtml(title)}</strong></p>` : '';
const html = `<div class="aui-message">${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 () {
var $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
}
};
createMessageConstructor('generic'); //Deprecated Oct 2017
createMessageConstructor('error');
createMessageConstructor('warning');
createMessageConstructor('info');
createMessageConstructor('confirmation');
createMessageConstructor('change');
createMessageConstructor('success'); //Deprecated Oct 2017
createMessageConstructor('hint'); //Deprecated Oct 2017
const MessageEl = skate('aui-message', {
created: function (element) {
var body = element.innerHTML;
var type = element.getAttribute('type') || 'info';
element.innerHTML = '';
messages[type](element, {
body: body,
removeOnHide: element.getAttribute('removeOnHide'),
closeable: element.getAttribute('closeable'),
delay: element.getAttribute('delay'),
duration: element.getAttribute('duration'),
fadeout: element.getAttribute('fadeout'),
title: element.getAttribute('title')
});
}
});
$(function () {
messages.setup();
});
deprecate.prop(messages, 'makeCloseable', {
extraInfo: 'Use the "closeable" option in the constructor instead. Docs: https://aui.atlassian.com/latest/docs/messages.html'
});
deprecate.prop(messages, 'createMessage', {
extraInfo: 'Use the provided convenience methods instead e.g. messages.info(). Docs: https://aui.atlassian.com/latest/docs/messages.html'
});
deprecate.prop(messages, 'makeFadeout', {
extraInfo: 'Use the "fadeout" option in the constructor instead. Docs: https://aui.atlassian.com/latest/docs/messages.html'
});
deprecate.prop(messages, 'generic', {
extraInfo: 'use the messages.info() method instead. Docs: https://aui.atlassian.com/latest/docs/messages.html'
});
deprecate.prop(messages, 'hint', {
extraInfo: 'use the messages.info() method instead. Docs: https://aui.atlassian.com/latest/docs/messages.html'
});
deprecate.prop(messages, 'success', {
extraInfo: 'use the messages.confirmation() method instead. Docs: https://aui.atlassian.com/latest/docs/messages.html'
});
// Exporting
// ---------
globalize('messages', messages);
export default messages;
export {
MessageEl
};