machine-name.js 7.7 KB

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