annotate core/misc/message.es6.js @ 19:fa3358dc1485 tip

Add ndrum files
author Chris Cannam
date Wed, 28 Aug 2019 13:14:47 +0100
parents af1871eacc83
children
rev   line source
Chris@18 1 /**
Chris@18 2 * @file
Chris@18 3 * Message API.
Chris@18 4 */
Chris@18 5 (Drupal => {
Chris@18 6 /**
Chris@18 7 * @typedef {class} Drupal.Message~messageDefinition
Chris@18 8 */
Chris@18 9
Chris@18 10 /**
Chris@18 11 * Constructs a new instance of the Drupal.Message class.
Chris@18 12 *
Chris@18 13 * This provides a uniform interface for adding and removing messages to a
Chris@18 14 * specific location on the page.
Chris@18 15 *
Chris@18 16 * @param {HTMLElement} messageWrapper
Chris@18 17 * The zone where to add messages. If no element is provided an attempt is
Chris@18 18 * made to determine a default location.
Chris@18 19 *
Chris@18 20 * @return {Drupal.Message~messageDefinition}
Chris@18 21 * Class to add and remove messages.
Chris@18 22 */
Chris@18 23 Drupal.Message = class {
Chris@18 24 constructor(messageWrapper = null) {
Chris@18 25 this.messageWrapper = messageWrapper;
Chris@18 26 }
Chris@18 27
Chris@18 28 /**
Chris@18 29 * Attempt to determine the default location for
Chris@18 30 * inserting JavaScript messages or create one if needed.
Chris@18 31 *
Chris@18 32 * @return {HTMLElement}
Chris@18 33 * The default destination for JavaScript messages.
Chris@18 34 */
Chris@18 35 static defaultWrapper() {
Chris@18 36 let wrapper = document.querySelector('[data-drupal-messages]');
Chris@18 37 if (!wrapper) {
Chris@18 38 wrapper = document.querySelector('[data-drupal-messages-fallback]');
Chris@18 39 wrapper.removeAttribute('data-drupal-messages-fallback');
Chris@18 40 wrapper.setAttribute('data-drupal-messages', '');
Chris@18 41 wrapper.removeAttribute('class');
Chris@18 42 }
Chris@18 43 return wrapper.innerHTML === ''
Chris@18 44 ? Drupal.Message.messageInternalWrapper(wrapper)
Chris@18 45 : wrapper.firstElementChild;
Chris@18 46 }
Chris@18 47
Chris@18 48 /**
Chris@18 49 * Provide an object containing the available message types.
Chris@18 50 *
Chris@18 51 * @return {Object}
Chris@18 52 * An object containing message type strings.
Chris@18 53 */
Chris@18 54 static getMessageTypeLabels() {
Chris@18 55 return {
Chris@18 56 status: Drupal.t('Status message'),
Chris@18 57 error: Drupal.t('Error message'),
Chris@18 58 warning: Drupal.t('Warning message'),
Chris@18 59 };
Chris@18 60 }
Chris@18 61
Chris@18 62 /**
Chris@18 63 * Sequentially adds a message to the message area.
Chris@18 64 *
Chris@18 65 * @name Drupal.Message~messageDefinition.add
Chris@18 66 *
Chris@18 67 * @param {string} message
Chris@18 68 * The message to display
Chris@18 69 * @param {object} [options]
Chris@18 70 * The context of the message.
Chris@18 71 * @param {string} [options.id]
Chris@18 72 * The message ID, it can be a simple value: `'filevalidationerror'`
Chris@18 73 * or several values separated by a space: `'mymodule formvalidation'`
Chris@18 74 * which can be used as an explicit selector for a message.
Chris@18 75 * @param {string} [options.type=status]
Chris@18 76 * Message type, can be either 'status', 'error' or 'warning'.
Chris@18 77 * @param {string} [options.announce]
Chris@18 78 * Screen-reader version of the message if necessary. To prevent a message
Chris@18 79 * being sent to Drupal.announce() this should be an emptry string.
Chris@18 80 * @param {string} [options.priority]
Chris@18 81 * Priority of the message for Drupal.announce().
Chris@18 82 *
Chris@18 83 * @return {string}
Chris@18 84 * ID of message.
Chris@18 85 */
Chris@18 86 add(message, options = {}) {
Chris@18 87 if (!this.messageWrapper) {
Chris@18 88 this.messageWrapper = Drupal.Message.defaultWrapper();
Chris@18 89 }
Chris@18 90 if (!options.hasOwnProperty('type')) {
Chris@18 91 options.type = 'status';
Chris@18 92 }
Chris@18 93
Chris@18 94 if (typeof message !== 'string') {
Chris@18 95 throw new Error('Message must be a string.');
Chris@18 96 }
Chris@18 97
Chris@18 98 // Send message to screen reader.
Chris@18 99 Drupal.Message.announce(message, options);
Chris@18 100 /**
Chris@18 101 * Use the provided index for the message or generate a pseudo-random key
Chris@18 102 * to allow message deletion.
Chris@18 103 */
Chris@18 104 options.id = options.id
Chris@18 105 ? String(options.id)
Chris@18 106 : `${options.type}-${Math.random()
Chris@18 107 .toFixed(15)
Chris@18 108 .replace('0.', '')}`;
Chris@18 109
Chris@18 110 // Throw an error if an unexpected message type is used.
Chris@18 111 if (!Drupal.Message.getMessageTypeLabels().hasOwnProperty(options.type)) {
Chris@18 112 throw new Error(
Chris@18 113 `The message type, ${
Chris@18 114 options.type
Chris@18 115 }, is not present in Drupal.Message.getMessageTypeLabels().`,
Chris@18 116 );
Chris@18 117 }
Chris@18 118
Chris@18 119 this.messageWrapper.appendChild(
Chris@18 120 Drupal.theme('message', { text: message }, options),
Chris@18 121 );
Chris@18 122
Chris@18 123 return options.id;
Chris@18 124 }
Chris@18 125
Chris@18 126 /**
Chris@18 127 * Select a message based on id.
Chris@18 128 *
Chris@18 129 * @name Drupal.Message~messageDefinition.select
Chris@18 130 *
Chris@18 131 * @param {string} id
Chris@18 132 * The message id to delete from the area.
Chris@18 133 *
Chris@18 134 * @return {Element}
Chris@18 135 * Element found.
Chris@18 136 */
Chris@18 137 select(id) {
Chris@18 138 return this.messageWrapper.querySelector(
Chris@18 139 `[data-drupal-message-id^="${id}"]`,
Chris@18 140 );
Chris@18 141 }
Chris@18 142
Chris@18 143 /**
Chris@18 144 * Removes messages from the message area.
Chris@18 145 *
Chris@18 146 * @name Drupal.Message~messageDefinition.remove
Chris@18 147 *
Chris@18 148 * @param {string} id
Chris@18 149 * Index of the message to remove, as returned by
Chris@18 150 * {@link Drupal.Message~messageDefinition.add}.
Chris@18 151 *
Chris@18 152 * @return {number}
Chris@18 153 * Number of removed messages.
Chris@18 154 */
Chris@18 155 remove(id) {
Chris@18 156 return this.messageWrapper.removeChild(this.select(id));
Chris@18 157 }
Chris@18 158
Chris@18 159 /**
Chris@18 160 * Removes all messages from the message area.
Chris@18 161 *
Chris@18 162 * @name Drupal.Message~messageDefinition.clear
Chris@18 163 */
Chris@18 164 clear() {
Chris@18 165 Array.prototype.forEach.call(
Chris@18 166 this.messageWrapper.querySelectorAll('[data-drupal-message-id]'),
Chris@18 167 message => {
Chris@18 168 this.messageWrapper.removeChild(message);
Chris@18 169 },
Chris@18 170 );
Chris@18 171 }
Chris@18 172
Chris@18 173 /**
Chris@18 174 * Helper to call Drupal.announce() with the right parameters.
Chris@18 175 *
Chris@18 176 * @param {string} message
Chris@18 177 * Displayed message.
Chris@18 178 * @param {object} options
Chris@18 179 * Additional data.
Chris@18 180 * @param {string} [options.announce]
Chris@18 181 * Screen-reader version of the message if necessary. To prevent a message
Chris@18 182 * being sent to Drupal.announce() this should be `''`.
Chris@18 183 * @param {string} [options.priority]
Chris@18 184 * Priority of the message for Drupal.announce().
Chris@18 185 * @param {string} [options.type]
Chris@18 186 * Message type, can be either 'status', 'error' or 'warning'.
Chris@18 187 */
Chris@18 188 static announce(message, options) {
Chris@18 189 if (
Chris@18 190 !options.priority &&
Chris@18 191 (options.type === 'warning' || options.type === 'error')
Chris@18 192 ) {
Chris@18 193 options.priority = 'assertive';
Chris@18 194 }
Chris@18 195 /**
Chris@18 196 * If screen reader message is not disabled announce screen reader
Chris@18 197 * specific text or fallback to the displayed message.
Chris@18 198 */
Chris@18 199 if (options.announce !== '') {
Chris@18 200 Drupal.announce(options.announce || message, options.priority);
Chris@18 201 }
Chris@18 202 }
Chris@18 203
Chris@18 204 /**
Chris@18 205 * Function for creating the internal message wrapper element.
Chris@18 206 *
Chris@18 207 * @param {HTMLElement} messageWrapper
Chris@18 208 * The message wrapper.
Chris@18 209 *
Chris@18 210 * @return {HTMLElement}
Chris@18 211 * The internal wrapper DOM element.
Chris@18 212 */
Chris@18 213 static messageInternalWrapper(messageWrapper) {
Chris@18 214 const innerWrapper = document.createElement('div');
Chris@18 215 innerWrapper.setAttribute('class', 'messages__wrapper');
Chris@18 216 messageWrapper.insertAdjacentElement('afterbegin', innerWrapper);
Chris@18 217 return innerWrapper;
Chris@18 218 }
Chris@18 219 };
Chris@18 220
Chris@18 221 /**
Chris@18 222 * Theme function for a message.
Chris@18 223 *
Chris@18 224 * @param {object} message
Chris@18 225 * The message object.
Chris@18 226 * @param {string} message.text
Chris@18 227 * The message text.
Chris@18 228 * @param {object} options
Chris@18 229 * The message context.
Chris@18 230 * @param {string} options.type
Chris@18 231 * The message type.
Chris@18 232 * @param {string} options.id
Chris@18 233 * ID of the message, for reference.
Chris@18 234 *
Chris@18 235 * @return {HTMLElement}
Chris@18 236 * A DOM Node.
Chris@18 237 */
Chris@18 238 Drupal.theme.message = ({ text }, { type, id }) => {
Chris@18 239 const messagesTypes = Drupal.Message.getMessageTypeLabels();
Chris@18 240 const messageWrapper = document.createElement('div');
Chris@18 241
Chris@18 242 messageWrapper.setAttribute('class', `messages messages--${type}`);
Chris@18 243 messageWrapper.setAttribute(
Chris@18 244 'role',
Chris@18 245 type === 'error' || type === 'warning' ? 'alert' : 'status',
Chris@18 246 );
Chris@18 247 messageWrapper.setAttribute('data-drupal-message-id', id);
Chris@18 248 messageWrapper.setAttribute('data-drupal-message-type', type);
Chris@18 249
Chris@18 250 messageWrapper.setAttribute('aria-label', messagesTypes[type]);
Chris@18 251
Chris@18 252 messageWrapper.innerHTML = `${text}`;
Chris@18 253
Chris@18 254 return messageWrapper;
Chris@18 255 };
Chris@18 256 })(Drupal);