message.es6.js 7.6 KB

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