Skip to content

Commit d48b2d8

Browse files
committed
Support for exeternal events
1 parent d99fc0b commit d48b2d8

File tree

8 files changed

+59
-34
lines changed

8 files changed

+59
-34
lines changed

dev/index.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@ <h2>The shown widget is for demonstration purpose only</h2>
4949
</pre>
5050
</code>
5151

52+
<p>
53+
To fire an event that handled inside widget, click <a href="javascript:;" onclick="_hw('event', 'open')">here to open</a>,
54+
and here <a href="javascript:;" onclick="_hw('event', 'close')">to close it</a>.
55+
</p>
5256

5357
<p>
5458
Below is a text that doesn't make sense at all.</p>

src/App.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { AppContext } from './AppContext';
55

66
type Props = Configurations;
77
export const App = ({ element, ...appSettings }: Props) => (
8-
<AppContext config={appSettings}>
8+
<AppContext config={appSettings} element={element}>
99
<Main />
1010
</AppContext>
1111
);

src/AppContext.tsx

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,43 @@
11
import { h, createContext, ComponentChildren } from 'preact';
2-
import { AppConfigurations, WidgetApi } from './models';
3-
import { useRef } from 'preact/hooks';
2+
import { AppConfigurations, WidgetApi, Globals } from './models';
3+
import { useEffect, useRef, useState } from 'preact/hooks';
44
import { ApiClient } from './services/apiClient';
55

66
export const ConfigContext = createContext<AppConfigurations>({} as AppConfigurations);
77
export const ServiceContext = createContext<WidgetApi | undefined>(undefined);
8+
export const GlobalsContext = createContext<Globals>({ widgetOpen: false, setWidgetOpen: (o) => undefined });
89

910
interface Props {
1011
children: ComponentChildren;
1112
config: AppConfigurations;
13+
element?: HTMLElement;
1214
}
13-
export const AppContext = ({ children, config }: Props) => {
15+
export const AppContext = ({ children, config, element }: Props) => {
1416
const services = useRef(new ApiClient({
1517
baseUrl: config.serviceBaseUrl,
1618
debug: config.debug
1719
}));
20+
21+
const [widgetOpen, setWidgetOpen] = useState(!config.minimized);
22+
useEffect(() => {
23+
element?.addEventListener('widget-event', (e: CustomEvent<{ name?: string }>) => {
24+
switch (e.detail.name) {
25+
case 'open':
26+
setWidgetOpen(true);
27+
break;
28+
case 'close':
29+
setWidgetOpen(false);
30+
break;
31+
}
32+
});
33+
}, [element]);
34+
1835
return (
1936
<ConfigContext.Provider value={config}>
2037
<ServiceContext.Provider value={services.current}>
21-
{children}
38+
<GlobalsContext.Provider value={{ widgetOpen, setWidgetOpen }}>
39+
{children}
40+
</GlobalsContext.Provider>
2241
</ServiceContext.Provider>
2342
</ConfigContext.Provider>
2443
);

src/components/TitleBar.tsx

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,23 @@
11
import { h } from 'preact';
22
import style from './titlebar.css';
3-
import { useContext, useState, useEffect } from 'preact/hooks';
4-
import { ConfigContext } from '../AppContext';
3+
import { useContext } from 'preact/hooks';
4+
import { ConfigContext, GlobalsContext } from '../AppContext';
55

66
interface OwnProps {
7-
onOpen?: () => void;
8-
onMinimize?: () => void;
97
routeTitle: string;
108
}
119
const TitleBar = (props: OwnProps) => {
1210
const config = useContext(ConfigContext);
13-
const [minimized, setMinimized] = useState(config.minimized);
14-
useEffect(() => {
15-
if (minimized) {
16-
props.onMinimize?.();
17-
} else {
18-
props.onOpen?.();
19-
}
20-
}, [minimized, props.onMinimize, props.onOpen]);
11+
const { setWidgetOpen, widgetOpen } = useContext(GlobalsContext);
2112

2213
return (
23-
<div className={style.root} onClick={() => setMinimized(!minimized)}>
24-
<h4>{minimized
25-
? config.text.minimizedTitle ?? 'Help'
26-
: props.routeTitle}</h4>
14+
<div className={style.root} onClick={() => setWidgetOpen(!widgetOpen)}>
15+
<h4>{widgetOpen
16+
? props.routeTitle
17+
: (config.text.minimizedTitle ?? 'Help')}</h4>
2718
<a
28-
className={minimized ? style.minimized : style.open}
29-
title={minimized ? 'Open' : 'Minimize'} />
19+
className={widgetOpen ? style.open : style.minimized}
20+
title={widgetOpen ? 'Minimize' : 'Open'} />
3021
</div>);
3122
};
3223

src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,4 @@ loader(
2121
window,
2222
defaultConfig,
2323
window.document.currentScript,
24-
(el, config) => render(h(App, { ...config }), el));
24+
(el, config) => render(h(App, { ...config, element: el }), el));

src/layout/Main.tsx

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,17 @@ import style from './main.css';
33
import ContactForm from '../routes/ContactForm';
44
import ThankYou from '../routes/ThankYou';
55
import { useContext, useState } from 'preact/hooks';
6-
import { ConfigContext } from '../AppContext';
6+
import { ConfigContext, GlobalsContext } from '../AppContext';
77
import clsx from 'clsx';
88
import TitleBar from '../components/TitleBar';
99
import Faq from '../routes/Faq';
1010
import { Router, RouteComponent } from './Router';
1111

1212
const Main = () => {
1313
const config = useContext(ConfigContext);
14+
const { widgetOpen } = useContext(GlobalsContext);
15+
1416
const [title, setTitle] = useState('');
15-
const [minimized, setMinimized] = useState(config.minimized);
1617
const getTitle = (route: string) => {
1718
switch (route) {
1819
case '/thankyou':
@@ -28,12 +29,10 @@ const Main = () => {
2829
return (
2930
<div className={clsx(style.root, { [style.noDark]: config.disableDarkMode })}>
3031
<div>
31-
<TitleBar routeTitle={title}
32-
onMinimize={() => setMinimized(true)}
33-
onOpen={() => setMinimized(false)} />
32+
<TitleBar routeTitle={title} />
3433
<div className={clsx(
3534
style.container,
36-
{ [style.minimized]: minimized },
35+
{ [style.minimized]: !widgetOpen },
3736
config.styles.classNameContainer)}>
3837
<Router
3938
onChange={(r) => setTitle(getTitle(r))}

src/loader.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Configurations } from './models';
22

3-
type MethodNames = 'init';
3+
type MethodNames = 'init' | 'event';
44
export const DEFAULT_NAME = '_hw';
55

66
/**
@@ -45,6 +45,9 @@ export default (
4545
+ `This means you have multiple instances with same identifier (e.g. '${DEFAULT_NAME}')`);
4646
}
4747

48+
// this will an root element of the widget instance
49+
let targetElement: HTMLElement;
50+
4851
// iterate over all methods that were called up until now
4952
for (let i = 0; i < loaderObject.q.length; i++) {
5053
const item = loaderObject.q[i];
@@ -54,6 +57,7 @@ export default (
5457
} else if (i !== 0 && methodName === 'init') {
5558
continue;
5659
}
60+
5761
switch (methodName) {
5862
case 'init':
5963
const loadedObject = Object.assign(defaultConfig, item[1]);
@@ -63,7 +67,7 @@ export default (
6367

6468
// the actual rendering of the widget
6569
const wrappingElement = loadedObject.element ?? win.document.body;
66-
const targetElement = wrappingElement.appendChild(win.document.createElement('div'));
70+
targetElement = wrappingElement.appendChild(win.document.createElement('div'));
6771
targetElement.setAttribute('id', `widget-${instanceName}`);
6872
render(targetElement, loadedObject);
6973

@@ -81,8 +85,11 @@ export default (
8185
// to convert LoaderObject into sync calls to methods
8286
win[instanceName] = (method: MethodNames, ...args: any[]) => {
8387
switch (method) {
84-
// TODO: here you can handle additional sync interactions
85-
// with the widget from page
88+
case 'event': {
89+
targetElement?.dispatchEvent(
90+
new CustomEvent('widget-event', { detail: { name: args?.[0] } }));
91+
break;
92+
}
8693
default:
8794
console.warn(`Unsupported method [${method}]`, args);
8895
}

src/models.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,8 @@ export interface WidgetApi {
4141
getFaq: () => Promise<FaqModel[]>;
4242
sendForm: (model: FormModel) => Promise<void>;
4343
}
44+
45+
export interface Globals {
46+
widgetOpen: boolean;
47+
setWidgetOpen: (open: boolean) => void;
48+
}

0 commit comments

Comments
 (0)