diff --git a/client/src/components/InlinePanel/index.js b/client/src/components/InlinePanel/index.js index c450929286..e87b0cf35d 100644 --- a/client/src/components/InlinePanel/index.js +++ b/client/src/components/InlinePanel/index.js @@ -181,6 +181,23 @@ export class InlinePanel extends ExpandingFormset { return forms.length; } + getActiveFormsIndex() { + const choiceIds = []; + const forms = $('> [data-inline-panel-child]', this.formsElt).not( + '.deleted', + ); + // eslint-disable-next-line func-names + forms.each(function () { + const id = $(this).attr('id'); + const index = id.match(/\d+$/)[0]; + + if (index !== undefined && typeof parseInt(index, 10) === 'number') { + choiceIds.push(index); + } + }); + return choiceIds; + } + updateAddButtonState() { if (this.opts.maxForms) { const addButton = $('#' + this.opts.formsetPrefix + '-ADD'); diff --git a/client/src/components/MultipleChooserPanel/index.js b/client/src/components/MultipleChooserPanel/index.js index 74ca3028fe..39bb799c3c 100644 --- a/client/src/components/MultipleChooserPanel/index.js +++ b/client/src/components/MultipleChooserPanel/index.js @@ -11,6 +11,27 @@ export class MultipleChooserPanel extends InlinePanel { ), ); + const getChoiceSelectIds = () => { + const chooserIds = []; + const formsIndexes = this.getActiveFormsIndex(); + + for (const formIndex of formsIndexes) { + const formPrefix = `${opts.formsetPrefix}-${formIndex}`; + const chooserFieldId = `${formPrefix}-${opts.chooserFieldName}`; + const chooserWidget = this.chooserWidgetFactory.getById(chooserFieldId); + + if ( + chooserWidget !== null && + chooserWidget.state !== null && + chooserWidget.state.id !== null && + typeof parseInt(chooserWidget.state.id, 10) === 'number' + ) { + chooserIds.push(chooserWidget.state.id); + } + } + return chooserIds; + }; + const openModalButton = document.getElementById( `${opts.formsetPrefix}-OPEN_MODAL`, ); @@ -30,6 +51,12 @@ export class MultipleChooserPanel extends InlinePanel { }, { multiple: true }, ); + if (opts.allowDuplicates === 'True') { + openModalButton.setAttribute( + 'chooserids', + getChoiceSelectIds().join(','), + ); + } }); } diff --git a/client/src/includes/chooserModal.js b/client/src/includes/chooserModal.js index e05a339d0d..9a7a3fae2f 100644 --- a/client/src/includes/chooserModal.js +++ b/client/src/includes/chooserModal.js @@ -234,6 +234,8 @@ class ChooserModalOnloadHandlerFactory { $('[data-multiple-choice-select]', containerElement).on('change', () => { this.updateMultipleChoiceSubmitEnabledState(modal); }); + + this.disabledDuplicateCheckboxes(modal); } updateMultipleChoiceSubmitEnabledState(modal) { @@ -246,6 +248,28 @@ class ChooserModalOnloadHandlerFactory { } } + disabledDuplicateCheckboxes(modal) { + const openModalButton = modal.triggerElement; + + if (openModalButton.hasAttribute('chooserids')) { + const selectedObjectIds = openModalButton + .getAttribute('chooserids') + .split(',') + .map(Number); + + // eslint-disable-next-line func-names + $('[data-multiple-choice-select]', modal.body).each(function () { + const value = $(this).val(); + + if (selectedObjectIds.includes(parseInt(value, 10))) { + $(this).prop('disabled', true); + } else { + $(this).prop('disabled', false); + } + }); + } + } + modalHasTabs(modal) { return $('[data-tabs]', modal.body).length; } diff --git a/wagtail/admin/panels/multiple_chooser_panel.py b/wagtail/admin/panels/multiple_chooser_panel.py index 90b30aafa8..b9408ba9e4 100644 --- a/wagtail/admin/panels/multiple_chooser_panel.py +++ b/wagtail/admin/panels/multiple_chooser_panel.py @@ -6,18 +6,22 @@ from .inline_panel import InlinePanel class MultipleChooserPanel(InlinePanel): - def __init__(self, relation_name, chooser_field_name=None, **kwargs): + def __init__( + self, relation_name, chooser_field_name=None, allow_duplicates=True, **kwargs + ): if chooser_field_name is None: raise ImproperlyConfigured( "MultipleChooserPanel must specify a chooser_field_name argument" ) self.chooser_field_name = chooser_field_name + self.allow_duplicates = allow_duplicates super().__init__(relation_name, **kwargs) def clone_kwargs(self): kwargs = super().clone_kwargs() kwargs["chooser_field_name"] = self.chooser_field_name + kwargs["allow_duplicates"] = self.allow_duplicates return kwargs class BoundPanel(InlinePanel.BoundPanel): @@ -38,6 +42,7 @@ class MultipleChooserPanel(InlinePanel): def get_context_data(self, parent_context=None): context = super().get_context_data(parent_context) context["chooser_field_name"] = self.panel.chooser_field_name + context["allow_duplicates"] = self.panel.allow_duplicates context[ "chooser_widget_definition" ] = self.chooser_widget_telepath_definition diff --git a/wagtail/admin/templates/wagtailadmin/panels/multiple_chooser_panel.html b/wagtail/admin/templates/wagtailadmin/panels/multiple_chooser_panel.html index 3cc6e945b8..b8126e11f2 100644 --- a/wagtail/admin/templates/wagtailadmin/panels/multiple_chooser_panel.html +++ b/wagtail/admin/templates/wagtailadmin/panels/multiple_chooser_panel.html @@ -20,6 +20,7 @@ canOrder: {% if can_order %}true{% else %}false{% endif %}, maxForms: {{ self.formset.max_num|unlocalize }}, chooserFieldName: "{{ chooser_field_name }}", + allowDuplicates: "{{ allow_duplicates }}" , }); })();