Skip to content

Commit a8f1160

Browse files
committed
JS: Converted come common services to typescript
1 parent feca1f0 commit a8f1160

9 files changed

Lines changed: 172 additions & 187 deletions

File tree

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414
"livereload": "livereload ./public/dist/",
1515
"permissions": "chown -R $USER:$USER bootstrap/cache storage public/uploads",
1616
"lint": "eslint \"resources/**/*.js\" \"resources/**/*.mjs\"",
17-
"fix": "eslint --fix \"resources/**/*.js\" \"resources/**/*.mjs\""
17+
"fix": "eslint --fix \"resources/**/*.js\" \"resources/**/*.mjs\"",
18+
"ts:lint": "tsc --noEmit"
1819
},
1920
"devDependencies": {
2021
"@lezer/generator": "^1.5.1",

resources/js/app.js

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import * as events from './services/events';
22
import * as httpInstance from './services/http';
33
import Translations from './services/translations';
4-
5-
import * as components from './services/components';
64
import * as componentMap from './components';
5+
import {ComponentStore} from './services/components.ts';
76

87
// Url retrieval function
98
window.baseUrl = function baseUrl(path) {
@@ -32,6 +31,6 @@ window.trans_choice = translator.getPlural.bind(translator);
3231
window.trans_plural = translator.parsePlural.bind(translator);
3332

3433
// Load & initialise components
35-
components.register(componentMap);
36-
window.$components = components;
37-
components.init();
34+
window.$components = new ComponentStore();
35+
window.$components.register(componentMap);
36+
window.$components.init();

resources/js/custom.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
declare module '*.svg' {
2+
const content: string;
3+
export default content;
4+
}

resources/js/global.d.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,7 @@
1-
declare module '*.svg' {
2-
const content: string;
3-
export default content;
4-
}
1+
import {ComponentStore} from "./services/components";
52

63
declare global {
74
interface Window {
8-
$components: {
9-
first: (string) => Object,
10-
}
5+
$components: ComponentStore,
116
}
127
}

resources/js/services/components.js

Lines changed: 0 additions & 165 deletions
This file was deleted.
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
import {kebabToCamel, camelToKebab} from './text';
2+
import {Component} from "../components/component";
3+
4+
/**
5+
* Parse out the element references within the given element
6+
* for the given component name.
7+
*/
8+
function parseRefs(name: string, element: HTMLElement):
9+
{refs: Record<string, HTMLElement>, manyRefs: Record<string, HTMLElement[]>} {
10+
const refs: Record<string, HTMLElement> = {};
11+
const manyRefs: Record<string, HTMLElement[]> = {};
12+
13+
const prefix = `${name}@`;
14+
const selector = `[refs*="${prefix}"]`;
15+
const refElems = [...element.querySelectorAll(selector)];
16+
if (element.matches(selector)) {
17+
refElems.push(element);
18+
}
19+
20+
for (const el of refElems as HTMLElement[]) {
21+
const refNames = (el.getAttribute('refs') || '')
22+
.split(' ')
23+
.filter(str => str.startsWith(prefix))
24+
.map(str => str.replace(prefix, ''))
25+
.map(kebabToCamel);
26+
for (const ref of refNames) {
27+
refs[ref] = el;
28+
if (typeof manyRefs[ref] === 'undefined') {
29+
manyRefs[ref] = [];
30+
}
31+
manyRefs[ref].push(el);
32+
}
33+
}
34+
return {refs, manyRefs};
35+
}
36+
37+
/**
38+
* Parse out the element component options.
39+
*/
40+
function parseOpts(componentName: string, element: HTMLElement): Record<string, string> {
41+
const opts: Record<string, string> = {};
42+
const prefix = `option:${componentName}:`;
43+
for (const {name, value} of element.attributes) {
44+
if (name.startsWith(prefix)) {
45+
const optName = name.replace(prefix, '');
46+
opts[kebabToCamel(optName)] = value || '';
47+
}
48+
}
49+
return opts;
50+
}
51+
52+
export class ComponentStore {
53+
/**
54+
* A mapping of active components keyed by name, with values being arrays of component
55+
* instances since there can be multiple components of the same type.
56+
*/
57+
protected components: Record<string, Component[]> = {};
58+
59+
/**
60+
* A mapping of component class models, keyed by name.
61+
*/
62+
protected componentModelMap: Record<string, typeof Component> = {};
63+
64+
/**
65+
* A mapping of active component maps, keyed by the element components are assigned to.
66+
*/
67+
protected elementComponentMap: WeakMap<HTMLElement, Record<string, Component>> = new WeakMap();
68+
69+
/**
70+
* Initialize a component instance on the given dom element.
71+
*/
72+
protected initComponent(name: string, element: HTMLElement): void {
73+
const ComponentModel = this.componentModelMap[name];
74+
if (ComponentModel === undefined) return;
75+
76+
// Create our component instance
77+
let instance: Component|null = null;
78+
try {
79+
instance = new ComponentModel();
80+
instance.$name = name;
81+
instance.$el = element;
82+
const allRefs = parseRefs(name, element);
83+
instance.$refs = allRefs.refs;
84+
instance.$manyRefs = allRefs.manyRefs;
85+
instance.$opts = parseOpts(name, element);
86+
instance.setup();
87+
} catch (e) {
88+
console.error('Failed to create component', e, name, element);
89+
}
90+
91+
if (!instance) {
92+
return;
93+
}
94+
95+
// Add to global listing
96+
if (typeof this.components[name] === 'undefined') {
97+
this.components[name] = [];
98+
}
99+
this.components[name].push(instance);
100+
101+
// Add to element mapping
102+
const elComponents = this.elementComponentMap.get(element) || {};
103+
elComponents[name] = instance;
104+
this.elementComponentMap.set(element, elComponents);
105+
}
106+
107+
/**
108+
* Initialize all components found within the given element.
109+
*/
110+
public init(parentElement: Document|HTMLElement = document) {
111+
const componentElems = parentElement.querySelectorAll('[component],[components]');
112+
113+
for (const el of componentElems) {
114+
const componentNames = `${el.getAttribute('component') || ''} ${(el.getAttribute('components'))}`.toLowerCase().split(' ').filter(Boolean);
115+
for (const name of componentNames) {
116+
this.initComponent(name, el as HTMLElement);
117+
}
118+
}
119+
}
120+
121+
/**
122+
* Register the given component mapping into the component system.
123+
* @param {Object<String, ObjectConstructor<Component>>} mapping
124+
*/
125+
public register(mapping: Record<string, typeof Component>) {
126+
const keys = Object.keys(mapping);
127+
for (const key of keys) {
128+
this.componentModelMap[camelToKebab(key)] = mapping[key];
129+
}
130+
}
131+
132+
/**
133+
* Get the first component of the given name.
134+
*/
135+
public first(name: string): Component|null {
136+
return (this.components[name] || [null])[0];
137+
}
138+
139+
/**
140+
* Get all the components of the given name.
141+
*/
142+
public get(name: string): Component[] {
143+
return this.components[name] || [];
144+
}
145+
146+
/**
147+
* Get the first component, of the given name, that's assigned to the given element.
148+
*/
149+
public firstOnElement(element: HTMLElement, name: string): Component|null {
150+
const elComponents = this.elementComponentMap.get(element) || {};
151+
return elComponents[name] || null;
152+
}
153+
}
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,15 @@
11
/**
22
* Convert a kebab-case string to camelCase
3-
* @param {String} kebab
4-
* @returns {string}
53
*/
6-
export function kebabToCamel(kebab) {
7-
const ucFirst = word => word.slice(0, 1).toUpperCase() + word.slice(1);
4+
export function kebabToCamel(kebab: string): string {
5+
const ucFirst = (word: string) => word.slice(0, 1).toUpperCase() + word.slice(1);
86
const words = kebab.split('-');
97
return words[0] + words.slice(1).map(ucFirst).join('');
108
}
119

1210
/**
1311
* Convert a camelCase string to a kebab-case string.
14-
* @param {String} camelStr
15-
* @returns {String}
1612
*/
17-
export function camelToKebab(camelStr) {
13+
export function camelToKebab(camelStr: string): string {
1814
return camelStr.replace(/[A-Z]/g, (str, offset) => (offset > 0 ? '-' : '') + str.toLowerCase());
1915
}

0 commit comments

Comments
 (0)