RelatedObjectLookups.js 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. /*global SelectBox, interpolate*/
  2. // Handles related-objects functionality: lookup link for raw_id_fields
  3. // and Add Another links.
  4. 'use strict';
  5. {
  6. const $ = django.jQuery;
  7. let popupIndex = 0;
  8. const relatedWindows = [];
  9. function dismissChildPopups() {
  10. relatedWindows.forEach(function(win) {
  11. if(!win.closed) {
  12. win.dismissChildPopups();
  13. win.close();
  14. }
  15. });
  16. }
  17. function setPopupIndex() {
  18. if(document.getElementsByName("_popup").length > 0) {
  19. const index = window.name.lastIndexOf("__") + 2;
  20. popupIndex = parseInt(window.name.substring(index));
  21. } else {
  22. popupIndex = 0;
  23. }
  24. }
  25. function addPopupIndex(name) {
  26. return name + "__" + (popupIndex + 1);
  27. }
  28. function removePopupIndex(name) {
  29. return name.replace(new RegExp("__" + (popupIndex + 1) + "$"), '');
  30. }
  31. function showAdminPopup(triggeringLink, name_regexp, add_popup) {
  32. const name = addPopupIndex(triggeringLink.id.replace(name_regexp, ''));
  33. const href = new URL(triggeringLink.href);
  34. if (add_popup) {
  35. href.searchParams.set('_popup', 1);
  36. }
  37. const win = window.open(href, name, 'height=500,width=800,resizable=yes,scrollbars=yes');
  38. relatedWindows.push(win);
  39. win.focus();
  40. return false;
  41. }
  42. function showRelatedObjectLookupPopup(triggeringLink) {
  43. return showAdminPopup(triggeringLink, /^lookup_/, true);
  44. }
  45. function dismissRelatedLookupPopup(win, chosenId) {
  46. const name = removePopupIndex(win.name);
  47. const elem = document.getElementById(name);
  48. if (elem.classList.contains('vManyToManyRawIdAdminField') && elem.value) {
  49. elem.value += ',' + chosenId;
  50. } else {
  51. document.getElementById(name).value = chosenId;
  52. }
  53. const index = relatedWindows.indexOf(win);
  54. if (index > -1) {
  55. relatedWindows.splice(index, 1);
  56. }
  57. win.close();
  58. }
  59. function showRelatedObjectPopup(triggeringLink) {
  60. return showAdminPopup(triggeringLink, /^(change|add|delete)_/, false);
  61. }
  62. function updateRelatedObjectLinks(triggeringLink) {
  63. const $this = $(triggeringLink);
  64. const siblings = $this.nextAll('.view-related, .change-related, .delete-related');
  65. if (!siblings.length) {
  66. return;
  67. }
  68. const value = $this.val();
  69. if (value) {
  70. siblings.each(function() {
  71. const elm = $(this);
  72. elm.attr('href', elm.attr('data-href-template').replace('__fk__', value));
  73. });
  74. } else {
  75. siblings.removeAttr('href');
  76. }
  77. }
  78. function updateRelatedSelectsOptions(currentSelect, win, objId, newRepr, newId) {
  79. // After create/edit a model from the options next to the current
  80. // select (+ or :pencil:) update ForeignKey PK of the rest of selects
  81. // in the page.
  82. const path = win.location.pathname;
  83. // Extract the model from the popup url '.../<model>/add/' or
  84. // '.../<model>/<id>/change/' depending the action (add or change).
  85. const modelName = path.split('/')[path.split('/').length - (objId ? 4 : 3)];
  86. // Exclude autocomplete selects.
  87. const selectsRelated = document.querySelectorAll(`[data-model-ref="${modelName}"] select:not(.admin-autocomplete)`);
  88. selectsRelated.forEach(function(select) {
  89. if (currentSelect === select) {
  90. return;
  91. }
  92. let option = select.querySelector(`option[value="${objId}"]`);
  93. if (!option) {
  94. option = new Option(newRepr, newId);
  95. select.options.add(option);
  96. return;
  97. }
  98. option.textContent = newRepr;
  99. option.value = newId;
  100. });
  101. }
  102. function dismissAddRelatedObjectPopup(win, newId, newRepr) {
  103. const name = removePopupIndex(win.name);
  104. const elem = document.getElementById(name);
  105. if (elem) {
  106. const elemName = elem.nodeName.toUpperCase();
  107. if (elemName === 'SELECT') {
  108. elem.options[elem.options.length] = new Option(newRepr, newId, true, true);
  109. updateRelatedSelectsOptions(elem, win, null, newRepr, newId);
  110. } else if (elemName === 'INPUT') {
  111. if (elem.classList.contains('vManyToManyRawIdAdminField') && elem.value) {
  112. elem.value += ',' + newId;
  113. } else {
  114. elem.value = newId;
  115. }
  116. }
  117. // Trigger a change event to update related links if required.
  118. $(elem).trigger('change');
  119. } else {
  120. const toId = name + "_to";
  121. const o = new Option(newRepr, newId);
  122. SelectBox.add_to_cache(toId, o);
  123. SelectBox.redisplay(toId);
  124. }
  125. const index = relatedWindows.indexOf(win);
  126. if (index > -1) {
  127. relatedWindows.splice(index, 1);
  128. }
  129. win.close();
  130. }
  131. function dismissChangeRelatedObjectPopup(win, objId, newRepr, newId) {
  132. const id = removePopupIndex(win.name.replace(/^edit_/, ''));
  133. const selectsSelector = interpolate('#%s, #%s_from, #%s_to', [id, id, id]);
  134. const selects = $(selectsSelector);
  135. selects.find('option').each(function() {
  136. if (this.value === objId) {
  137. this.textContent = newRepr;
  138. this.value = newId;
  139. }
  140. }).trigger('change');
  141. updateRelatedSelectsOptions(selects[0], win, objId, newRepr, newId);
  142. selects.next().find('.select2-selection__rendered').each(function() {
  143. // The element can have a clear button as a child.
  144. // Use the lastChild to modify only the displayed value.
  145. this.lastChild.textContent = newRepr;
  146. this.title = newRepr;
  147. });
  148. const index = relatedWindows.indexOf(win);
  149. if (index > -1) {
  150. relatedWindows.splice(index, 1);
  151. }
  152. win.close();
  153. }
  154. function dismissDeleteRelatedObjectPopup(win, objId) {
  155. const id = removePopupIndex(win.name.replace(/^delete_/, ''));
  156. const selectsSelector = interpolate('#%s, #%s_from, #%s_to', [id, id, id]);
  157. const selects = $(selectsSelector);
  158. selects.find('option').each(function() {
  159. if (this.value === objId) {
  160. $(this).remove();
  161. }
  162. }).trigger('change');
  163. const index = relatedWindows.indexOf(win);
  164. if (index > -1) {
  165. relatedWindows.splice(index, 1);
  166. }
  167. win.close();
  168. }
  169. window.showRelatedObjectLookupPopup = showRelatedObjectLookupPopup;
  170. window.dismissRelatedLookupPopup = dismissRelatedLookupPopup;
  171. window.showRelatedObjectPopup = showRelatedObjectPopup;
  172. window.updateRelatedObjectLinks = updateRelatedObjectLinks;
  173. window.dismissAddRelatedObjectPopup = dismissAddRelatedObjectPopup;
  174. window.dismissChangeRelatedObjectPopup = dismissChangeRelatedObjectPopup;
  175. window.dismissDeleteRelatedObjectPopup = dismissDeleteRelatedObjectPopup;
  176. window.dismissChildPopups = dismissChildPopups;
  177. // Kept for backward compatibility
  178. window.showAddAnotherPopup = showRelatedObjectPopup;
  179. window.dismissAddAnotherPopup = dismissAddRelatedObjectPopup;
  180. window.addEventListener('unload', function(evt) {
  181. window.dismissChildPopups();
  182. });
  183. $(document).ready(function() {
  184. setPopupIndex();
  185. $("a[data-popup-opener]").on('click', function(event) {
  186. event.preventDefault();
  187. opener.dismissRelatedLookupPopup(window, $(this).data("popup-opener"));
  188. });
  189. $('body').on('click', '.related-widget-wrapper-link[data-popup="yes"]', function(e) {
  190. e.preventDefault();
  191. if (this.href) {
  192. const event = $.Event('django:show-related', {href: this.href});
  193. $(this).trigger(event);
  194. if (!event.isDefaultPrevented()) {
  195. showRelatedObjectPopup(this);
  196. }
  197. }
  198. });
  199. $('body').on('change', '.related-widget-wrapper select', function(e) {
  200. const event = $.Event('django:update-related');
  201. $(this).trigger(event);
  202. if (!event.isDefaultPrevented()) {
  203. updateRelatedObjectLinks(this);
  204. }
  205. });
  206. $('.related-widget-wrapper select').trigger('change');
  207. $('body').on('click', '.related-lookup', function(e) {
  208. e.preventDefault();
  209. const event = $.Event('django:lookup-related');
  210. $(this).trigger(event);
  211. if (!event.isDefaultPrevented()) {
  212. showRelatedObjectLookupPopup(this);
  213. }
  214. });
  215. });
  216. }