Security update to Drupal 8.4.6
[yaffs-website] / web / core / misc / machine-name.es6.js
1 /**
2  * @file
3  * Machine name functionality.
4  */
5
6 (function ($, Drupal, drupalSettings) {
7   /**
8    * Attach the machine-readable name form element behavior.
9    *
10    * @type {Drupal~behavior}
11    *
12    * @prop {Drupal~behaviorAttach} attach
13    *   Attaches machine-name behaviors.
14    */
15   Drupal.behaviors.machineName = {
16
17     /**
18      * Attaches the behavior.
19      *
20      * @param {Element} context
21      *   The context for attaching the behavior.
22      * @param {object} settings
23      *   Settings object.
24      * @param {object} settings.machineName
25      *   A list of elements to process, keyed by the HTML ID of the form
26      *   element containing the human-readable value. Each element is an object
27      *   defining the following properties:
28      *   - target: The HTML ID of the machine name form element.
29      *   - suffix: The HTML ID of a container to show the machine name preview
30      *     in (usually a field suffix after the human-readable name
31      *     form element).
32      *   - label: The label to show for the machine name preview.
33      *   - replace_pattern: A regular expression (without modifiers) matching
34      *     disallowed characters in the machine name; e.g., '[^a-z0-9]+'.
35      *   - replace: A character to replace disallowed characters with; e.g.,
36      *     '_' or '-'.
37      *   - standalone: Whether the preview should stay in its own element
38      *     rather than the suffix of the source element.
39      *   - field_prefix: The #field_prefix of the form element.
40      *   - field_suffix: The #field_suffix of the form element.
41      */
42     attach(context, settings) {
43       const self = this;
44       const $context = $(context);
45       let timeout = null;
46       let xhr = null;
47
48       function clickEditHandler(e) {
49         const data = e.data;
50         data.$wrapper.removeClass('visually-hidden');
51         data.$target.trigger('focus');
52         data.$suffix.hide();
53         data.$source.off('.machineName');
54       }
55
56       function machineNameHandler(e) {
57         const data = e.data;
58         const options = data.options;
59         const baseValue = $(e.target).val();
60
61         const rx = new RegExp(options.replace_pattern, 'g');
62         const expected = baseValue.toLowerCase().replace(rx, options.replace).substr(0, options.maxlength);
63
64         // Abort the last pending request because the label has changed and it
65         // is no longer valid.
66         if (xhr && xhr.readystate !== 4) {
67           xhr.abort();
68           xhr = null;
69         }
70
71         // Wait 300 milliseconds for Ajax request since the last event to update
72         // the machine name i.e., after the user has stopped typing.
73         if (timeout) {
74           clearTimeout(timeout);
75           timeout = null;
76         }
77         if (baseValue.toLowerCase() !== expected) {
78           timeout = setTimeout(() => {
79             xhr = self.transliterate(baseValue, options).done((machine) => {
80               self.showMachineName(machine.substr(0, options.maxlength), data);
81             });
82           }, 300);
83         }
84         else {
85           self.showMachineName(expected, data);
86         }
87       }
88
89       Object.keys(settings.machineName).forEach((source_id) => {
90         let machine = '';
91         let eventData;
92         const options = settings.machineName[source_id];
93
94         const $source = $context.find(source_id).addClass('machine-name-source').once('machine-name');
95         const $target = $context.find(options.target).addClass('machine-name-target');
96         const $suffix = $context.find(options.suffix);
97         const $wrapper = $target.closest('.js-form-item');
98         // All elements have to exist.
99         if (!$source.length || !$target.length || !$suffix.length || !$wrapper.length) {
100           return;
101         }
102         // Skip processing upon a form validation error on the machine name.
103         if ($target.hasClass('error')) {
104           return;
105         }
106         // Figure out the maximum length for the machine name.
107         options.maxlength = $target.attr('maxlength');
108         // Hide the form item container of the machine name form element.
109         $wrapper.addClass('visually-hidden');
110         // Determine the initial machine name value. Unless the machine name
111         // form element is disabled or not empty, the initial default value is
112         // based on the human-readable form element value.
113         if ($target.is(':disabled') || $target.val() !== '') {
114           machine = $target.val();
115         }
116         else if ($source.val() !== '') {
117           machine = self.transliterate($source.val(), options);
118         }
119         // Append the machine name preview to the source field.
120         const $preview = $(`<span class="machine-name-value">${options.field_prefix}${Drupal.checkPlain(machine)}${options.field_suffix}</span>`);
121         $suffix.empty();
122         if (options.label) {
123           $suffix.append(`<span class="machine-name-label">${options.label}: </span>`);
124         }
125         $suffix.append($preview);
126
127         // If the machine name cannot be edited, stop further processing.
128         if ($target.is(':disabled')) {
129           return;
130         }
131
132         eventData = {
133           $source,
134           $target,
135           $suffix,
136           $wrapper,
137           $preview,
138           options,
139         };
140         // If it is editable, append an edit link.
141         const $link = $(`<span class="admin-link"><button type="button" class="link">${Drupal.t('Edit')}</button></span>`).on('click', eventData, clickEditHandler);
142         $suffix.append($link);
143
144         // Preview the machine name in realtime when the human-readable name
145         // changes, but only if there is no machine name yet; i.e., only upon
146         // initial creation, not when editing.
147         if ($target.val() === '') {
148           $source.on('formUpdated.machineName', eventData, machineNameHandler)
149             // Initialize machine name preview.
150             .trigger('formUpdated.machineName');
151         }
152
153         // Add a listener for an invalid event on the machine name input
154         // to show its container and focus it.
155         $target.on('invalid', eventData, clickEditHandler);
156       });
157     },
158
159     showMachineName(machine, data) {
160       const settings = data.options;
161       // Set the machine name to the transliterated value.
162       if (machine !== '') {
163         if (machine !== settings.replace) {
164           data.$target.val(machine);
165           data.$preview.html(settings.field_prefix + Drupal.checkPlain(machine) + settings.field_suffix);
166         }
167         data.$suffix.show();
168       }
169       else {
170         data.$suffix.hide();
171         data.$target.val(machine);
172         data.$preview.empty();
173       }
174     },
175
176     /**
177      * Transliterate a human-readable name to a machine name.
178      *
179      * @param {string} source
180      *   A string to transliterate.
181      * @param {object} settings
182      *   The machine name settings for the corresponding field.
183      * @param {string} settings.replace_pattern
184      *   A regular expression (without modifiers) matching disallowed characters
185      *   in the machine name; e.g., '[^a-z0-9]+'.
186      * @param {string} settings.replace_token
187      *   A token to validate the regular expression.
188      * @param {string} settings.replace
189      *   A character to replace disallowed characters with; e.g., '_' or '-'.
190      * @param {number} settings.maxlength
191      *   The maximum length of the machine name.
192      *
193      * @return {jQuery}
194      *   The transliterated source string.
195      */
196     transliterate(source, settings) {
197       return $.get(Drupal.url('machine_name/transliterate'), {
198         text: source,
199         langcode: drupalSettings.langcode,
200         replace_pattern: settings.replace_pattern,
201         replace_token: settings.replace_token,
202         replace: settings.replace,
203         lowercase: true,
204       });
205     },
206   };
207 }(jQuery, Drupal, drupalSettings));