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);
|