# Async loading and multiple chat rooms

> How to register events before uchat.js loads, how to split events across several chat rooms on one page, and how to attach and clean up chat rooms in an SPA, and how to add a floating widget in the bottom-right corner.

`uchat.js` loads with `async`, so its run order relative to your page scripts is not fixed. This page covers how to register events regardless of load order, how to handle several chat rooms, and how to attach and detach chat rooms in an SPA.

## Preload code {#preload}

Add the code below **before `uchat.js`**. It stores events registered before `uchat.js` is ready in `U.events`, and they are used as-is once it is ready.

```html
<script>U=window.U=window.U||{},U.events=U.events||[],U.chat=function(n){return{on:function(e,t){U.events.push([n,e,t])},off:function(e){for(var t=U.events.length;t>0;)U.events[--t][0]==n&&U.events[t][1]==e&&U.events.splice(t,1)}}};</script>
<script async src="https://client.uchat.io/uchat.js"></script>
```

When `uchat.js` is ready, `U.chat` is replaced with the real function. There is one difference between before and after.

| | Before ready (preload code) | After ready |
| --- | --- | --- |
| `on(name, callback)` | Stored | Registered (`after.create` for rooms that are already ready is called immediately) |
| `off(name)` | Removes only entries whose name is **exactly the same** | Removes by label or event name (see below) |

Before it is ready, passing only a label such as `off('#label')` removes nothing. In cleanup code that might run at any time, pass the full name you used in `on` (`'after.message#alarm'`) to `off` as-is. This works both before and after it is ready.

## Selecting chat rooms {#select-room}

```js
U.chat('*').on('after.join', handler);      // All chat rooms on the page
U.chat('room-a').on('after.join', handler); // room-a only
U.chat('room-b').on('after.join', handler); // room-b only
```

- `U.chat('room-id')` registers for that chat room only, and `U.chat('*')` registers for all chat rooms on the page (including ones added later).
- You cannot pass several IDs at once. Register for each one, or register with `'*'` and branch on `room.id` inside the callback.
- If the chat room is already open, `U.chat('room-id')` returns that chat room's `room` object. If not, it returns a registrar with only `on` and `off`. Either way, you use `on` and `off` the same way. → [U.chat() and the room object](/en/doc/3.2)

## Labels and removal {#labels}

Add `#label` after an event name to remove it by label later.

```js
U.chat('*').on('after.message#alarm', onMessage);
U.chat('*').on('after.user.join#alarm', onJoin);

U.chat('*').off('#alarm');              // Everything labeled alarm
U.chat('*').off('after.message');       // All after.message (any label)
U.chat('*').off('after.message#alarm'); // Only entries matching both
```

`U.chat('*').off(...)` removes not only what was registered with `'*'` but **also everything registered per chat room**. To avoid removing events registered by other scripts (plugins, other widgets), give your events a unique label (such as `#my-widget`) and remove them by label.

## after.create and readiness {#after-create}

`after.create` fires when the chat room screen (skin) is ready. It comes before joining, so set up the [Skin UI API](/en/doc/3.4) and channels inside it.

If you register `after.create` on a chat room that is already ready, it is called once right away without waiting. So you do not miss it even if your script runs late.

```js
U.chat('*').on('after.create#my-widget', function (room) {
	if (room.id !== 'my-room') return;
	// Use room.skin, room.plugin
});
```

## Adding and removing tags, changing attributes {#tag-lifecycle}

`uchat.js` watches the `<u-chat>` tags on the page.

- Add a `<u-chat>` to the page and the chat room opens. Remove it and the chat room closes.
- When a chat room closes, **events registered with that chat room ID are removed with it.** Events registered with `'*'` remain. If you need the events again when you re-add the tag, register them again or use `'*'`.
- Only one tag per `room` ID opens on a page. A second tag is ignored, and removing the first tag does not make the second one open in its place.
- **Changes to attributes** such as `room` and `user_data` **are not detected.** To change a value, remove the tag and add it again with the new attributes.

```js
function replaceRoom(container, roomId) {
	container.replaceChildren(); // Remove the existing <u-chat> → chat room closes
	var tag = document.createElement('u-chat');
	tag.setAttribute('room', roomId);
	tag.style.cssText = 'display:block; width:100%; height:500px;';
	container.appendChild(tag); // New chat room opens
}
```

## Using in an SPA {#spa}

In an SPA, load `uchat.js` only once when a chat room enters the screen, and clean up your events when you leave the screen. When the framework removes the `<u-chat>` tag, the chat room closes with it.

The `uchat()` function below does the same job as the preload code, so it is fine if it runs before `uchat.js`.

```js
// uchat.js — a framework-agnostic helper
export function uchat(roomId) {
	const U = (window.U = window.U || {});
	U.events = U.events || [];
	if (!U.chat) {
		U.chat = (n) => ({
			on: (e, t) => U.events.push([n, e, t]),
			off: (e) => {
				for (let t = U.events.length; t > 0; )
					U.events[--t][0] == n && U.events[t][1] == e && U.events.splice(t, 1);
			}
		});
	}
	if (!document.querySelector('script[data-uchat]')) {
		const s = document.createElement('script');
		s.src = 'https://client.uchat.io/uchat.js';
		s.async = true;
		s.dataset.uchat = '';
		document.head.appendChild(s);
	}
	return U.chat(roomId);
}
```

React:

```jsx
import { useEffect } from 'react';
import { uchat } from './uchat';

export default function ChatRoom({ roomId }) {
	useEffect(() => {
		const name = 'after.message#my-widget-' + roomId;
		uchat('*').on(name, (room, data) => {
			if (room.id !== roomId) return;
			console.log(data.user.nick, data.content);
		});
		return () => uchat('*').off(name);
	}, [roomId]);

	// Changing key creates a new tag, so it switches to the other chat room
	return <u-chat key={roomId} room={roomId} style={{ display: 'block', width: '100%', height: '500px' }} />;
}
```

- Register events with `'*'` and branch on `room.id`. Events registered with a chat room ID are removed along with the tag, so registering with `'*'` keeps a remount from losing events to that removal order.
- When cleaning up, pass the full name you used in `on` to `off`. It is removed even if `uchat.js` is not ready yet.
- Changing only the `room` attribute does not switch chat rooms, so use `key` to create a new tag.

In Vue, call `on` in `onMounted` and `off` in `onUnmounted`, and register `u-chat` as a custom element (see [5-minute setup](/en/doc/1.2#frameworks)). In Svelte, register `on` in `onMount` and call `off` in the cleanup function it returns. To switch chat rooms, create a new tag with `:key` in Vue and a `{#key roomId}` block in Svelte.

## Floating widget {#widget}

To attach a chat room as a button in the bottom-right corner of the page, use the widget from the `@u-chat/embed` npm package. While closed, it shows new messages as a preview with an unread count, and clicking it opens the chat. You can also generate this code in the **Floating widget** tab of the install code screen on the admin page, and pick the attributes below, plus position and size, under **Widget settings** on that screen.

Sites without a bundler (HTML, Gnuboard, WordPress, and so on) load it with one `<script>` line. You can add it to a page that already loads `uchat.js`.

```html
<script src="https://cdn.jsdelivr.net/npm/@u-chat/embed@latest/dist/widget.global.js"></script>
<u-chat-widget launcher-text="{user_count} online">
	<u-chat room="my-room"></u-chat>
</u-chat-widget>
```

To load it from the chat server instead of jsDelivr, use `https://client.uchat.io/widget.js`. An independent server uses its own `widget.js`, and the widget connects to the `uchat.js` on that same server, so no `src` attribute is needed.

React (`npm install @u-chat/react`):

```jsx
import { UchatWidget } from '@u-chat/react';

export default function App() {
	return <UchatWidget room="my-room" launcherText="{user_count} online" />;
}
```

Svelte 5 (`npm install @u-chat/svelte`):

```svelte
<script>
	import { UchatWidget } from '@u-chat/svelte';
</script>

<!-- Pass the text as {'...'} so Svelte does not read the braces as an expression -->
<UchatWidget room="my-room" launcherText={'{user_count} online'} />
```

Vue 3 (`npm install @u-chat/vue`). No `isCustomElement` setting is needed.

```vue
<script setup>
import { UchatWidget } from '@u-chat/vue';
</script>

<template>
	<UchatWidget room="my-room" launcher-text="{user_count} online" />
</template>
```

Widget attributes (camelCase such as `launcherText` in React and Svelte):

| Attribute | Description |
| --- | --- |
| `launcher-text` | Button text. `{user_count}` becomes the user count and `{unread_count}` the unread count |
| `variant` | `bubble` (default, round button), `bar` (long bar at the bottom that opens as a bottom sheet) or `side` (right-edge sidebar: open from the start at full screen height, pushing the page content to the left; folding it leaves a vertical tab on the right edge. Screens narrower than 768px start folded and overlay when opened) |
| `launcher-color`, `launcher-image` | Button background color and background image URL (the image wins) |
| `panel-title` | Title above the open chat |
| `preview-duration` | How long a new message preview stays (ms, default 5000) |
| `lang` | Language of the widget buttons and messages (`ko`, `en`, `ja`). Without it: the inner `<u-chat lang>` → the page `<html lang>` → the browser language; other languages get English |
| `src` | `uchat.js` URL. For an independent server, use that server's URL |

- For member-integrated chat rooms, put `user_data` on the inner `<u-chat>` (`userData` in React, Svelte and Vue). On the install code screen, **Placement → Floating widget** gives widget code with the signature already in it.
- The widget joins the chat room as soon as the page opens, so the preview and user count show even while it is closed. Past chat (`past` in `after.message`) and your own messages are not counted as unread.
- Hiding it with `×` disconnects and remembers the hidden state for this chat room only. A small button to turn it back on stays in the bottom-right corner.
- From scripts, call `open()`, `close()`, `toggle()`, `maximize()`, `restore()`, `hide()`, `show()`, and `destroy()`. In React, Vue and Svelte, put a `ref` (`bind:this` in Svelte) on the component and call the same names.
- If `uchat.js` cannot be loaded, the widget gets an `uchat-widget-error` event (`event.detail` is the error).

To create the widget from a script instead of a tag, use `createUchatWidget(target, options)`. It is on `window.UchatEmbed` with the one-line script and in `@u-chat/embed/widget` with npm.

```js
const widget = UchatEmbed.createUchatWidget(document.body, {
	chat: { room: 'my-room', user_data: signedUserData },
	widget: { launcherText: '{user_count} online', variant: 'bar' },
});
widget.addEventListener('uchat-widget-error', (event) => console.error(event.detail));
widget.open();
```

Change the position and size with CSS variables.

```css
u-chat-widget {
	--uchat-widget-right: 20px;
	--uchat-widget-bottom: 20px;
	--uchat-widget-panel-width: 380px;
	--uchat-widget-panel-height: 640px;
	--uchat-widget-side-width: 360px;
	--uchat-widget-accent: #ff7e25;
	--uchat-widget-z-index: 2147483000;
}
```
