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 // Search for the element with '[data-drupal-messages]' selector. 41 // If not found then only try to search for fallback element. 42 let wrapper = 43 document.querySelector('[data-drupal-messages]') || 44 document.querySelector('[data-drupal-messages-fallback]'); 45 if (!wrapper) { 46 // If no status messages element is found, a fallback element is created to prevent 47 // execution-breaking JS errors when attempting to report a problem. 48 // This scenario can occur on any page that does not include a status_messages 49 // render element. 50 wrapper = document.createElement('div'); 51 document.body.appendChild(wrapper); 52 } 53 54 if (wrapper.hasAttribute('data-drupal-messages-fallback')) { 55 // Remove the fallback attribute if it exists. 56 wrapper.removeAttribute('data-drupal-messages-fallback'); 57 wrapper.classList.remove('hidden'); 58 } 59 wrapper.setAttribute('data-drupal-messages', ''); 60 61 return wrapper.innerHTML === '' 62 ? Drupal.Message.messageInternalWrapper(wrapper) 63 : wrapper.firstElementChild; 64 } 65 66 /** 67 * Provide an object containing the available message types. 68 * 69 * @return {Object} 70 * An object containing message type strings. 71 */ 72 static getMessageTypeLabels() { 73 return { 74 status: Drupal.t('Status message'), 75 error: Drupal.t('Error message'), 76 warning: Drupal.t('Warning message'), 77 }; 78 } 79 80 /** 81 * Sequentially adds a message to the message area. 82 * 83 * @name Drupal.Message~messageDefinition.add 84 * 85 * @param {string} message 86 * The message to display 87 * @param {object} [options] 88 * The context of the message. 89 * @param {string} [options.id] 90 * The message ID, it can be a simple value: `'file_validation_error'` 91 * or several values separated by a space: `'my_module form_validation'` 92 * which can be used as an explicit selector for a message. 93 * @param {string} [options.type=status] 94 * Message type, can be either 'status', 'error' or 'warning'. 95 * @param {string} [options.announce] 96 * Screen-reader version of the message if necessary. To prevent a message 97 * being sent to Drupal.announce() this should be an empty string. 98 * @param {string} [options.priority] 99 * Priority of the message for Drupal.announce(). 100 * 101 * @return {string} 102 * ID of message. 103 */ 104 add(message, options = {}) { 105 if (!options.hasOwnProperty('type')) { 106 options.type = 'status'; 107 } 108 109 if (typeof message !== 'string') { 110 throw new Error('Message must be a string.'); 111 } 112 113 // Send message to screen reader. 114 Drupal.Message.announce(message, options); 115 /** 116 * Use the provided index for the message or generate a pseudo-random key 117 * to allow message deletion. 118 */ 119 options.id = options.id 120 ? String(options.id) 121 : `${options.type}-${Math.random().toFixed(15).replace('0.', '')}`; 122 123 // Throw an error if an unexpected message type is used.
124 if (!Drupal.Message.getMessageTypeLabels().hasOwnProperty(options.type)) { 125 const { type } = options; 126 throw new Error( 127 `The message type, ${type}, is not present in Drupal.Message.getMessageTypeLabels().`, 128 ); 129 } 130 131 this.messageWrapper.appendChild( 132 Drupal.theme('message', { text: message }, options), 133 ); 134 135 return options.id; 136 } 137 138 /** 139 * Select a message based on id. 140 * 141 * @name Drupal.Message~messageDefinition.select 142 * 143 * @param {string} id 144 * The message id to delete from the area. 145 * 146 * @return {Element} 147 * Element found. 148 */ 149 select(id) { 150 return this.messageWrapper.querySelector( 151 `[data-drupal-message-id^="${id}"]`, 152 ); 153 } 154 155 /** 156 * Removes a message element from the message area. 157 * 158 * @name Drupal.Message~messageDefinition.remove 159 * 160 * @param {string} id 161 * The unique identifier of the message to remove, as returned by 162 * {@link Drupal.Message~messageDefinition.add}. 163 * 164 * @return {Element} 165 * Returns the removed message element. 166 */ 167 remove(id) { 168 return this.messageWrapper.removeChild(this.select(id)); 169 } 170 171 /** 172 * Removes all messages from the message area. 173 * 174 * @name Drupal.Message~messageDefinition.clear 175 */ 176 clear() { 177 Array.prototype.forEach.call( 178 this.messageWrapper.querySelectorAll('[data-drupal-message-id]'), 179 (message) => { 180 this.messageWrapper.removeChild(message); 181 }, 182 ); 183 } 184 185 /** 186 * Helper to call Drupal.announce() with the right parameters. 187 * 188 * @param {string} message 189 * Displayed message. 190 * @param {object} options 191 * Additional data. 192 * @param {string} [options.announce] 193 * Screen-reader version of the message if necessary. To prevent a message 194 * being sent to Drupal.announce() this should be `''`. 195 * @param {string} [options.priority] 196 * Priority of the message for Drupal.announce(). 197 * @param {string} [options.type] 198 * Message type, can be either 'status', 'error' or 'warning'. 199 */ 200 static announce(message, options) { 201 if ( 202 !options.priority && 203 (options.type === 'warning' || options.type === 'error') 204 ) { 205 options.priority = 'assertive'; 206 } 207 /** 208 * If screen reader message is not disabled announce screen reader 209 * specific text or fallback to the displayed message. 210 */ 211 if (options.announce !== '') { 212 Drupal.announce(options.announce || message, options.priority); 213 } 214 } 215 216 /** 217 * Function for creating the internal message wrapper element. 218 * 219 * @param {HTMLElement} messageWrapper 220 * The message wrapper. 221 * 222 * @return {HTMLElement} 223 * The internal wrapper DOM element. 224 */ 225 static messageInternalWrapper(messageWrapper) { 226 const innerWrapper = document.createElement('div'); 227 innerWrapper.setAttribute('class', 'messages__wrapper'); 228 messageWrapper.insertAdjacentElement('afterbegin', innerWrapper); 229 return innerWrapper; 230 } 231 }; 232 233 /** 234 * Theme function for a message. 235 * 236 * @param {object} message 237 * The message object. 238 * @param {string} message.text 239 * The message text. 240 * @param {object} options 241 * The message context. 242 * @param {string} options.type 243 * The message type. 244 * @param {string} options.id 245 * ID of the message, for reference. 246 * 247 * @return {HTMLElement} 248 * A DOM Node. 249 */ 250 Drupal.theme.message = ({ text }, { type, id }) => { 251 const messagesTypes = Drupal.Message.getMessageTypeLabels(); 252 const messageWrapper = document.createElement('div'); 253 254 messageWrapper.setAttribute('class', `messages messages--${type}`); 255 messageWrapper.setAttribute( 256 'role', 257 type === 'error' || type === 'warning' ? 'alert' : 'status', 258 ); 259 messageWrapper.setAttribute('data-drupal-message-id', id); 260 messageWrapper.setAttribute('data-drupal-message-type', type); 261 262 messageWrapper.setAttribute('aria-label', messagesTypes[type]); 263 264 messageWrapper.innerHTML = `${text}`; 265 266 return messageWrapper; 267 }; 268})(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.