Add a higher-level API for chooser modals

Previously, anything invoking the chooser modal needed to make its own call to ModalWorkflow, which meant it needed to import the corresponding 'onloadHandlers' dict, know the appropriate chosen response identifier to listen to, and know how to modify the chooser URL to pass parameters (if applicable). This would mean a lot of duplicated logic if there were multiple places where the modal is invoked.

Here we introduce a ChooserModal base class which encapsulates those details - a caller just needs to instantiate it with the base URL, and call `open` on it to open the modal (passing an options dict and a response callback).
pull/9445/head
Matt Westcott 2022-12-16 13:28:23 +00:00
rodzic e3d42c546b
commit 947a7883f9
10 zmienionych plików z 125 dodań i 55 usunięć

Wyświetl plik

@ -1,11 +1,9 @@
import { chooserModalOnloadHandlers } from '../../includes/chooserModal';
import { ChooserModal } from '../../includes/chooserModal';
export class Chooser {
modalOnloadHandlers = chooserModalOnloadHandlers;
chooserModalClass = ChooserModal;
titleStateKey = 'title'; // key used in the 'state' dictionary to hold the human-readable title
editUrlStateKey = 'edit_url'; // key used in the 'state' dictionary to hold the URL of the edit page
chosenResponseName = 'chosen'; // identifier for the ModalWorkflow response that indicates an item was chosen
constructor(id) {
this.initHTMLElements(id);
@ -30,7 +28,6 @@ export class Chooser {
);
this.input = document.getElementById(id);
this.editLink = this.chooserElement.querySelector('.edit-link');
this.chooserBaseUrl = this.chooserElement.dataset.chooserUrl;
}
getStateFromHTML() {
@ -117,25 +114,19 @@ export class Chooser {
}
}
getModalUrl() {
return this.chooserBaseUrl;
}
getModalUrlParams() {
getModalOptions() {
return null;
}
openChooserModal() {
// eslint-disable-next-line no-undef
ModalWorkflow({
url: this.getModalUrl(),
urlParams: this.getModalUrlParams(),
onload: this.modalOnloadHandlers,
responses: {
[this.chosenResponseName]: (result) => {
this.setState(result);
},
},
if (!this.modal) {
// eslint-disable-next-line new-cap
this.modal = new this.chooserModalClass(
this.chooserElement.dataset.chooserUrl,
);
}
this.modal.open(this.getModalOptions(), (result) => {
this.setState(result);
});
}
}

Wyświetl plik

@ -1,4 +1,5 @@
import $ from 'jquery';
import { ChooserModal } from '../../includes/chooserModal';
import { initTooltips } from '../../includes/initTooltips';
/* global wagtail */
@ -208,3 +209,35 @@ const PAGE_CHOOSER_MODAL_ONLOAD_HANDLERS = {
},
};
window.PAGE_CHOOSER_MODAL_ONLOAD_HANDLERS = PAGE_CHOOSER_MODAL_ONLOAD_HANDLERS;
class PageChooserModal extends ChooserModal {
onloadHandlers = PAGE_CHOOSER_MODAL_ONLOAD_HANDLERS;
chosenResponseName = 'pageChosen';
getURL(opts) {
let url = super.getURL();
if (opts.parentId) {
url += opts.parentId + '/';
}
return url;
}
getURLParams(opts) {
const urlParams = super.getURLParams(opts);
urlParams.page_type = opts.model_names.join(',');
if (opts.target_pages) {
urlParams.target_pages = opts.target_pages;
}
if (opts.match_subclass) {
urlParams.match_subclass = opts.match_subclass;
}
if (opts.can_choose_root) {
urlParams.can_choose_root = 'true';
}
if (opts.user_perms) {
urlParams.user_perms = opts.user_perms;
}
return urlParams;
}
}
window.PageChooserModal = PageChooserModal;

Wyświetl plik

@ -2,12 +2,12 @@ import { Chooser } from '../../components/ChooserWidget';
class PageChooser extends Chooser {
// eslint-disable-next-line no-undef
modalOnloadHandlers = PAGE_CHOOSER_MODAL_ONLOAD_HANDLERS;
chooserModalClass = PageChooserModal;
titleStateKey = 'adminTitle';
editUrlStateKey = 'editUrl';
chosenResponseName = 'pageChosen';
constructor(id, parentId, options) {
constructor(id, parentId, options = {}) {
super(id);
this.initialParentId = parentId;
this.options = options;
@ -21,29 +21,18 @@ class PageChooser extends Chooser {
return state;
}
getModalUrl() {
let url = super.getModalUrl();
getModalOptions() {
const opts = {
model_names: this.options.model_names,
target_pages: this.options.target_pages,
match_subclass: this.options.match_subclass,
can_choose_root: this.options.can_choose_root,
user_perms: this.options.user_perms,
};
if (this.state && this.state.parentId) {
url += this.state.parentId + '/';
opts.parentId = this.state.parentId;
}
return url;
}
getModalUrlParams() {
const urlParams = { page_type: this.options.model_names.join(',') };
if (this.options.target_pages) {
urlParams.target_pages = this.options.target_pages;
}
if (this.options.match_subclass) {
urlParams.match_subclass = this.options.match_subclass;
}
if (this.options.can_choose_root) {
urlParams.can_choose_root = 'true';
}
if (this.options.user_perms) {
urlParams.user_perms = this.options.user_perms;
}
return urlParams;
return opts;
}
}
window.PageChooser = PageChooser;

Wyświetl plik

@ -1,5 +1,8 @@
import $ from 'jquery';
import { ChooserModalOnloadHandlerFactory } from '../../includes/chooserModal';
import {
ChooserModalOnloadHandlerFactory,
ChooserModal,
} from '../../includes/chooserModal';
class DocumentChooserModalOnloadHandlerFactory extends ChooserModalOnloadHandlerFactory {
ajaxifyLinks(modal, context) {
@ -25,3 +28,8 @@ window.DOCUMENT_CHOOSER_MODAL_ONLOAD_HANDLERS =
creationFormTabSelector: '#tab-upload',
creationFormEventName: 'wagtail:documents-upload',
}).getOnLoadHandlers();
class DocumentChooserModal extends ChooserModal {
onloadHandlers = window.DOCUMENT_CHOOSER_MODAL_ONLOAD_HANDLERS;
}
window.DocumentChooserModal = DocumentChooserModal;

Wyświetl plik

@ -2,7 +2,7 @@ import { Chooser } from '../../components/ChooserWidget';
class DocumentChooser extends Chooser {
// eslint-disable-next-line no-undef
modalOnloadHandlers = DOCUMENT_CHOOSER_MODAL_ONLOAD_HANDLERS;
chooserModalClass = DocumentChooserModal;
}
window.DocumentChooser = DocumentChooser;

Wyświetl plik

@ -1,5 +1,8 @@
import $ from 'jquery';
import { ChooserModalOnloadHandlerFactory } from '../../includes/chooserModal';
import {
ChooserModalOnloadHandlerFactory,
ChooserModal,
} from '../../includes/chooserModal';
class ImageChooserModalOnloadHandlerFactory extends ChooserModalOnloadHandlerFactory {
ajaxifyLinks(modal, context) {
@ -111,3 +114,8 @@ window.IMAGE_CHOOSER_MODAL_ONLOAD_HANDLERS =
creationFormEventName: 'wagtail:images-upload',
creationFormTabSelector: '#tab-upload',
}).getOnLoadHandlers();
class ImageChooserModal extends ChooserModal {
onloadHandlers = window.IMAGE_CHOOSER_MODAL_ONLOAD_HANDLERS;
}
window.ImageChooserModal = ImageChooserModal;

Wyświetl plik

@ -2,7 +2,7 @@ import { Chooser } from '../../components/ChooserWidget';
class ImageChooser extends Chooser {
// eslint-disable-next-line no-undef
modalOnloadHandlers = IMAGE_CHOOSER_MODAL_ONLOAD_HANDLERS;
chooserModalClass = ImageChooserModal;
initHTMLElements(id) {
super.initHTMLElements(id);

Wyświetl plik

@ -1,22 +1,26 @@
import { ChooserModal } from '../../includes/chooserModal';
import { Chooser } from '../../components/ChooserWidget';
/* global wagtailConfig */
class SnippetChooser extends Chooser {
titleStateKey = 'string';
getModalUrl() {
let urlQuery = '';
class SnippetChooserModal extends ChooserModal {
getURLParams(opts) {
const params = super.getURLParams(opts);
if (wagtailConfig.ACTIVE_CONTENT_LOCALE) {
// The user is editing a piece of translated content.
// Pass the locale along as a request parameter. If this
// snippet is also translatable, the results will be
// pre-filtered by this locale.
urlQuery = '?locale=' + wagtailConfig.ACTIVE_CONTENT_LOCALE;
params.locale = wagtailConfig.ACTIVE_CONTENT_LOCALE;
}
return this.chooserBaseUrl + urlQuery;
return params;
}
}
class SnippetChooser extends Chooser {
titleStateKey = 'string';
chooserModalClass = SnippetChooserModal;
}
window.SnippetChooser = SnippetChooser;
function createSnippetChooser(id) {

Wyświetl plik

@ -313,6 +313,39 @@ class ChooserModalOnloadHandlerFactory {
const chooserModalOnloadHandlers =
new ChooserModalOnloadHandlerFactory().getOnLoadHandlers();
class ChooserModal {
onloadHandlers = chooserModalOnloadHandlers;
chosenResponseName = 'chosen'; // identifier for the ModalWorkflow response that indicates an item was chosen
constructor(baseUrl) {
this.baseUrl = baseUrl;
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
getURL(opts) {
return this.baseUrl;
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
getURLParams(opts) {
return {};
}
open(opts, callback) {
// eslint-disable-next-line no-undef
ModalWorkflow({
url: this.getURL(opts || {}),
urlParams: this.getURLParams(opts || {}),
onload: this.onloadHandlers,
responses: {
[this.chosenResponseName]: (result) => {
callback(result);
},
},
});
}
}
export {
validateCreationForm,
submitCreationForm,
@ -320,4 +353,5 @@ export {
SearchController,
ChooserModalOnloadHandlerFactory,
chooserModalOnloadHandlers,
ChooserModal,
};

Wyświetl plik

@ -55,3 +55,6 @@ global.IMAGE_CHOOSER_MODAL_ONLOAD_HANDLERS = { type: 'image' };
global.PAGE_CHOOSER_MODAL_ONLOAD_HANDLERS = { type: 'page' };
global.EMBED_CHOOSER_MODAL_ONLOAD_HANDLERS = { type: 'embed' };
global.DOCUMENT_CHOOSER_MODAL_ONLOAD_HANDLERS = { type: 'document' };
class PageChooserModal {}
global.PageChooserModal = PageChooserModal;