Skip to content

Commit

Permalink
CloseWatcher - minor subedit and live example (#36337)
Browse files Browse the repository at this point in the history
* Fix typo and add more info about activation

* CloseWatcher - minor edit and live example

* Further improve intro

* restucture intro

* Apply suggestions from code review

Co-authored-by: sideshowbarker <[email protected]>

---------

Co-authored-by: sideshowbarker <[email protected]>
  • Loading branch information
hamishwillee and sideshowbarker authored Oct 20, 2024
1 parent b55c682 commit e626cc8
Showing 1 changed file with 125 additions and 5 deletions.
130 changes: 125 additions & 5 deletions files/en-us/web/api/closewatcher/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,7 @@ browser-compat: api.CloseWatcher

{{APIRef("HTML DOM")}} {{SeeCompatTable}}

The `CloseWatcher` interface listens and responds to close requests.

Some UI components have "close behavior", meaning that the component appears, and the user can close it when they are finished with it. For example: sidebars, popups, dialogs, or notifications.

Users generally expect to be able to use a particular mechanism to close these elements, and the mechanism tends to be device-specific. For example, on a device with a keyboard it might be the <kbd>Esc</kbd> key, but Android might use the back button. For built-in components, such as [popover](/en-US/docs/Web/API/Popover_API) or {{htmlelement("dialog")}} elements, the browser takes care of these differences, closing the element when the user performs the close action appropriate for the device. However, when a web developer implements their own closable UI component (for example, a sidebar), it is hard to implement this kind of device-specific close behavior. The `CloseWatcher` interface solves this problem by delivering a `close` event to the when the user executes the close action for the device.
The `CloseWatcher` interface allows a custom UI component with open and close semantics to respond to device-specific close actions in the same way as a built-in component.

{{InheritanceDiagram}}

Expand Down Expand Up @@ -42,6 +38,23 @@ _This interface also inherits methods from its parent, {{domxref("EventTarget")}
- {{domxref("CloseWatcher.close_event", "close")}} {{Experimental_Inline}}
- : An event fired when a close request was received.

## Description

Some UI components have "close behavior", meaning that the component appears, and the user can close it when they are finished with it. For example: sidebars, popups, dialogs, or notifications.

Users generally expect to be able to use a particular mechanism to close these elements, and the mechanism tends to be device-specific. For example, on a device with a keyboard it might be the <kbd>Esc</kbd> key, but Android might use the back button. For built-in components, such as [popover](/en-US/docs/Web/API/Popover_API) or {{htmlelement("dialog")}} elements, the browser takes care of these differences, closing the element when the user performs the close action appropriate for the device. However, when a web developer implements their own closable UI component (for example, a sidebar), it is hard to implement this kind of device-specific close behavior.

The `CloseWatcher` interface solves this problem by delivering a `cancel` event, followed by a `close` event, when the user executes the device-specific close action.
Web applications can use the `onclose` handler to close the UI element in response to the device-specific event.
They can also trigger these same events in response to the UI element's normal closing mechanism, and then implement common `close` event handling for both the application- and device-specific close action.
Once the `onclose` event handler completes the `CloseWatcher` is destroyed and the events will no longer be fired.

In some applications the UI element may only be allowed to close when it is in a particular state; for example, when some needed information is populated.
To address these cases, applications can prevent the `close` event from being emitted by implementing a handler for the `cancel` event that calls {{domxref("Event.preventDefault()")}} if the UI element is not ready to close.

You can create `CloseWatcher` instances without [user activation](/en-US/docs/Web/Security/User_activation), and this can be useful to implement cases like session inactivity timeout dialogs. However, if you create more than one `CloseWatcher` without user activation, then the watchers will be grouped, so a single close request will close them both.
In addition, the first close watcher does not necessarily have to be a `CloseWatcher` object: it could be a modal dialog element, or a popover generated by an element with the popover attribute

## Examples

### Processing close requests
Expand All @@ -65,6 +78,113 @@ watcher.onclose = () => {
picker.querySelector(".close-button").onclick = () => watcher.requestClose();
```

### Closing a sidebar using a platform close request

In this example we have a sidebar component that is displayed when an "Open" button is selected, and hidden using either a "Close" button or platform-native mechanisms.
To make it more interesting, this is a live example!

Note also that the example is a little contrived, because normally we would use a toggle button to change a sidebar state.
We could certainly do that, but using separate "Open" and "Close" buttons makes it easier to demonstrate the feature.

#### HTML

The HTML defines "Open" and "Close" {{htmlelement("button")}} elements, along with {{htmlelement("div")}} elements for the main content and the sidebar.
CSS is used to animate the display of the sidebar element when the `open` class is added or removed from the sidebar and content elements (this CSS is hidden because it is not relevant to the example).

```html
<button id="sidebar-open" type="button">Open</button>
<button id="sidebar-close" type="button">Close</button>
<div class="sidebar">Sidebar</div>
<div class="main-content">Main content</div>
```

```css hidden
.sidebar {
position: fixed;
top: 20px;
left: -300px;
right: auto;
bottom: 0;
width: 300px; /* Adjust the width as needed */
background-color: lightblue;
}

.main-content {
position: fixed;
top: 20px;
left: 0;
right: 0;
bottom: 0;
width: auto; /* Adjust the width as needed */
background-color: green;
margin-left: 0px; /* Adjust for the sidebar width */
}

.sidebar.open {
left: 0; /* Slide the sidebar to the right when open */
transition: left 0.3s ease-in-out; /* Add a smooth transition effect */
}

.main-content.open {
margin-left: 300px; /* Adjust for the sidebar width */
transition: margin-left 0.3s ease-in-out;
background-color: green;
}
```

#### JavaScript

The code first gets variables for the buttons and `<div>` elements defined in the HTML.
It also defines a function `closeSidebar()` that is called when the sidebar is closed, to remove the `open` class from the `<div>` elements, and adds a `click` event listener that calls the `openSidebar()` method when the "Open" button is clicked.

```js
const sidebar = document.querySelector(".sidebar");
const mainContent = document.querySelector(".main-content");
const sidebarOpen = document.getElementById("sidebar-open");
const sidebarClose = document.getElementById("sidebar-close");

function closeSidebar() {
sidebar.classList.remove("open");
mainContent.classList.remove("open");
}

sidebarOpen.addEventListener("click", openSidebar);
```

The implementation of `openSidebar()` is given below.
The method first checks if the sidebar is already open, and if not, adds the `open` class to the elements so that the sidebar is displayed.

We then create a new `CloseWatcher` and add a listener that will call {{domxref("CloseWatcher.close()", "close()")}} on it if the "Close" button is clicked.
This ensures that the `close` event is called when either platform native close methods or the "Close" button are used.
The implementation of the `onclose()` event handler simply closes the sidebar, and the `CloseWatcher` is then destroyed automatically.

```js
function openSidebar() {
if (!sidebar.classList.contains("open")) {
sidebar.classList.add("open");
mainContent.classList.add("open");

//Add new CloseWatcher
const watcher = new CloseWatcher();

sidebarClose.addEventListener("click", () => watcher.close());

// Handle close event, invoked by platform mechanisms or "Close" button
watcher.onclose = () => {
closeSidebar();
};
}
}
```

Note that we chose to call `close()` on the watcher instead of {{domxref("CloseWatcher.requestClose()")}} because we don't need the `cancel` event to be emitted (we would use `requestClose()` and the `cancel` event handler if there was a reason to ever prevent the sidebar from closing prematurely).

#### Result

Select the "Open" button to open the sidebar. You should be able to close the sidebar using the "Close" button or the usual platform method, such as the <kbd>Esc</kbd> key on Windows.

{{ EmbedLiveSample("Closing a sidebar using a platform close request", "100%", "200") }}

## Specifications

{{Specifications}}
Expand Down

0 comments on commit e626cc8

Please sign in to comment.