Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added an example of how to use get user decision for beforeunload #754

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions how-to/workspace-platform-starter/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## v19.2.0

- Added an example workspace platform override (see [Get User Decision For Before Unload](./client/src/modules/platform-override/get-user-decision-for-beforeunload/README.md)) that supports **get user decision for before unload**. If a platform has the setting "enableBeforeUnload": true in the platform section of their manifest then OpenFin will call a platform's getUserDecisionForBeforeUnload override if any view (app) adds an event listener `beforeunload` (e.g. `window.addEventListener("beforeunload", beforeUnloadListener);`) and calls e.preventDefault(); when the event is fired. The platform override gets a list of all the views that have prevented the default close behavior and it can make a decision on what to do. Our module is an example of how you can plug in your own custom logic. We show a dialog to get confirmation on whether or not the view/page/window should close.

## v19.1.0

- Breaking Change (if you do not update your manifest): Added modules as an option for platformProvider settings and made the workspace platform starter platform override a module (so you can decide to load it, chain it with other overrides or exclude it). Please see the new document [how to customize your platform override](./docs/how-to-customize-your-platform-override.md). If you want the default platform override to check endpoints to for saving/fetching workspaces and pages then you need to add the default-wps-platform module id to the endpoint clients array in the endpointProvider (see the manifest.fin.json as an example).
- Updated module ids for default interop override to reflect new naming of modules for platform override: default-wps-interop instead of wps-interop-override.
- Updated Snap to 0.5.0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
## What is this?

This is an example and this type of logic is best placed on the server (to reduce load on the client) but we are giving a client example to show you a simple example and to demonstrate how platform overrides can be used for very specific use cases.
This module is off by default but you can turn it on to see quick examples.
This module is off by default and the configuration is not in the manifest but you can turn it on to see quick examples by using the configuration below.

This example is related to the following scenario:

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Get User Decision For Before Unload

## What is this?

Workspace has the ability to listen to the content/app loaded within a browser window. If that content/app has a `beforeunload` event listener and it prevents the default close behavior using e.preventDefault(); then a platform can be notified of this and decide how to react (should it notify the user and ask for confirmation on whether the close action should continue or should it just close).

This module does one thing. It provides a platform override for the function **getUserDecisionForBeforeUnload**. For this function to be called we need the following:

- The "enableBeforeUnload" setting has to be set to true in the platform section of your [manifest](../../../../../public/manifest.fin.json).
- You need to provide a **getUserDecisionForBeforeUnload** override in your platform override. We do this through this example module's [platform-override](./platform-override.ts).
- To see it in action you need to have an app that adds an event listener for "beforeunload" and calls e.preventDefault(). We have added an example app to our app catalogue called **Example Warn Before Closing App** and it prevents the default close behavior if you have text inside of the textbox and doesn't if the textbox is empty. The example comes from our container starter example: [Implement Warn Before Closing Dialog](https://github.com/built-on-openfin/container-starter/tree/main/how-to/use-platform/warn-before-closing-dialog).

## How is it configured?

This example module is defined as a platform override module in a manifest or settings service:

```json
{
"id": "get-user-decision-for-beforeunload",
"icon": "http://localhost:8080/favicon.ico",
"title": "get user decision for beforeunload",
"description": "get user decision for beforeunload",
"enabled": true,
"url": "http://localhost:8080/js/modules/platform-override/get-user-decision-for-beforeunload.bundle.js",
"data": {
"title": "Unsaved content changes",
"message": "You have unsaved changes in your content. Are you sure you want to close this {CLOSE_TYPE}?",
"cancelButtonLabel": "Cancel",
"closeButtonLabel": "Close"
}
}
```

This is defined in the modules section of the platformProvider settings.

Order is important when it comes to platform and interop overrides. The request will hit the first entry in the array and every time super.x is called it will go to the next entry in the array until it hits the default implementation.

The module makes use of our dialog helper to create a popup to get a decision from the user. There are default labels for the title, message and buttons but this example shows how different labels can be provided through configuration.

## How can I test this?

- You would enable this module in the manifest.fin.json file in the public folder by copying the configuration from above.
- You would launch the platform using npm run client
- You would then launch the **Example Warn Before Closing App** through home (or store).
- You would close the window and see that no dialog was shown.
- You would launch the **Example Warn Before Closing App** again but this time add some text into the textbox.
- You would close the window and see that a dialog was shown to ask for confirmation.

## Where can I find out more about Platform Overrides

[How To Customize Your Platform Override](../../../../../docs/how-to-customize-your-platform-override.md)
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import type { ModuleImplementation, ModuleTypes } from "workspace-platform-starter/shapes/module-shapes";
import { GetUserDecisionForBeforeunload } from "./platform-override";

/**
* Define the entry points for the module.
*/
export const entryPoints: { [type in ModuleTypes]?: ModuleImplementation } = {
platformOverride: new GetUserDecisionForBeforeunload()
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
// eslint-disable-next-line max-classes-per-file
import type OpenFin from "@openfin/core";
import type { ViewsPreventingUnloadPayload, WorkspacePlatformProvider } from "@openfin/workspace-platform";
import type { Logger, LoggerCreator } from "workspace-platform-starter/shapes/logger-shapes";
import type { ModuleDefinition } from "workspace-platform-starter/shapes/module-shapes";
import type {
PlatformOverride,
PlatformOverrideHelpers,
PlatformOverrideOptions
} from "workspace-platform-starter/shapes/platform-shapes";
import type { GetUserDecisionForBeforeunloadOptions } from "./shapes";

/**
* Implementation for the get user decision for beforeunload platform override.
*/
export class GetUserDecisionForBeforeunload
implements PlatformOverride<GetUserDecisionForBeforeunloadOptions>
{
/**
* The module definition including settings.
* @internal
*/
private _definition: ModuleDefinition<GetUserDecisionForBeforeunloadOptions> | undefined;

/**
* The logger for displaying information from the module.
* @internal
*/
private _logger?: Logger;

/**
* Helper methods for the module.
* @internal
*/
private _helpers: PlatformOverrideHelpers | undefined;

/**
* Initialize the module.
* @param definition The definition of the module from configuration include custom options.
* @param loggerCreator For logging entries.
* @param helpers Helper methods for the module to interact with the application core.
* @returns Nothing.
*/
public async initialize(
definition: ModuleDefinition<GetUserDecisionForBeforeunloadOptions>,
loggerCreator: LoggerCreator,
helpers: PlatformOverrideHelpers
): Promise<void> {
this._definition = definition;
this._logger = loggerCreator("GetUserDecisionForBeforeunload");
this._helpers = helpers;

this._logger.info("Initializing");
}

/**
* Close down any resources being used by the module.
* @returns Nothing.
*/
public async closedown(): Promise<void> {
this._logger?.info("Closedown");
}

/**
* Get the override constructor for the platform override (useful if you wish this implementation to be layered with other implementations and passed to the platform's initialization object as part of an array).
* @param options The options for the platform override defined as part of the platform.
* @returns The override constructor to be used in an array.
*/
public async getConstructorOverride(
options: PlatformOverrideOptions
): Promise<OpenFin.ConstructorOverride<WorkspacePlatformProvider>> {
return (Base: OpenFin.Constructor<WorkspacePlatformProvider>) => {
// use settings passed through the module definition in your override or the default options passed with the function call
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const moduleData = this._definition?.data ?? {};
const logger = this._logger;
const helpers = this._helpers;
/**
* Extend the Platform Override.
*/
return class CustomPlatformOverride extends Base {
/**
* Constructor for the interop override.
*/
constructor() {
super();
// this is just an example to show a reference to the options, module data and local reference to the passed helpers.
logger?.info(
`Options passed: ${JSON.stringify(options)} and module data: ${JSON.stringify(moduleData)} with session id: ${helpers?.sessionId}`
);
}

/**
* Handle the decision of whether a Window, Page or specific View should close when trying to prevent an unload. This is meant to be overridden.
* Called in {@link WorkspacePlatformProvider.closeWindow} and {@link WorkspacePlatformProvider.closeView}.
*
* When closing a Page, this override is called by {@link WorkspacePlatformProvider.shouldPageClose}
* and page proceeds to close if all views passed in are determined to close. In this case, the `closeType` property will have `'page'` value.
*
* Normally you would use this method to show a dialog indicating that there are Views that are trying to prevent an unload.
* By default it will always return all Views passed into it as meaning to close.
* @param payload payload containing the views that are preventing an unload, views that are not, and the type of close operation
* @returns `Promise<{windowShouldClose: boolean, viewsToClose: OpenFin.Identity[]}>`
*/
public async getUserDecisionForBeforeUnload(
payload: ViewsPreventingUnloadPayload
): Promise<OpenFin.BeforeUnloadUserDecision> {
logger?.info("getUserDecisionForBeforeUnload called:", payload);
if (payload.viewsPreventingUnload.length > 0 && helpers?.getDialogClient) {
const dialogClient = await helpers?.getDialogClient();
let title = "Unsaved content changes";
let message = "You have unsaved changes. Are you sure you want to close?";
let cancelButtonLabel = "Cancel";
let closeButtonLabel = "Close";

if (moduleData?.title && moduleData.title.trim().length > 0) {
title = moduleData.title;
}
if (moduleData?.message && moduleData.message.trim().length > 0) {
message = moduleData.message.replace("{CLOSE_TYPE}", payload.closeType);
} else if (payload.closeType === "window") {
message =
"You have unsaved changes in your content. Are you sure you want to close this window?";
} else if (payload.closeType === "page") {
message = "You have unsaved changes in your content. Are you sure you want to close this page?";
} else if (payload.closeType === "view") {
message = "You have unsaved changes. Are you sure you want to close this view?";
}

if (moduleData?.cancelButtonLabel && moduleData.cancelButtonLabel.trim().length > 0) {
cancelButtonLabel = moduleData.cancelButtonLabel;
}

if (moduleData?.closeButtonLabel && moduleData.closeButtonLabel.trim().length > 0) {
closeButtonLabel = moduleData.closeButtonLabel;
}

const result = await dialogClient?.showConfirmation(
{
title,
message,
buttons: [
{
label: cancelButtonLabel,
id: "cancel"
},
{
label: closeButtonLabel,
id: "close"
}
]
},
payload.windowId
);
if (result) {
if (result.id === "close") {
const views = [...payload.viewsNotPreventingUnload, ...payload.viewsPreventingUnload];
return {
windowShouldClose: true,
viewsToClose: views
};
}
return { windowShouldClose: false, viewsToClose: [] };
}
}
return super.getUserDecisionForBeforeUnload(payload);
}
};
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**
* Options for the get user decision for beforeunload platform override.
*/
export interface GetUserDecisionForBeforeunloadOptions {
/**
* The title of the dialog window if there are unsaved content changes.
*/
title?: string;

/**
* The message to show. Will replace the token {CLOSE_TYPE} with the string window, page or view depending on the close type.
*/
message?: string;

/**
* The label for the button that cancels the close action because there are unsaved content changes: Defaults to Cancel.
*/
cancelButtonLabel?: string;

/**
* The label for the button that continues the close action even if there are unsaved content changes.
*/
closeButtonLabel?: string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -826,6 +826,28 @@ const configs = [
experiments: {
outputModule: true
}
},
{
entry: './client/src/modules/platform-override/get-user-decision-for-beforeunload/index.ts',
devtool: 'source-map',
module: {
rules: [loaderRule]
},
resolve: {
extensions: ['.tsx', '.ts', '.js'],
alias
},
externals: { fin: 'fin' },
output: {
filename: 'get-user-decision-for-beforeunload.bundle.js',
library: {
type: 'module'
},
path: path.resolve(__dirname, '..', 'public', 'js', 'modules', 'platform-override')
},
experiments: {
outputModule: true
}
}
];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,5 +87,6 @@ This now opens up the capability to add your own logic to the platform (you want
- [Default Workspace Starter Platform Override Module](../client/src/modules/platform-override/wps-platform-override/)
- [Example Workspace Platform Override That Validates App Urls and Access](../client/src/modules/platform-override/application-url-and-access-validator/)
- [Example Workspace Platform Override That Has Custom Snap SDK Logic To Determine Which Windows Can Snap](../client/src/modules/platform-override/snap-window-selection-override/)
- [Example Workspace Platform Override That supports notifying users about Apps having unsaved changes when closing a window/page/view](../client/src/modules/platform-override/get-user-decision-for-beforeunload/README.md)

[<- Back to Table Of Contents](../README.md)
24 changes: 24 additions & 0 deletions how-to/workspace-platform-starter/public/common/apps.json
Original file line number Diff line number Diff line change
Expand Up @@ -317,5 +317,29 @@
"intents": [],
"images": [],
"tags": ["developer", "view"]
},
{
"appId": "openfin-warn-before-closing",
"name": "openfin-warn-before-closing",
"title": "Example Warn Before Closing App",
"description": "An example app from our container starter: https://github.com/built-on-openfin/container-starter/tree/main/how-to/use-platform/warn-before-closing-dialog that makes the platform aware if there are unsaved changes (platform needs enableBeforeUnload to be set to true in the manifest and needs to override getUserDecisionForBeforeUnload.",
"manifest": {
"url": "https://built-on-openfin.github.io/container-starter/main/use-platform-warn-before-closing-dialog/html/view.html",
"customData": {},
"api": {},
"fdc3InteropApi": "2.0"
},
"manifestType": "inline-view",
"icons": [
{
"src": "http://localhost:8080/common/images/icon-blue.png"
}
],
"contactEmail": "[email protected]",
"supportEmail": "[email protected]",
"publisher": "OpenFin",
"intents": [],
"images": [],
"tags": ["developer", "view"]
}
]
1 change: 1 addition & 0 deletions how-to/workspace-platform-starter/public/manifest.fin.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"preventQuitOnLastWindowClosed": true,
"appLogsTimezone": "utc",
"enableAppLogging": false,
"enableBeforeUnload": true,
"logManagement": {
"enabled": false
},
Expand Down
Loading