PageSourceSearch

https://www.portseattle.org/core/misc/message.js?v=11.3.10

js portseattle.org collected 2026-09-24 08:38:32 UTC 8,417 bytes, 267 lines download raw bytes

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.