annotate core/misc/machine-name.es6.js @ 19:fa3358dc1485 tip

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