# @turbonomic/turbo-ui-manager

> Wrapper library to embed and control Turbonomic UI within a parent web app

Latest version **1.4.1** (published 2023-06-21) · Apache-2.0 license · 0 weekly downloads

## Install

```sh
npm install @turbonomic/turbo-ui-manager
pnpm add @turbonomic/turbo-ui-manager
yarn add @turbonomic/turbo-ui-manager
bun add @turbonomic/turbo-ui-manager
```

## Health

**Score 25/100 (F)** — status: abandoned.

Positive: has types; no vulnerabilities; high quality score.

Warnings: low downloads; no esm support.

Negative: abandoned; low maintenance score.

## Facts

| | |
|---|---|
| Version | 1.4.1 |
| Published | 2023-06-21 |
| First published | 2019-11-26 |
| Weekly downloads | 0 |
| License | Apache-2.0 |
| TypeScript types | bundled |
| Module format | CommonJS |
| Dependencies | 3 |
| Unpacked size | 1.1 MB |
| Known vulnerabilities | 0 |
| Install scripts | no |
| Author | Turbonomic Inc. |
| Maintainers | vnikiten01, arun-veluchamy, akhandsi_ibm, bkearns.turbonomic, dflorissi, boconnell |

## Links

- npm: https://www.npmjs.com/package/@turbonomic/turbo-ui-manager
- Homepage: https://github.com/turbonomic
- npm.io page: https://npm.io/package/@turbonomic/turbo-ui-manager

## Dependencies (3)

- [postmate](https://npm.io/package/postmate.md) 1.5.2
- [base64url](https://npm.io/package/base64url.md) 3.0.1
- [@types/postmate](https://npm.io/package/@types/postmate.md) 1.5.0

## Recent versions

- 1.4.1 (latest) — 2023-06-21
- 1.4.0 — 2023-06-07
- 1.3.0 — 2020-11-20
- 1.2.0 — 2020-07-17
- 1.1.2 — 2020-06-24
- 1.1.1 — 2020-04-15
- 1.1.0 — 2020-04-14
- 1.0.2 — 2019-12-02
- 1.0.1 — 2019-11-26
- 1.0.0 — 2019-11-26

## README

<p align="center">
  <img src="https://cloud.githubusercontent.com/assets/4391815/26681386/05b857c4-46ab-11e7-8c71-15a46d886834.png">
</p>

> A lightweight frontend library with simple API to integrate and embed the [© Turbonomic, Inc](https://www.turbonomic.com/) UI within a parent application UI.

***

## Features
* Promise-based API.
* Bi-directional communication between parent and Turbonomic child application.
* Subscribe for notification about certain events.

## Changes/History
* see: [Changelog here](./CHANGELOG.md)

## Installing
`turbo-ui-manager` can be installed with npm or yarn.

**Yarn**
```bash
$ yarn add @turbonomic/turbo-ui-manager # Install via Yarn
```
**NPM**
```bash
$ npm i @turbonomic/turbo-ui-manager --save # Install via NPM
```

## Import
The library is built to UMD format with library namespace `TurboUIManager`;

**ES6 Imports**
```javascript
import {TurboUI} from '@turbonomic/turbo-ui-manager';
```
**HTML script include**
```html
<script src="./dist/turbo-ui-manager.js"></script>
...
<script>
  // self executing function here
  window.onload = function() {
    TurboUIManager.TurboUI.createUIComponent(...);
  };
</script>
```
**require**
```javascript
const TurboUIManager = require('turbo-ui-manager');
...
TurboUIManager.TurboUI.createUIComponent(...);
```

## Usage Overview
1. The `Parent Application` requests the `turbo-ui-manager` to create a new instance of the Turbonomic UI at a given URL attached to a given DOM node and returns an object with interface methods and properties.

2. The `turbo-ui-manaager` asynchronously establishes a handshake with the Turbonomic UI and notifies the `Parent Application` when this has completed or fails for any reason.

3. The `Parent Application` can subscribe for notification about certain events such as navigation view changes from within the Turbonomic UI instance, Errors, etc.

4. The `Parent Application` can command the Turbonomic UI to navigate to internal views using exposed methods on the interface.

5. The `Parent Application` can call the exposed `destroy` method to cleanup and remove the instance if/when it is no longer needed.

***
### Example

**Parent**
```css
.append-turbonomic-container {
   height: 900px;
   width: 100%;
 }
```

```javascript
import { TurboUI } from '@turbonomic/turbo-ui-manager';

const opts: TurboUI.ITurboUIManagerSettings = {
    url: 'http://example.com/turbonomic/app',
    handshakeTimeout: 1000 * 60,
};
const turboUIManager = TurboUI.createUIComponent(document.getElementById('append-turbonomic-container') as HTMLElement, opts);
// suggest to show some loading animation while waiting for onReady or onSessionStart
turboUIManager.on(TurboUIEvent.LOGIN_READY, onReady);
turboUIManager.on(TurboUIEvent.SESSION_START, onSessionStart);
turboUIManager.on(TurboUIEvent.ERROR, onError);

// immediate call to navigate to plans will be queued and processed after handshake establishes connection
turboUIManager.navigateToPlans();

// navigate to a supported view by view-name
turboUIManager.navigateToViewByName(TurboUI.TurboUIManagerView.SEARCH);
```

***

## API

### Methods

> #### `TurboUI.createUIComponent`

```typescript
const turboUIManager: TurboUI.ITurboUIManager = TurboUI.createUIComponent(domElement, options);
```
> This is the exposed function to initialize everything and return a reference with the public interface. It returns synchronously while the internal initialization happens asynchronously. It is possible to invoke calls with the returned interface before initialization has completed - such as subscribing for notification of events like the APP_READY event - these will simply be queued and handled afterwards.

Name | Type | Description | Required | Default
---- | ---- | ----------- | -------- | -------  
`domElement` | `HTMLElement` | _Passed in HTML element where the Turbonomic UI instance will be appended to in the DOM_ | `true` |
`options` | `ITurboUIManagerSettings` | _Options object with some config details for the connection to the Turbonomic UI_ | `true` |

---

> #### ITurboUIManager.navigateTo___________()

There are a number of ways to command navigation of the UI:

> * #### Navigate using exposed hooks for specific views
> Command the Turbonomic UI to transition to the given top-level view. Each of these corresponds to a `TurboUIManagerView`

```typescript
turboUIManager.navigateToHome();
turboUIManager.navigateToSearch();
turboUIManager.navigateToPlans();
turboUIManager.navigateToReports();
turboUIManager.navigateToPlacement();
turboUIManager.navigateToDashboards();
turboUIManager.navigateToSettings();
```

> * #### Navigate to view by view-name argument: ITurboUIManager.navigateToViewByName(viewName: TurboUIManagerView)
> Command the Turbonomic UI to transition to the given top-level view by supplying an enumerated view-name argument

```typescript
turboUIManager.navigateToViewByName(TurboUIManagerView.PLANS);
turboUIManager.navigateToViewByName(TurboUIManagerView.DASHBOARDS);
```

> * #### Navigate to view by url-encoded viewKey: ITurboUIManager.navigateToViewByKey(viewKey: base64-string)
> Command the Turbonomic UI to transition to a hashed-view "viewKey" which is a base64 url encoded view path.
> Ordinarily the intent is to instantiate the UI during create with a viewKey populated from the url query params in
> order to handle stateful page refreshes or url sharing/bookmarking. 

```typescript
turboUIManager.navigateToViewByKey(new URLSearchParams(window.location.search).get('viewKey'));
```

> * #### Navigate to view by scopeID: ITurboUIManager.navigateToScopeByID(scopeID: uuid-string)
> Command the Turbonomic UI to show scoped dashboard overview for a specific entity identified by its UUID (received from API)

```typescript
turboUIManager.navigateToScopeByID('1839473243894');
```

> * #### Navigate to view by view-options: ITurboUIManager.navigateToViewByOptions(viewOptions: TurboUI.ITurboUIManagerViewSettings)
> Encompasses a combination of all of the above options but follows a hard-coded priority order using the options as follows:
> 1. `scopeID` is checked and followed if it exists
> 2. `viewKey` is checked and followed if it exists
> 3. `view` enumerated view-name is checked and followed if it exists
> 4. `default` fallback is to navigate to the home view if no other condition is met.

```typescript
turboUIManager.navigateToViewByOptions({
  scopeID: '01234567891011',
  viewKey: 'Iy92aWV3L21haW4vaG9tZS9NYXJrZXQvaHlicmlk',
  view: TurboUIManagerView.HOME,
});
```

---

> #### ITurboUIManager.viewNotifications()

```typescript
turboUIManager.viewNotifications();
```
> Open the notifications slideout.

---

> #### ITurboUIManager.on()
```typescript
turboUIManager.on(eventName, callbackFunction);
```

> register a subscriber function for a given `TurboUIEvent`. It returns a handle function that can be invoked to remove the specific
> subscriber. Multiple subscribers can be bound to a given event name topic. To remove all subscribers for an event use `off`

Name | Type | Description | Required | Default
---- | ---- | ----------- | -------- | -------  
`eventName` | `TurboUIEvent` | _One-of TurboUIEvent values from exported enum_ | `true`
`callbackFunction` | `TurboEventListener` | _a callback function accepting 0-N number of arguments_ | `true`

---

> #### ITurboUIManager.off()

```typescript
turboUIManager.off(eventName);
```

> Deregister and removes all subscriber functions associated with specific `TurboUIEvent`

Name | Type | Description | Required | Default
---- | ---- | ----------- | -------- | -------
`eventName` | `TurboUIEvent` | _One-of TurboUIEvent values from exported enum to unsubscribe from_ | `true` |

---

> #### ITurboUIManager.clearAllSubscribers()

```typescript
turboUIManager.clearAllSubscribers();
```

> Deregister and removes all subscriber functions for all events. Use this is dismounting/hiding the TurboUI element 
> with the anticipation that you may resume showing it again as this will prevent potential detatched event listeners
> representing a memory-leak. You will need to re-register the subscribers for handling events upon resuming/showing
> the TurboUI once again.

---

> #### ITurboUIManager.hide()

```typescript
turboUIManager.hide(element);
```

> Hides/minimizes the turbo-ui instance attached to the provided element.
> Is Promise-based: Tries to Query-select the turbo-ui from the provided element and sets height to `'0px'` explicitly.
> IFF the query-selection cannot locate the underlying Turbo-UI, the promise will be rejected. This is useful because it 
> provides a way to be aware that a new call to create and instantiate the UI may be required (or indicates the root DOM
> element provided no longer contains the Turbo-UI as a child node).

Name | Type | Description | Required | Default
---- | ---- | ----------- | -------- | -------
`element` | `HTMLElement` | _The root HTML DOM element provided to create and mount the turboUI instance initially

---

> #### ITurboUIManager.show()

```typescript
turboUIManager.show(element);
```

> Show/maximizes the turbo-ui instance attached to the provided element.
> Is Promise-based: Tries to Query-select the turbo-ui from the provided element and sets height to `'inherit'` explicitly.
> IFF the query-selection cannot locate the underlying Turbo-UI, the promise will be rejected. This is useful because it 
> indicates either something went wrong with the root DOM node provided not being correct and/or that a new call to 
> initialize/create the Turbo-UI instance is required.

Name | Type | Description | Required | Default
---- | ---- | ----------- | -------- | -------
`element` | `HTMLElement` | _The root HTML DOM element provided to create and mount the turboUI instance initially

---

> #### ITurboUIManager.getEnabledViews()
```typescript
const enabledViews: TurboUI.TurboUIManagerView[] = turboUIManager.getEnabledViews();
```

> A method returning the list of enabled TurboUI.TurboUIManagerView items.
> Note: this is also notified as an argument to subscribers of the event `TurboUIManagerEvent.SESSION_START`.
---

> #### ITurboUIManager.getCurrentView()
```typescript
const currentView: TurboUI.TurboUIManagerView = turboUIManager.getCurrentView();
```

> Call method returning the `currentView` property of the underlying Turbonomic UI.

> #### ITurboUIManager.getCurrentViewState()
```typescript
const currentView: TurboUI.TurboUIManagerView = turboUIManager.getCurrentViewState();
```

> Call method returning an `IViewTransition` interface object describing the current state of the underlying Turbonomic UI.
> The object can contain the following properties: `viewName`, `viewUrl` and `viewKey`
> Note: it is also possible (and probably better) to subscribe for `TurboUIManagerEvent.VIEW_CHANGE` events with a 
> callback function that will receive this every time the value changes.

---

> #### ITurboUIManager.isSessionStarted()
```typescript
const isSessionStarted: boolean = turboUIManager.isSessionStarted();
```

> Check whether an authenticated session has been started within the TurboUI.
> Note: Typically you will subscribe for `TurboUIManagerEvent.SESSION_START` event to receive a callback when the session starts.
>       However, if you are re-connecting to an existing TurboUI container, the session may already (still) be active so this enables
>       to check the truthiness of that state.

---

> #### ITurboUIManager.getUrlFromViewKey(viewKey: base64-encoded-string)
```typescript
const urlPath: string = turboUIManager.getUrlFromViewKey('Iy92aWV3L21haW4vaG9tZS9NYXJrZXQvaHlicmlk');
console.log(urlPath); // ----> '#/view/main/home/Market/hybrid'
```

> Returns the decoded url-path of the view from the supplied view-key. The viewKey is a url-safe base64 encoded string. This
> is useful for logging or debugging purposes but is not necessary for navigation purposes normally. This path is also provided 
> along with the top-level `TurboUIManagerView` name and the encoded `viewKey` to the `TurboUIManagerEvent.VIEW_CHANGE` subscribers 

---

### Interfaces & Enums

> #### `TurboUI.ITurboUIManagerSettings`

```typescript
const options: TurboUI.ITurboUIManagerConnectionSettings = {
  url: 'https://example.com/turbonomic/app',
  handshakeTimeout: 5000,
  scopeID?: string|null,
  viewKey?: string|null,
  view?: TurboUIManagerView,
}
```

> This is the configuration object for connecting with the underlying Turbonomic UI instance. It is required to provide a fully-qualified URL to the instance.

> **Note** the URL for the Turbonomic UI _must be_ on the same domain as the parent application, else the handshake will fail.

Name | Type | Description | Required | Default
---- | ---- | ----------- | -------- | -------
`url` | `string` | _Fully Qualified URL pointing to the root of the Turbonomic instance to be embedded_ | `true` | |
`handshakeTimeout` | `number` | _Maximum time in milliseconds to wait for the handshake to be established with child Turbonomic UI instance_ | `false` | 10000 |
`scopeID` | `string (uuid)` | _(priority 1) Initialize the Turbo-UI connection to display the scoped dashboard overview for a specific entity identify by its uuid (as scopeID)_ | `false` | HOME |
`viewKey` | `string (base64)` | _(priority 2) Initialize the Turbo-UI connection to display the specified view identified by the base-64 url encoded hash_ | `false` | HOME |
`view` | `TurboUIManagerView` | _(priority 3) A specific Top-Level TurboUI View to display upon initializing the connection_ | `false` | HOME |

---

> #### `TurboUI.TurboUIManagerView`

> Enum of top-level views within the Turbonomic UI. These ordinarily would exist in the Left-hand Navbar. Any number of these can be restricted for various reasons.
> 

Values |
------ |
`HOME` ,`SEARCH` ,`PLANS` ,`PLACEMENT` ,`DASHBOARDS`, `REPORTS` ,`SETTINGS` ,`NOTIFICATIONS` |

---

> #### `TurboUI.TurboUIManagerEvent`
```typescript
turboUIManager.on(TurboUIEvent.ERROR, onError);
turboUIManager.on(TurboUIEvent.LOGIN_READY, onReady);
turboUIManager.on(TurboUIEvent.VIEW_CHANGE, onViewChange);
turboUIManager.on(TurboUIEvent.SESSION_START, onSessionStart);

private onReady = () => {
  // Turbonomic UI Login Form is loaded. May want to display some loading animation graphic until this is called.
  // IF configured for SSO integration, this event will not be emitted
};

private onSessionStart = (viewTransition: TurboUI.IViewTransition) => {
  // User session is logged-in and started; IViewTransition viewState information is returned
  // If using SSO for authentication, this is probably the first event that will be received to indicate the UI is ready
  // to be displayed. Recommend displaying some loading animation graphic until this is called.
};

private onError = (e: TurboUI.ITurboUIManagerError) => {
  // Some error has occurred. Read from property 'type' which is one-of TurboUI.TurboUIManagerErrorType in order to 
  // handle different types of errors accordingly (see error-types below)
};
```

#### onViewChange(TurboUI.TurboUIManagerEvent.

```typescript
TurboUI.on(TurboUI.VIEW_CHANGE, onViewChange);

private onViewChange = (viewTransition: TurboUI.IViewTransition) => {
  // Turbonomic UI View has changed (e.g. user has navigated from within the child application directly)
  const { viewName, viewKey, viewUrl } = viewTransition;
  console.log(viewName);    // ----> 'HOME'
  console.log(viewKey); // ----> 'Iy92aWV3L21haW4vaG9tZS9NYXJrZXQvaHlicmlk'
  console.log(viewUrl); // ----> '#/view/main/home/Market/hybrid'
};
```

Some notes about workflows with VIEW_CHANGE transitions:

1. Use-Case: Instantiate a new instance of IWO UI to a given View-State. 
  (The view transition info will come through the SESSION_START event in this case NOT the VIEW_CHANGE for the first transition)
  - View: e.g. "HOME"
  - ScopeID: Directly link to a scoped overview
  - ViewKey: browser refresh, bookmark, or link-sharing
  
2. Navigating back to existing Turbonomic UI instance from an external/parent UI view:
  - User went to Turbonomic View e.g. "HOME", then to some External/Parent View (non Turbonomic), and back to the SAME Turbonomic view as before e.g. "HOME" again.
  - Same as above but the second time going into Turbonomic they went to a different view (e.g. (Turbonomic) HOME -> (parent app) <some view> -> (Turbonomic) Plans ).

----

> A subscriber for View Transition events will receive an object conforming to `IViewTransition` interface.
> This object received identifies:

Name | Type | Description | Always-Included | Default
---- | ---- | ----------- | -------- | -------
`view` | `TurboUI.TurboUIManagerView` | _Top-Level enumerated TurboUI Manager View (useful to indicate corresponding navigation/breadcrumbs_ | `true` | |  
`viewKey` | `string` | _Base64, URL-safe encoding of the underlying TurboUI embedded view. Useful to push onto url-query string params to track for stateful reloads and url sharing/bookmarking_ | `true` | |
`viewUrl` | `string` | _Non-Encoded (not query-param safe) url path segment of the underlying TurboUI embedded view. Useful for human-readable logging and debugging_ | `true` | |

---

> #### `TurboUI.ITurboUIManagerError`  
> #### `TurboUI.TurboUIManagerErrorType`

```typescript
turboUIManager.on(TurboUIEvent.ERROR, this.onError);


private onError = (error: TurboUI.TurboUIManagerError) => {
  const errorType: TurboUI.TurboUIManagerErrorType = error.type;
  switch(errorType) {
    case TurboUIManagerErrorType.HANDSHAKE_FAILURE:
      console.warn('Failed to establish handshake with TurboUI within 10000 ms. time limit');
    case TurboUIManagerErrorType.INITIALIZATION_ERROR:
      console.warn('Some error occurred within the Turbonomic UI instance while initializing as embedded application');
    case TurboUIManagerErrorType.RESTRICTED_VIEW:
      console.warn(`View Transition Failed. ${view} is currently restricted`);
    default:
      console.error('Some unexpected error occurred');
  }
}
```

> A subscriber for Error events will receive an object conforming to `ITurboUIManagerError`. Within this object is a `type` field which is one of `ITurboUIManagerErrorType` which allows parent code to handle different error cases.

Name | Type | Description | Required | Default
---- | ---- | ----------- | -------- | -------
`url` | `string` | _Fully Qualified URL pointing to the root of the Turbonomic instance to be embedded_ | `true` | |  
`handshakeTimeout` | `number` | _Maximum time in milliseconds to wait for the handshake to be established with child Turbonomic UI instance_ | `false` | 10000 |

---

## License
Apache-2.0

---
_Source: https://npm.io/package/@turbonomic/turbo-ui-manager · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
