|
1 | 1 | import 'bootstrap'; |
2 | 2 | import 'bootstrap/dist/css/bootstrap.min.css'; |
3 | | -import DOMPurify from 'dompurify'; |
4 | 3 | import 'font-awesome/css/font-awesome.css'; |
5 | | -import { createDomElement, emptyElement } from 'multiple-select-vanilla'; |
6 | 4 |
|
7 | | -import { exampleRouting, navbarRouting } from './app-routing.js'; |
8 | | -import mainHtml from './main.html?raw'; |
| 5 | +import { App } from './app.js'; |
9 | 6 | import './style.scss'; |
10 | 7 |
|
11 | | -const pageLayoutGlobs = import.meta.glob('/src/./**/*.html', { query: '?raw', eager: true, import: 'default' }); |
12 | | - |
13 | | -interface ViewRouter { |
14 | | - name: string; |
15 | | - view: string; |
16 | | - viewModel: any; |
17 | | - title: string; |
18 | | -} |
19 | | - |
20 | | -interface ViewModel { |
21 | | - mount?: () => void; |
22 | | - unmount?: () => void; |
23 | | -} |
24 | | - |
25 | | -class Main { |
26 | | - loading = true; |
27 | | - currentModel?: ViewModel; |
28 | | - currentRouter?: ViewRouter; |
29 | | - defaultRouteName = 'getting-started'; |
30 | | - stateBangChar = '#/'; |
31 | | - baseUrl = window.location.origin + window.location.pathname; |
32 | | - viewModelObj: any = {}; |
33 | | - |
34 | | - async init() { |
35 | | - const location = window.location; |
36 | | - document.querySelector<HTMLDivElement>('#app')!.innerHTML = DOMPurify.sanitize(mainHtml, { |
37 | | - RETURN_TRUSTED_TYPE: true, |
38 | | - }) as unknown as string; |
39 | | - |
40 | | - let route = location.hash.replace(this.stateBangChar, ''); |
41 | | - if (!route || route === '/' || route === '#') { |
42 | | - route = this.defaultRouteName; |
43 | | - } |
44 | | - |
45 | | - // create all routes and always load first route Example01 |
46 | | - this.createRouteLinks(); |
47 | | - this.loadRoute(route); |
48 | | - |
49 | | - // on page load make route active, when changing route afterward that will be covered by each nav click events |
50 | | - Array.from(document.querySelectorAll('.panel-wm-left a.nav-link,.navbar-nav a.nav-link')).forEach(link => { |
51 | | - if (route.includes(link.id)) { |
52 | | - link.classList.add('active'); |
53 | | - } |
54 | | - }); |
55 | | - |
56 | | - // re-render on browser history navigation change |
57 | | - window.onpopstate = () => { |
58 | | - const winLoc = window.location; |
59 | | - const prevRoute = winLoc.hash.replace(this.stateBangChar, ''); |
60 | | - |
61 | | - // change active link to previous route |
62 | | - this.removeAllActiveLinks(); |
63 | | - const navItemElm = document.querySelector(`#${prevRoute}`); |
64 | | - if (navItemElm) { |
65 | | - navItemElm.scrollIntoView(); |
66 | | - navItemElm.classList.add('active'); |
67 | | - } |
68 | | - this.loadRoute(prevRoute || this.defaultRouteName, false); |
69 | | - }; |
70 | | - } |
71 | | - |
72 | | - createRouteLinks() { |
73 | | - for (const navRoute of navbarRouting) { |
74 | | - // <li class="nav-item"><a class="nav-link" id="home">Home</a></li> |
75 | | - const liElm = createDomElement('li', { className: 'nav-item' }); |
76 | | - const aElm = createDomElement('a', { id: navRoute.name, className: 'nav-link', textContent: navRoute.title }, liElm); |
77 | | - aElm.addEventListener('click', this.clickEventListener.bind(this)); |
78 | | - document.querySelector('.navbar-nav')?.appendChild(liElm); |
79 | | - } |
80 | | - |
81 | | - for (const groupRoute of exampleRouting) { |
82 | | - const groupLiElm = createDomElement('li', { className: 'm-1' }); |
83 | | - createDomElement('p', { className: 'navbar-vertical-label mb-1', textContent: groupRoute.name }, groupLiElm); |
84 | | - document.querySelector('.nav-pills')?.appendChild(groupLiElm); |
85 | | - |
86 | | - for (const singleRoute of groupRoute.routes) { |
87 | | - // <li class="nav-item"><a class="nav-link" id="example01">Single Select</a></li> |
88 | | - const liElm = createDomElement('li', { className: 'nav-item' }); |
89 | | - const aElm = createDomElement('a', { |
90 | | - id: singleRoute.name, |
91 | | - className: 'nav-link', |
92 | | - textContent: singleRoute.title, |
93 | | - }); |
94 | | - aElm.addEventListener('click', this.clickEventListener.bind(this)); |
95 | | - |
96 | | - liElm.appendChild(aElm); |
97 | | - document.querySelector('.nav-pills')?.appendChild(liElm); |
98 | | - } |
99 | | - } |
100 | | - } |
101 | | - |
102 | | - async loadRoute(routeName: string, changeBrowserState = true) { |
103 | | - const contentElm = document.querySelector('.panel-wm-content') as HTMLElement; |
104 | | - emptyElement(contentElm); |
105 | | - contentElm.classList.add('cloak'); |
106 | | - let foundRouter = navbarRouting.find(r => r.name === routeName); |
107 | | - |
108 | | - if (foundRouter?.name === 'examples') { |
109 | | - const exampleElm = document.querySelector('.nav-pills .nav-item a.nav-link'); |
110 | | - exampleElm?.classList.add('active'); |
111 | | - } else { |
112 | | - for (const groupRoute of exampleRouting) { |
113 | | - const found = (groupRoute.routes as ViewRouter[]).find(r => r.name === routeName); |
114 | | - if (found) { |
115 | | - foundRouter = found; |
116 | | - } |
117 | | - } |
118 | | - } |
119 | | - |
120 | | - if (this.currentModel) { |
121 | | - this.unmountCurrentVM(this.currentModel, this.currentRouter); |
122 | | - } |
123 | | - |
124 | | - if (foundRouter) { |
125 | | - this.currentRouter = foundRouter; |
126 | | - document.querySelector('.panel-wm-content')!.innerHTML = DOMPurify.sanitize(pageLayoutGlobs[foundRouter.view] as string, { |
127 | | - RETURN_TRUSTED_TYPE: true, |
128 | | - }) as unknown as string; |
129 | | - const vm = new foundRouter.viewModel() as ViewModel; |
130 | | - this.currentModel = vm; |
131 | | - (window as any)[foundRouter.name] = vm.mount?.(); |
132 | | - |
133 | | - // before leaving the page/SPA, we'll unmount everything |
134 | | - window.onbeforeunload = () => { |
135 | | - contentElm.classList.add('cloak'); |
136 | | - vm.unmount?.(); |
137 | | - this.removeAllActiveLinks(true); |
138 | | - this.unmountAll(); |
139 | | - if (foundRouter?.name) { |
140 | | - delete (window as any)[foundRouter.name]; |
141 | | - } |
142 | | - }; |
143 | | - } |
144 | | - |
145 | | - if (changeBrowserState) { |
146 | | - window.history.pushState({}, routeName, `${this.baseUrl}${this.stateBangChar}${routeName}`); |
147 | | - } |
148 | | - document.title = `Multiple-Select-Vanilla · ${routeName}`; |
149 | | - contentElm.classList.remove('cloak'); |
150 | | - } |
151 | | - |
152 | | - async clickEventListener(e: Event) { |
153 | | - // remove any active links |
154 | | - this.removeAllActiveLinks(); |
155 | | - |
156 | | - // change active link to new route |
157 | | - const targetElm = e.target as HTMLElement; |
158 | | - targetElm.classList.toggle('active'); |
159 | | - |
160 | | - this.loadRoute(targetElm.id); |
161 | | - } |
162 | | - |
163 | | - removeAllActiveLinks(unbindListeners = false) { |
164 | | - document.querySelectorAll('.panel-wm-left a.nav-link,.navbar-nav a.nav-link').forEach(link => { |
165 | | - link.classList.remove('active'); |
166 | | - if (unbindListeners) { |
167 | | - link.removeEventListener('click', this.clickEventListener.bind(this) as EventListener); |
168 | | - } |
169 | | - }); |
170 | | - } |
171 | | - |
172 | | - unmountCurrentVM(vm: ViewModel, vr?: ViewRouter) { |
173 | | - vm.unmount?.(); |
174 | | - if (vr) { |
175 | | - delete (window as any)[vr.name]; |
176 | | - } |
177 | | - } |
178 | | - |
179 | | - unmountAll() { |
180 | | - for (const vmKey of Object.keys(this.viewModelObj)) { |
181 | | - const viewModel = this.viewModelObj[vmKey]; |
182 | | - if (typeof viewModel?.unmount === 'function') { |
183 | | - viewModel?.unmount(); |
184 | | - |
185 | | - // also clear all of its variable references to avoid detached elements |
186 | | - for (const ref of Object.keys(viewModel)) { |
187 | | - viewModel[ref] = null; |
188 | | - } |
189 | | - } |
190 | | - // nullify the object and then delete them to make sure they will be removed by the garbage collector |
191 | | - (<any>window)[vmKey] = null; |
192 | | - this.viewModelObj[vmKey] = null; |
193 | | - delete (<any>window)[vmKey]; |
194 | | - delete this.viewModelObj[vmKey]; |
195 | | - } |
196 | | - } |
197 | | -} |
198 | | - |
199 | | -// execute main init |
200 | | -const main = new Main(); |
201 | | -main.init(); |
| 8 | +new App().init(); |
0 commit comments