浏览代码

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).
Matt Westcott 2 年之前
父节点
当前提交
947a7883f9

+ 11 - 20
client/src/components/ChooserWidget/index.js

@@ -1,11 +1,9 @@
-import { chooserModalOnloadHandlers } from '../../includes/chooserModal';
+import { ChooserModal } from '../../includes/chooserModal';
 
 
 export class Chooser {
 export class Chooser {
-  modalOnloadHandlers = chooserModalOnloadHandlers;
-
+  chooserModalClass = ChooserModal;
   titleStateKey = 'title'; // key used in the 'state' dictionary to hold the human-readable title
   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
   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) {
   constructor(id) {
     this.initHTMLElements(id);
     this.initHTMLElements(id);
@@ -30,7 +28,6 @@ export class Chooser {
     );
     );
     this.input = document.getElementById(id);
     this.input = document.getElementById(id);
     this.editLink = this.chooserElement.querySelector('.edit-link');
     this.editLink = this.chooserElement.querySelector('.edit-link');
-    this.chooserBaseUrl = this.chooserElement.dataset.chooserUrl;
   }
   }
 
 
   getStateFromHTML() {
   getStateFromHTML() {
@@ -117,25 +114,19 @@ export class Chooser {
     }
     }
   }
   }
 
 
-  getModalUrl() {
-    return this.chooserBaseUrl;
-  }
-
-  getModalUrlParams() {
+  getModalOptions() {
     return null;
     return null;
   }
   }
 
 
   openChooserModal() {
   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);
     });
     });
   }
   }
 }
 }

+ 33 - 0
client/src/entrypoints/admin/page-chooser-modal.js

@@ -1,4 +1,5 @@
 import $ from 'jquery';
 import $ from 'jquery';
+import { ChooserModal } from '../../includes/chooserModal';
 import { initTooltips } from '../../includes/initTooltips';
 import { initTooltips } from '../../includes/initTooltips';
 
 
 /* global wagtail */
 /* global wagtail */
@@ -208,3 +209,35 @@ const PAGE_CHOOSER_MODAL_ONLOAD_HANDLERS = {
   },
   },
 };
 };
 window.PAGE_CHOOSER_MODAL_ONLOAD_HANDLERS = 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;

+ 13 - 24
client/src/entrypoints/admin/page-chooser.js

@@ -2,12 +2,12 @@ import { Chooser } from '../../components/ChooserWidget';
 
 
 class PageChooser extends Chooser {
 class PageChooser extends Chooser {
   // eslint-disable-next-line no-undef
   // eslint-disable-next-line no-undef
-  modalOnloadHandlers = PAGE_CHOOSER_MODAL_ONLOAD_HANDLERS;
+  chooserModalClass = PageChooserModal;
+
   titleStateKey = 'adminTitle';
   titleStateKey = 'adminTitle';
   editUrlStateKey = 'editUrl';
   editUrlStateKey = 'editUrl';
-  chosenResponseName = 'pageChosen';
 
 
-  constructor(id, parentId, options) {
+  constructor(id, parentId, options = {}) {
     super(id);
     super(id);
     this.initialParentId = parentId;
     this.initialParentId = parentId;
     this.options = options;
     this.options = options;
@@ -21,29 +21,18 @@ class PageChooser extends Chooser {
     return state;
     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) {
     if (this.state && this.state.parentId) {
-      url += 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;
+      opts.parentId = this.state.parentId;
     }
     }
-    return urlParams;
+    return opts;
   }
   }
 }
 }
 window.PageChooser = PageChooser;
 window.PageChooser = PageChooser;

+ 9 - 1
client/src/entrypoints/documents/document-chooser-modal.js

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

+ 1 - 1
client/src/entrypoints/documents/document-chooser.js

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

+ 9 - 1
client/src/entrypoints/images/image-chooser-modal.js

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

+ 1 - 1
client/src/entrypoints/images/image-chooser.js

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

+ 11 - 7
client/src/entrypoints/snippets/snippet-chooser.js

@@ -1,22 +1,26 @@
+import { ChooserModal } from '../../includes/chooserModal';
 import { Chooser } from '../../components/ChooserWidget';
 import { Chooser } from '../../components/ChooserWidget';
 
 
 /* global wagtailConfig */
 /* 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) {
     if (wagtailConfig.ACTIVE_CONTENT_LOCALE) {
       // The user is editing a piece of translated content.
       // The user is editing a piece of translated content.
       // Pass the locale along as a request parameter. If this
       // Pass the locale along as a request parameter. If this
       // snippet is also translatable, the results will be
       // snippet is also translatable, the results will be
       // pre-filtered by this locale.
       // 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;
 window.SnippetChooser = SnippetChooser;
 
 
 function createSnippetChooser(id) {
 function createSnippetChooser(id) {

+ 34 - 0
client/src/includes/chooserModal.js

@@ -313,6 +313,39 @@ class ChooserModalOnloadHandlerFactory {
 const chooserModalOnloadHandlers =
 const chooserModalOnloadHandlers =
   new ChooserModalOnloadHandlerFactory().getOnLoadHandlers();
   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 {
 export {
   validateCreationForm,
   validateCreationForm,
   submitCreationForm,
   submitCreationForm,
@@ -320,4 +353,5 @@ export {
   SearchController,
   SearchController,
   ChooserModalOnloadHandlerFactory,
   ChooserModalOnloadHandlerFactory,
   chooserModalOnloadHandlers,
   chooserModalOnloadHandlers,
+  ChooserModal,
 };
 };

+ 3 - 0
client/tests/stubs.js

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