RelatedObjectLookups.js 8.8 KB

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