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 this.messageWrapper 178 .querySelectorAll('[data-drupal-message-id]') 179 .forEach((message) => { 180 this.messageWrapper.removeChild(message); 181 }); 182 } 183 184 /** 185 * Helper to call Drupal.announce() with the right parameters. 186 * 187 * @param {string} message 188 * Displayed message. 189 * @param {object} options 190 * Additional data. 191 * @param {string} [options.announce] 192 * Screen-reader version of the message if necessary. To prevent a message 193 * being sent to Drupal.announce() this should be `''`. 194 * @param {string} [options.priority] 195 * Priority of the message for Drupal.announce(). 196 * @param {string} [options.type] 197 * Message type, can be either 'status', 'error' or 'warning'. 198 */ 199 static announce(message, options) { 200 if ( 201 !options.priority && 202 (options.type === 'warning' || options.type === 'error') 203 ) { 204 options.priority = 'assertive'; 205 } 206 /** 207 * If screen reader message is not disabled announce screen reader 208 * specific text or fallback to the displayed message. 209 */ 210 if (options.announce !== '') { 211 Drupal.announce(options.announce || message, options.priority); 212 } 213 } 214 215 /** 216 * Function for creating the internal message wrapper element. 217 * 218 * @param {HTMLElement} messageWrapper 219 * The message wrapper. 220 * 221 * @return {HTMLElement} 222 * The internal wrapper DOM element. 223 */ 224 static messageInternalWrapper(messageWrapper) { 225 const innerWrapper = document.createElement('div'); 226 innerWrapper.setAttribute('class', 'messages__wrapper'); 227 messageWrapper.insertAdjacentElement('afterbegin', innerWrapper); 228 return innerWrapper; 229 } 230 }; 231 232 /** 233 * Theme function for a message. 234 * 235 * @param {object} message 236 * The message object. 237 * @param {string} message.text 238 * The message text. 239 * @param {object} options 240 * The message context. 241 * @param {string} options.type 242 * The message type. 243 * @param {string} options.id 244 * ID of the message, for reference. 245 * 246 * @return {HTMLElement} 247 * A DOM Node. 248 */ 249 Drupal.theme.message = ({ text }, { type, id }) => { 250 const messagesTypes = Drupal.Message.getMessageTypeLabels(); 251 const messageWrapper = document.createElement('div'); 252 253 messageWrapper.setAttribute('class', `messages messages--${type}`); 254 messageWrapper.setAttribute( 255 'role', 256 type === 'error' || type === 'warning' ? 'alert' : 'status', 257 ); 258 messageWrapper.setAttribute('data-drupal-message-id', id); 259 messageWrapper.setAttribute('data-drupal-message-type', type); 260 261 messageWrapper.setAttribute('aria-label', messagesTypes[type]); 262 263 messageWrapper.innerHTML = `${text}`; 264 265 return messageWrapper; 266 }; 267})(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.