]> BookStack Code Mirror - bookstack/blobdiff - resources/js/services/components.js
Guest create page: name field autofocus
[bookstack] / resources / js / services / components.js
index 04fd0dcf4bb7f5c459027ebda3883214e0c5415e..d1503db4d4a9f20dc60e6f386e5f53a01c7b9708 100644 (file)
@@ -1,5 +1,23 @@
+import {kebabToCamel, camelToKebab} from "./text";
+
+/**
+ * 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 +26,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 +51,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);
 }
 
 /**
@@ -92,17 +109,6 @@ function parseOpts(name, element) {
     return opts;
 }
 
-/**
- * Convert a kebab-case string to camelCase
- * @param {String} kebab
- * @returns {string}
- */
-function kebabToCamel(kebab) {
-    const ucFirst = (word) => word.slice(0,1).toUpperCase() + word.slice(1);
-    const words = kebab.split('-');
-    return words[0] + words.slice(1).map(ucFirst).join('');
-}
-
 /**
  * Initialize all components found within the given element.
  * @param {Element|Document} parentElement
@@ -125,7 +131,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];
     }
 }
 
@@ -143,10 +149,17 @@ export function first(name) {
  * @param {String} name
  * @returns {Component[]}
  */
-export function get(name = '') {
+export function get(name) {
     return components[name] || [];
 }
 
-function camelToKebab(camelStr) {
-    return camelStr.replace(/[A-Z]/g, (str, offset) =>  (offset > 0 ? '-' : '') + str.toLowerCase());
+/**
+ * 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;
 }
\ No newline at end of file