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