Skip to content

Commit

Permalink
feat: improve tile dialog rendering support (#144)
Browse files Browse the repository at this point in the history
* feat: improve tile dialog rendering support

The Embed SDK and Looker now send and receive information that allows
dialogs opened from tiles to be rendered in a user friendly manner.

1. The top of tile drilling dialogs is scrolled into view.
2. Alert and download dialogs remain in view.

The developer has to make a couple of simple calls to activate this
functionality.

In addition, a convenience method has been added to simplify IFRAME
dynamic height support.
  • Loading branch information
bryans99 committed Mar 13, 2023
1 parent 59b1322 commit 93051a2
Show file tree
Hide file tree
Showing 17 changed files with 425 additions and 85 deletions.
69 changes: 68 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -594,11 +594,50 @@ Prior to the release of the Embed SDK, Looker exposed an API that utilized JavaS
## Additional Considerations
## Dynamic dashboard height
The IFRAMEs containing dashboards can be resized to reflect the height of the embedded dashboard. This allows the IFRAME to own the scrollbar rather than the embedded dashboard. To implement dynamic dashboard heights, listen to `page:properties:changed` events and use the height to set the IFRAME height. Example:
```javascript
const pagePropertiesChangedHandler = (
{ height }: PagePropertiesChangedEvent,
elementId: string
) => {
if (height && height > 100) {
const element = document.querySelector(
`#${elementId} iframe`
) as HTMLIFrameElement
if (element) {
element.style.height = `${height}px`
}
}
}


LookerEmbedSDK.createDashboardWithId(runtimeConfig.dashboardId)
.appendTo('#dashboard')
.on('page:properties:changed', (event: PagePropertiesChangedEvent) => {
pagePropertiesChangedHandler(event, 'dashboard')
})
.build()
.connect()
```
The Embed SDK also contains a convenience method to add this functionality for you. Example:
```javascript
LookerEmbedSDK.createDashboardWithId(runtimeConfig.dashboardId)
.withDynamicIFrameHeight()
.appendTo('#dashboard')
.build()
.connect()
```
### Full screen tile visualizations
Looker has the capability to display individual tile visualizations in full screen mode. This feature works for embedded IFRAMEs but the `fullscreen` feature MUST be added to the containing IFRAME. Version 1.8.2 of the Embed SDK was updated to allow features to be added. The following example shows how to enable support for full screen mode.
```
```javascript
LookerEmbedSDK.createDashboardWithId(runtimeConfig.dashboardId)
// Allow fullscreen tile visualizations
.withAllowAttr('fullscreen')
Expand All @@ -617,3 +656,31 @@ Looker has the capability to display individual tile visualizations in full scre
...
})
```
### Tile dialogs
Users have the capability of opening dialogs from a dashboard tile. One downside of opening the dialogs is that unexpected scrolling can occur. With Looker 23.6+ it is now possible to mitigate the scrolling using the Embed SDK. Example:
```javascript
LookerEmbedSDK.createDashboardWithId(runtimeConfig.dashboardId)
// Scrolls the top of the IFRAME into view when drilling
.withDialogScroll()
// Ensures that the tile download and tile alert dialogs remain in view
.withScrollMonitor()
// Append to the #dashboard element
.appendTo('#dashboard')
...
// Finalize the build
.build()
// Connect to Looker
.connect()
// Finish up setup
.then((dashboard: LookerEmbedDashboard) => {
...
})
.catch((error: Error) => {
...
})
```
Note that this functionality is also available to the javascript API. See [here](demo/message_example.ts) for how to add this functionality.
35 changes: 9 additions & 26 deletions demo/demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ import type {
LookerEmbedLook,
LookerEmbedDashboard,
LookerEmbedExplore,
PagePropertiesChangedEvent,
SessionStatus,
} from '../src/index'
import { LookerEmbedSDK } from '../src/index'
Expand Down Expand Up @@ -169,28 +168,6 @@ const preventNavigation = (event: any): any => {
return {}
}

/**
* A page properties changed handler that can be used to control the height of the
* embedded IFRAME. Different dashboards can be displayed by either calling the
* `loadDashboard` Embed SDK method OR by using the inbuilt embed content navigation
* feature. Whenever, the dashboard changes a `page:properties:changed` event is
* fired and this event contains the height of the dashboard content.
*/
const pagePropertiesChangedHandler = (
{ height }: PagePropertiesChangedEvent,
elementId: string
) => {
const { useDynamicHeights } = getConfiguration()
if (useDynamicHeights && height && height > 100) {
const element = document.querySelector(
`#${elementId} iframe`
) as HTMLIFrameElement
if (element) {
element.style.height = `${height}px`
}
}
}

/**
* Initialize the show dashboard configuration checkbox.
*/
Expand Down Expand Up @@ -378,6 +355,15 @@ const renderDashboard = (runtimeConfig: RuntimeConfig) => {
document.querySelector<HTMLDivElement>('#demo-dashboard')!.style.display =
''
LookerEmbedSDK.createDashboardWithId(runtimeConfig.dashboardId)
// When true scrolls the top of the IFRAME into view
.withDialogScroll(runtimeConfig.useDynamicHeights)
// When true updates the IFRAME height to reflect the height of the
// dashboard
.withDynamicIFrameHeight(runtimeConfig.useDynamicHeights)
// When true monitors the scroll position of the hosting window
// and sends it to the Looker IFRAME. The Looker IFRAME uses the
// information to position dialogs correctly.
.withScrollMonitor(runtimeConfig.useDynamicHeights)
// Allow fullscreen tile visualizations
.withAllowAttr('fullscreen')
// Append to the #dashboard element
Expand All @@ -403,9 +389,6 @@ const renderDashboard = (runtimeConfig: RuntimeConfig) => {
.on('dashboard:delete:complete', () =>
updateStatus('#dashboard-state', 'Deleted')
)
.on('page:properties:changed', (event: PagePropertiesChangedEvent) => {
pagePropertiesChangedHandler(event, 'dashboard')
})
.on('session:status', (event: SessionStatus) => {
processSessionStatus(event, '#dashboard-state')
})
Expand Down
72 changes: 65 additions & 7 deletions demo/message_example.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import type {
LookerEmbedCookielessSessionData,
PagePropertiesChangedEvent,
EnvClientDialogEvent,
} from '../src/index'
import {
initSSOEmbed,
Expand Down Expand Up @@ -212,6 +213,29 @@ const initializePreventNavigationCheckbox = () => {
}
}

/**
* Get the id of the dashboard IFRAME
*/
const getDashboardFrameId = ({ dashboardId }: RuntimeConfig) =>
`embed-dasboard-${dashboardId}`

/**
* Send scroll data to the Looker client
*/
const sendScrollData = () => {
const runtimeConfig = getConfiguration()
const dashboardFrameId = getDashboardFrameId(runtimeConfig)
const element = document.getElementById(dashboardFrameId)
if (element) {
getEmbedFrame(dashboardFrameId)?.send('env:host:scroll', {
offsetLeft: element.offsetLeft,
offsetTop: element.offsetTop,
screenX: window.scrollX,
scrollY: window.scrollY,
})
}
}

/**
* Initialize the use dynamic heights configuration checkbox.
*/
Expand All @@ -226,6 +250,14 @@ const initializeUseDynamicHeightsCheckbox = () => {
updateConfiguration(runtimeConfig)
location.reload()
})
if (useDynamicHeights) {
document.addEventListener('scroll', (_event: Event) => {
sendScrollData()
})
window.addEventListener('resize', (_event: Event) => {
sendScrollData()
})
}
}
}

Expand Down Expand Up @@ -254,12 +286,6 @@ const initializeConfigurationControls = () => {
initializeResetConfigButton()
}

/**
* Get the id of the dashboard IFRAME
*/
const getDashboardFrameId = ({ dashboardId }: RuntimeConfig) =>
`embed-dasboard-${dashboardId}`

/**
* Initialize the dashboard controls
*/
Expand Down Expand Up @@ -297,6 +323,36 @@ const initializeDashboardControls = (runtimeConfig: RuntimeConfig) => {
}
}

/**
* Scroll drilling dialog into view
*/
const envClientDialogEventListener = ({
open,
placement,
}: EnvClientDialogEvent) => {
const runtimeConfig = getConfiguration()
if (runtimeConfig.useDynamicHeights) {
const dashboardFrameId = getDashboardFrameId(runtimeConfig)
const element = document.getElementById(dashboardFrameId)
if (element) {
// Placement of 'cover' means that the dialog top is close
// to the top of the IFRAME. The top MAY be scrolled out
// of view. The following attempts to scroll the top of the
// dialog into view.
if (open && placement === 'cover') {
// Timeout is a little ugly. Suspect there might be an issue
// with a Looker component where the last row is scrolled
// into view. Normally not an issue because outside of embed
// as the dialog is limited to the viewport.
// Make timeout configurable?
window.setTimeout(() => {
element.scrollIntoView(true)
}, 200)
}
}
}
}

/**
* Render a dashboard. When active this sets up listeners
* for events that can be sent by the embedded Looker UI.
Expand All @@ -307,7 +363,7 @@ const renderDashboard = (
recoverableError?: boolean
) => {
if (runtimeConfig.showDashboard) {
const { dashboardId } = runtimeConfig
const { dashboardId, useDynamicHeights } = runtimeConfig
document.querySelector<HTMLDivElement>('#demo-dashboard')!.style.display =
''
addEmbedFrame(
Expand All @@ -334,6 +390,8 @@ const renderDashboard = (
.on('page:properties:changed', (event: PagePropertiesChangedEvent) => {
pagePropertiesChangedHandler(event, 'dashboard')
})
// Listen to messages that can scroll drilling dialog into view
.on('env:client:dialog', envClientDialogEventListener)
// Listen to messages to prevent the user from navigating away
.on('drillmenu:click', embedEventListener)
.on('drillmodal:explore', embedEventListener)
Expand Down
2 changes: 1 addition & 1 deletion docs/assets/search.js

Large diffs are not rendered by default.

86 changes: 55 additions & 31 deletions docs/classes/EmbedBuilder.html

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions docs/classes/EmbedClient.html

Large diffs are not rendered by default.

Loading

0 comments on commit 93051a2

Please sign in to comment.