Updated to Drupal 8.5. Core Media not yet in use.
[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((sourceId) => {
90         let machine = '';
91         const options = settings.machineName[sourceId];
92
93         const $source = $context.find(sourceId).addClass('machine-name-source').once('machine-name');
94         const $target = $context.find(options.target).addClass('machine-name-target');
95         const $suffix = $context.find(options.suffix);
96         const $wrapper = $target.closest('.js-form-item');
97         // All elements have to exist.
98         if (!$source.length || !$target.length || !$suffix.length || !$wrapper.length) {
99           return;
100         }
101         // Skip processing upon a form validation error on the machine name.
102         if ($target.hasClass('error')) {
103           return;
104         }
105         // Figure out the maximum length for the machine name.
106         options.maxlength = $target.attr('maxlength');
107         // Hide the form item container of the machine name form element.
108         $wrapper.addClass('visually-hidden');
109         // Determine the initial machine name value. Unless the machine name
110         // form element is disabled or not empty, the initial default value is
111         // based on the human-readable form element value.
112         if ($target.is(':disabled') || $target.val() !== '') {
113           machine = $target.val();
114         }
115         else if ($source.val() !== '') {
116           machine = self.transliterate($source.val(), options);
117         }
118         // Append the machine name preview to the source field.
119         const $preview = $(`<span class="machine-name-value">${options.field_prefix}${Drupal.checkPlain(machine)}${options.field_suffix}</span>`);
120         $suffix.empty();
121         if (options.label) {
122           $suffix.append(`<span class="machine-name-label">${options.label}: </span>`);
123         }
124         $suffix.append($preview);
125
126         // If the machine name cannot be edited, stop further processing.
127         if ($target.is(':disabled')) {
128           return;
129         }
130
131         const eventData = {
132           $source,
133           $target,
134           $suffix,
135           $wrapper,
136           $preview,
137           options,
138         };
139         // If it is editable, append an edit link.
140         const $link = $(`<span class="admin-link"><button type="button" class="link">${Drupal.t('Edit')}</button></span>`).on('click', eventData, clickEditHandler);
141         $suffix.append($link);
142
143         // Preview the machine name in realtime when the human-readable name
144         // changes, but only if there is no machine name yet; i.e., only upon
145         // initial creation, not when editing.
146         if ($target.val() === '') {
147           $source.on('formUpdated.machineName', eventData, machineNameHandler)
148             // Initialize machine name preview.
149             .trigger('formUpdated.machineName');
150         }
151
152         // Add a listener for an invalid event on the machine name input
153         // to show its container and focus it.
154         $target.on('invalid', eventData, clickEditHandler);
155       });
156     },
157
158     showMachineName(machine, data) {
159       const settings = data.options;
160       // Set the machine name to the transliterated value.
161       if (machine !== '') {
162         if (machine !== settings.replace) {
163           data.$target.val(machine);
164           data.$preview.html(settings.field_prefix + Drupal.checkPlain(machine) + settings.field_suffix);
165         }
166         data.$suffix.show();
167       }
168       else {
169         data.$suffix.hide();
170         data.$target.val(machine);
171         data.$preview.empty();
172       }
173     },
174
175     /**
176      * Transliterate a human-readable name to a machine name.
177      *
178      * @param {string} source
179      *   A string to transliterate.
180      * @param {object} settings
181      *   The machine name settings for the corresponding field.
182      * @param {string} settings.replace_pattern
183      *   A regular expression (without modifiers) matching disallowed characters
184      *   in the machine name; e.g., '[^a-z0-9]+'.
185      * @param {string} settings.replace_token
186      *   A token to validate the regular expression.
187      * @param {string} settings.replace
188      *   A character to replace disallowed characters with; e.g., '_' or '-'.
189      * @param {number} settings.maxlength
190      *   The maximum length of the machine name.
191      *
192      * @return {jQuery}
193      *   The transliterated source string.
194      */
195     transliterate(source, settings) {
196       return $.get(Drupal.url('machine_name/transliterate'), {
197         text: source,
198         langcode: drupalSettings.langcode,
199         replace_pattern: settings.replace_pattern,
200         replace_token: settings.replace_token,
201         replace: settings.replace,
202         lowercase: true,
203       });
204     },
205   };
206 }(jQuery, Drupal, drupalSettings));