-
Notifications
You must be signed in to change notification settings - Fork 205
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
1a51a25
commit 5a77b44
Showing
1 changed file
with
295 additions
and
0 deletions.
There are no files selected for viewing
295 changes: 295 additions & 0 deletions
295
src/unfold/static/admin/js/admin/RelatedObjectLookups.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,295 @@ | ||
/*global SelectBox, interpolate*/ | ||
// Handles related-objects functionality: lookup link for raw_id_fields | ||
// and Add Another links. | ||
"use strict"; | ||
{ | ||
const $ = django.jQuery; | ||
let popupIndex = 0; | ||
const relatedWindows = []; | ||
|
||
function dismissChildPopups() { | ||
relatedWindows.forEach(function (win) { | ||
if (!win.closed) { | ||
win.dismissChildPopups(); | ||
win.close(); | ||
} | ||
}); | ||
} | ||
|
||
function setPopupIndex() { | ||
if (document.getElementsByName("_popup").length > 0) { | ||
const index = window.name.lastIndexOf("__") + 2; | ||
popupIndex = parseInt(window.name.substring(index)); | ||
} else { | ||
popupIndex = 0; | ||
} | ||
} | ||
|
||
function addPopupIndex(name) { | ||
return name + "__" + (popupIndex + 1); | ||
} | ||
|
||
function removePopupIndex(name) { | ||
return name.replace(new RegExp("__" + (popupIndex + 1) + "$"), ""); | ||
} | ||
|
||
function showAdminPopup(triggeringLink, name_regexp, add_popup) { | ||
const name = addPopupIndex(triggeringLink.id.replace(name_regexp, "")); | ||
const href = new URL(triggeringLink.href); | ||
if (add_popup) { | ||
href.searchParams.set("_popup", 1); | ||
} | ||
const win = window.open( | ||
href, | ||
name, | ||
"height=768,width=1024,resizable=yes,scrollbars=yes" | ||
); | ||
relatedWindows.push(win); | ||
win.focus(); | ||
return false; | ||
} | ||
|
||
function showRelatedObjectLookupPopup(triggeringLink) { | ||
return showAdminPopup(triggeringLink, /^lookup_/, true); | ||
} | ||
|
||
function dismissRelatedLookupPopup(win, chosenId) { | ||
const name = removePopupIndex(win.name); | ||
const elem = document.getElementById(name); | ||
if (elem.classList.contains("vManyToManyRawIdAdminField") && elem.value) { | ||
elem.value += "," + chosenId; | ||
} else { | ||
document.getElementById(name).value = chosenId; | ||
} | ||
const index = relatedWindows.indexOf(win); | ||
if (index > -1) { | ||
relatedWindows.splice(index, 1); | ||
} | ||
win.close(); | ||
} | ||
|
||
function showRelatedObjectPopup(triggeringLink) { | ||
return showAdminPopup(triggeringLink, /^(change|add|delete)_/, false); | ||
} | ||
|
||
function updateRelatedObjectLinks(triggeringLink) { | ||
const $this = $(triggeringLink); | ||
const siblings = $this.nextAll( | ||
".view-related, .change-related, .delete-related" | ||
); | ||
if (!siblings.length) { | ||
return; | ||
} | ||
const value = $this.val(); | ||
if (value) { | ||
siblings.each(function () { | ||
const elm = $(this); | ||
elm.attr( | ||
"href", | ||
elm.attr("data-href-template").replace("__fk__", value) | ||
); | ||
elm.removeAttr("aria-disabled"); | ||
}); | ||
} else { | ||
siblings.removeAttr("href"); | ||
siblings.attr("aria-disabled", true); | ||
} | ||
} | ||
|
||
function updateRelatedSelectsOptions( | ||
currentSelect, | ||
win, | ||
objId, | ||
newRepr, | ||
newId, | ||
skipIds = [] | ||
) { | ||
// After create/edit a model from the options next to the current | ||
// select (+ or :pencil:) update ForeignKey PK of the rest of selects | ||
// in the page. | ||
|
||
const path = win.location.pathname; | ||
// Extract the model from the popup url '.../<model>/add/' or | ||
// '.../<model>/<id>/change/' depending the action (add or change). | ||
const modelName = path.split("/")[path.split("/").length - (objId ? 4 : 3)]; | ||
// Select elements with a specific model reference and context of "available-source". | ||
const selectsRelated = document.querySelectorAll( | ||
`[data-model-ref="${modelName}"] [data-context="available-source"]` | ||
); | ||
|
||
selectsRelated.forEach(function (select) { | ||
if ( | ||
currentSelect === select || | ||
(skipIds && skipIds.includes(select.id)) | ||
) { | ||
return; | ||
} | ||
|
||
let option = select.querySelector(`option[value="${objId}"]`); | ||
|
||
if (!option) { | ||
option = new Option(newRepr, newId); | ||
select.options.add(option); | ||
// Update SelectBox cache for related fields. | ||
if ( | ||
window.SelectBox !== undefined && | ||
!SelectBox.cache[currentSelect.id] | ||
) { | ||
SelectBox.add_to_cache(select.id, option); | ||
SelectBox.redisplay(select.id); | ||
} | ||
return; | ||
} | ||
|
||
option.textContent = newRepr; | ||
option.value = newId; | ||
}); | ||
} | ||
|
||
function dismissAddRelatedObjectPopup(win, newId, newRepr) { | ||
const name = removePopupIndex(win.name); | ||
const elem = document.getElementById(name); | ||
if (elem) { | ||
const elemName = elem.nodeName.toUpperCase(); | ||
if (elemName === "SELECT") { | ||
elem.options[elem.options.length] = new Option( | ||
newRepr, | ||
newId, | ||
true, | ||
true | ||
); | ||
updateRelatedSelectsOptions(elem, win, null, newRepr, newId); | ||
} else if (elemName === "INPUT") { | ||
if ( | ||
elem.classList.contains("vManyToManyRawIdAdminField") && | ||
elem.value | ||
) { | ||
elem.value += "," + newId; | ||
} else { | ||
elem.value = newId; | ||
} | ||
} | ||
// Trigger a change event to update related links if required. | ||
$(elem).trigger("change"); | ||
} else { | ||
const toId = name + "_to"; | ||
const toElem = document.getElementById(toId); | ||
const o = new Option(newRepr, newId); | ||
SelectBox.add_to_cache(toId, o); | ||
SelectBox.redisplay(toId); | ||
if (toElem && toElem.nodeName.toUpperCase() === "SELECT") { | ||
const skipIds = [name + "_from"]; | ||
updateRelatedSelectsOptions(toElem, win, null, newRepr, newId, skipIds); | ||
} | ||
} | ||
const index = relatedWindows.indexOf(win); | ||
if (index > -1) { | ||
relatedWindows.splice(index, 1); | ||
} | ||
win.close(); | ||
} | ||
|
||
function dismissChangeRelatedObjectPopup(win, objId, newRepr, newId) { | ||
const id = removePopupIndex(win.name.replace(/^edit_/, "")); | ||
const selectsSelector = interpolate("#%s, #%s_from, #%s_to", [id, id, id]); | ||
const selects = $(selectsSelector); | ||
selects | ||
.find("option") | ||
.each(function () { | ||
if (this.value === objId) { | ||
this.textContent = newRepr; | ||
this.value = newId; | ||
} | ||
}) | ||
.trigger("change"); | ||
updateRelatedSelectsOptions(selects[0], win, objId, newRepr, newId); | ||
selects | ||
.next() | ||
.find(".select2-selection__rendered") | ||
.each(function () { | ||
// The element can have a clear button as a child. | ||
// Use the lastChild to modify only the displayed value. | ||
this.lastChild.textContent = newRepr; | ||
this.title = newRepr; | ||
}); | ||
const index = relatedWindows.indexOf(win); | ||
if (index > -1) { | ||
relatedWindows.splice(index, 1); | ||
} | ||
win.close(); | ||
} | ||
|
||
function dismissDeleteRelatedObjectPopup(win, objId) { | ||
const id = removePopupIndex(win.name.replace(/^delete_/, "")); | ||
const selectsSelector = interpolate("#%s, #%s_from, #%s_to", [id, id, id]); | ||
const selects = $(selectsSelector); | ||
selects | ||
.find("option") | ||
.each(function () { | ||
if (this.value === objId) { | ||
$(this).remove(); | ||
} | ||
}) | ||
.trigger("change"); | ||
const index = relatedWindows.indexOf(win); | ||
if (index > -1) { | ||
relatedWindows.splice(index, 1); | ||
} | ||
win.close(); | ||
} | ||
|
||
window.showRelatedObjectLookupPopup = showRelatedObjectLookupPopup; | ||
window.dismissRelatedLookupPopup = dismissRelatedLookupPopup; | ||
window.showRelatedObjectPopup = showRelatedObjectPopup; | ||
window.updateRelatedObjectLinks = updateRelatedObjectLinks; | ||
window.dismissAddRelatedObjectPopup = dismissAddRelatedObjectPopup; | ||
window.dismissChangeRelatedObjectPopup = dismissChangeRelatedObjectPopup; | ||
window.dismissDeleteRelatedObjectPopup = dismissDeleteRelatedObjectPopup; | ||
window.dismissChildPopups = dismissChildPopups; | ||
|
||
// Kept for backward compatibility | ||
window.showAddAnotherPopup = showRelatedObjectPopup; | ||
window.dismissAddAnotherPopup = dismissAddRelatedObjectPopup; | ||
|
||
window.addEventListener("unload", function (evt) { | ||
window.dismissChildPopups(); | ||
}); | ||
|
||
$(document).ready(function () { | ||
setPopupIndex(); | ||
$("a[data-popup-opener]").on("click", function (event) { | ||
event.preventDefault(); | ||
opener.dismissRelatedLookupPopup(window, $(this).data("popup-opener")); | ||
}); | ||
$("body").on( | ||
"click", | ||
'.related-widget-wrapper-link[data-popup="yes"]', | ||
function (e) { | ||
e.preventDefault(); | ||
if (this.href) { | ||
const event = $.Event("django:show-related", { href: this.href }); | ||
$(this).trigger(event); | ||
if (!event.isDefaultPrevented()) { | ||
showRelatedObjectPopup(this); | ||
} | ||
} | ||
} | ||
); | ||
$("body").on("change", ".related-widget-wrapper select", function (e) { | ||
const event = $.Event("django:update-related"); | ||
$(this).trigger(event); | ||
if (!event.isDefaultPrevented()) { | ||
updateRelatedObjectLinks(this); | ||
} | ||
}); | ||
$(".related-widget-wrapper select").trigger("change"); | ||
$("body").on("click", ".related-lookup", function (e) { | ||
e.preventDefault(); | ||
const event = $.Event("django:lookup-related"); | ||
$(this).trigger(event); | ||
if (!event.isDefaultPrevented()) { | ||
showRelatedObjectLookupPopup(this); | ||
} | ||
}); | ||
}); | ||
} |