]> BookStack Code Mirror - bookstack/commitdiff
Replaced el.components mapping with component service weakmap
authorDan Brown <redacted>
Wed, 16 Nov 2022 15:46:41 +0000 (15:46 +0000)
committerDan Brown <redacted>
Wed, 16 Nov 2022 15:46:41 +0000 (15:46 +0000)
Old system was hard to track in terms of usage and it's application of
'components' properties directly to elements was shoddy.
This routes usage via the components service, with element-specific
component usage tracked via a local weakmap.
Updated existing found usages to use the new system.

resources/js/components/attachments.js
resources/js/components/code-editor.js
resources/js/components/confirm-dialog.js
resources/js/components/entity-selector-popup.js
resources/js/components/image-manager.js
resources/js/components/page-editor.js
resources/js/components/tag-manager.js
resources/js/components/user-select.js
resources/js/services/components.js

index b373e1d47b59110300072b76745730202c90123d..b4e400aeb716b53047b0d7c80492e2a9dbdba7b0 100644 (file)
@@ -43,7 +43,9 @@ export class Attachments extends Component {
 
     reloadList() {
         this.stopEdit();
-        this.mainTabs.components.tabs.show('items');
+        /** @var {Tabs} */
+        const tabs = window.$components.firstOnElement(this.mainTabs, 'tabs');
+        tabs.show('items');
         window.$http.get(`/attachments/get/page/${this.pageId}`).then(resp => {
             this.list.innerHTML = resp.data;
             window.$components.init(this.list);
index 241cdece9a9f732cc309839698dc96cab6e14dde..205cbd8fdbc21efec49301430a7eee97fa67a991 100644 (file)
@@ -126,7 +126,7 @@ export class CodeEditor extends Component {
         }
 
         this.loadHistory();
-        this.popup.components.popup.show(() => {
+        this.getPopup().show(() => {
             Code.updateLayout(this.editor);
             this.editor.focus();
         }, () => {
@@ -135,10 +135,17 @@ export class CodeEditor extends Component {
     }
 
     hide() {
-        this.popup.components.popup.hide();
+        this.getPopup().hide();
         this.addHistory();
     }
 
+    /**
+     * @returns {Popup}
+     */
+    getPopup() {
+        return window.$components.firstOnElement(this.popup, 'popup');
+    }
+
     async updateEditorMode(language) {
         const Code = await window.importVersioned('code');
         Code.setMode(this.editor, language, this.editor.getValue());
index 215c0b94e29fd7fe5b3da1f85cd1cc347a8bfe3c..572945d5aba4ae7099a30dc29a28fe58f62d4542 100644 (file)
@@ -34,7 +34,7 @@ export class ConfirmDialog extends Component {
      * @returns {Popup}
      */
     getPopup() {
-        return this.container.components.popup;
+        return window.$components.firstOnElement(this.container, 'popup');
     }
 
     /**
index 69534dea5ec814c820a6c1853a287e6154c9a795..d455f7ee7d5286f3bbfb9979ec0651ea6dfac98c 100644 (file)
@@ -17,16 +17,26 @@ export class EntitySelectorPopup extends Component {
 
     show(callback) {
         this.callback = callback;
-        this.container.components.popup.show();
+        this.getPopup().show();
         this.getSelector().focusSearch();
     }
 
     hide() {
-        this.container.components.popup.hide();
+        this.getPopup().hide();
     }
 
+    /**
+     * @returns {Popup}
+     */
+    getPopup() {
+        return window.$components.firstOnElement(this.container, 'popup');
+    }
+
+    /**
+     * @returns {EntitySelector}
+     */
     getSelector() {
-        return this.selectorEl.components['entity-selector'];
+        return window.$components.firstOnElement(this.selectorEl, 'entity-selector');
     }
 
     onSelectButtonClick() {
index a78aa348320c4ec7275a65d53089d37326532826..a44fffc1b437776af3d723e445eef617db7757b9 100644 (file)
@@ -94,7 +94,7 @@ export class ImageManager extends Component {
 
         this.callback = callback;
         this.type = type;
-        this.popupEl.components.popup.show();
+        this.getPopup().show();
         this.dropzoneContainer.classList.toggle('hidden', type !== 'gallery');
 
         if (!this.hasData) {
@@ -104,7 +104,14 @@ export class ImageManager extends Component {
     }
 
     hide() {
-        this.popupEl.components.popup.hide();
+        this.getPopup().hide();
+    }
+
+    /**
+     * @returns {Popup}
+     */
+    getPopup() {
+        return window.$components.firstOnElement(this.popupEl, 'popup');
     }
 
     async loadGallery() {
index 41e070b9dd18635f57c0495ce0d29087ec9e77a4..d6faabd054129d76e7d94bf6c0a69d430c6688d1 100644 (file)
@@ -196,7 +196,8 @@ export class PageEditor extends Component {
         event.preventDefault();
 
         const link = event.target.closest('a').href;
-        const dialog = this.switchDialogContainer.components['confirm-dialog'];
+        /** @var {ConfirmDialog} **/
+        const dialog = window.$components.firstOnElement(this.switchDialogContainer, 'confirm-dialog');
         const [saved, confirmed] = await Promise.all([this.saveDraft(), dialog.show()]);
 
         if (saved && confirmed) {
index b51cfe9b2c2609a9dbe15e2c45e126dbd641f401..cfbc514a07250070563798cb04c1b1f330e92c1f 100644 (file)
@@ -11,7 +11,8 @@ export class TagManager extends Component {
 
     setupListeners() {
         this.container.addEventListener('change', event => {
-            const addRemoveComponent = this.addRemoveComponentEl.components['add-remove-rows'];
+            /** @var {AddRemoveRows} **/
+            const addRemoveComponent = window.$components.firstOnElement(this.addRemoveComponentEl, 'add-remove-rows');
             if (!this.hasEmptyRows()) {
                 addRemoveComponent.add();
             }
index 549963eed4193e6e5fc72ea46eadd73f77010e7a..d4d88a633c115ab06a9bd718e32ca41090efc109 100644 (file)
@@ -4,12 +4,11 @@ import {Component} from "./component";
 export class UserSelect extends Component {
 
     setup() {
+        this.container = this.$el;
         this.input = this.$refs.input;
         this.userInfoContainer = this.$refs.userInfo;
 
-        this.hide = this.$el.components.dropdown.hide;
-
-        onChildEvent(this.$el, 'a.dropdown-search-item', 'click', this.selectUser.bind(this));
+        onChildEvent(this.container, 'a.dropdown-search-item', 'click', this.selectUser.bind(this));
     }
 
     selectUser(event, userEl) {
@@ -20,4 +19,10 @@ export class UserSelect extends Component {
         this.hide();
     }
 
+    hide() {
+        /** @var {Dropdown} **/
+        const dropdown = window.$components.firstOnElement(this.container, 'dropdown');
+        dropdown.hide();
+    }
+
 }
\ No newline at end of file
index 04fd0dcf4bb7f5c459027ebda3883214e0c5415e..c0fc125242c6a7314b3244b2db6eade757611fd1 100644 (file)
@@ -1,5 +1,21 @@
+/**
+ * A mapping of active components keyed by name, with values being arrays of component
+ * instances since there can be multiple components of the same type.
+ * @type {Object<String, Component[]>}
+ */
 const components = {};
-const componentMap = {};
+
+/**
+ * A mapping of component class models, keyed by name.
+ * @type {Object<String, Constructor<Component>>}
+ */
+const componentModelMap = {};
+
+/**
+ * A mapping of active component maps, keyed by the element components are assigned to.
+ * @type {WeakMap<Element, Object<String, Component>>}
+ */
+const elementComponentMap = new WeakMap();
 
 /**
  * Initialize a component instance on the given dom element.
@@ -8,7 +24,7 @@ const componentMap = {};
  */
 function initComponent(name, element) {
     /** @type {Function<Component>|undefined} **/
-    const componentModel = componentMap[name];
+    const componentModel = componentModelMap[name];
     if (componentModel === undefined) return;
 
     // Create our component instance
@@ -33,11 +49,10 @@ function initComponent(name, element) {
     }
     components[name].push(instance);
 
-    // Add to element listing
-    if (typeof element.components === 'undefined') {
-        element.components = {};
-    }
-    element.components[name] = instance;
+    // Add to element mapping
+    const elComponents = elementComponentMap.get(element) || {};
+    elComponents[name] = instance;
+    elementComponentMap.set(element, elComponents);
 }
 
 /**
@@ -125,7 +140,7 @@ export function init(parentElement = document) {
 export function register(mapping) {
     const keys = Object.keys(mapping);
     for (const key of keys) {
-        componentMap[camelToKebab(key)] = mapping[key];
+        componentModelMap[camelToKebab(key)] = mapping[key];
     }
 }
 
@@ -147,6 +162,17 @@ export function get(name = '') {
     return components[name] || [];
 }
 
+/**
+ * Get the first component, of the given name, that's assigned to the given element.
+ * @param {Element} element
+ * @param {String} name
+ * @returns {Component|null}
+ */
+export function firstOnElement(element, name) {
+    const elComponents = elementComponentMap.get(element) || {};
+    return elComponents[name] || null;
+}
+
 function camelToKebab(camelStr) {
     return camelStr.replace(/[A-Z]/g, (str, offset) =>  (offset > 0 ? '-' : '') + str.toLowerCase());
 }
\ No newline at end of file