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