Skip to content

Commit 4c6c573

Browse files
committed
Merge branch 'spdavid-IFramePanel' into dev
2 parents 3962e82 + 873bb78 commit 4c6c573

16 files changed

+309
-2
lines changed
61.1 KB
Loading

docs/documentation/docs/controls/IFrameDialog.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# IFrameDialog control
22

3-
This control renders a Dialog with an iframe as a content.
3+
This control renders a Dialog with an iframe as content.
44

55
Here is an example of the control in action:
66

@@ -58,4 +58,4 @@ The IFrameDialog component can be configured with the following properties:
5858
| scrolling | string | no | Specifies whether or not to display scrollbars in an iframe |
5959
| seamless | string | no | When present, it specifies that the iframe should look like it is a part of the containing document (no borders or scrollbars) |
6060

61-
![](https://telemetry.sharepointpnp.com/sp-dev-fx-controls-react/wiki/controls/FileTypeIcon)
61+
![](https://telemetry.sharepointpnp.com/sp-dev-fx-controls-react/wiki/controls/IFrameDialog)
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# IFramePanel control
2+
3+
This control renders a Panel with an iframe as content.
4+
5+
Here is an example of the control in action:
6+
7+
![IFrameDialog control](../assets/IFramePanel.png)
8+
9+
## How to use this control in your solutions
10+
11+
- Check that you installed the `@pnp/spfx-controls-react` dependency. Check out the [getting started](../#getting-started) page for more information about installing the dependency.
12+
- Import the following modules to your component:
13+
14+
```TypeScript
15+
import { IFramePanel } from "@pnp/spfx-controls-react/lib/IFramePanel";
16+
```
17+
18+
- The `IFramePanel` uses the [Office Fabric UI implemenation of the panel](https://developer.microsoft.com/en-us/fabric#/components/panel). The properties of this control inherit the panel properties.
19+
20+
- Use the `IFramePanel` control in your code as follows (`this._onIframeLoaded` and `this._onDismiss` are methods that should be implemented if you want to execute some actions when the iframe content is loaded and dialog should be closed respectively.)
21+
22+
23+
```TypeScript
24+
<IFramePanel url={this.state.iFrameUrl}
25+
type={PanelType.medium}
26+
headerText="Panel Title"
27+
closeButtonAriaLabel="Close"
28+
isOpen={this.state.iFramePanelOpened}
29+
onDismiss={this._onDismiss.bind(this)}
30+
iframeOnLoad={this._onIframeLoaded.bind(this)} />
31+
```
32+
33+
## Implementation
34+
35+
The IFramePanel component extends the properties from the [Fabric UI IPanelProps](https://developer.microsoft.com/en-us/fabric#/components/panel)
36+
along with the additional following properties:
37+
38+
| Property | Type | Required | Description |
39+
| ---- | ---- | ---- | ---- |
40+
| url | string | yes | iframe Url |
41+
| heigth | string | yes | iframe's height, if empty it will be dynamically set to the full height available in the panel's content area |
42+
| iframeOnload | iframeOnLoad?: (iframe: any) => {} | no | iframe's onload event handler |
43+
| name | string | no | Specifies the name of an iframe |
44+
| allowFullScreen | boolean | no | Specifies if iframe content can be displayed in a full screen |
45+
| allowTransparency | boolean | no | Specifies if transparency is allowed in iframe |
46+
| sandbox | string | no | Enables an extra set of restrictions for the content in an iframe |
47+
| scrolling | string | no | Specifies whether or not to display scrollbars in an iframe |
48+
| seamless | string | no | When present, it specifies that the iframe should look like it is a part of the containing document (no borders or scrollbars) |
49+
50+
![](https://telemetry.sharepointpnp.com/sp-dev-fx-controls-react/wiki/controls/IFramePanel)

docs/documentation/mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ nav:
2525
- TaxonomyPicker: 'controls/TaxonomyPicker.md'
2626
- PeoplePicker: 'controls/PeoplePicker.md'
2727
- IFrameDialog: 'controls/IFrameDialog.md'
28+
- IFramePanel: 'controls/IFramePanel.md'
2829
- 'Field Controls':
2930
- 'Getting started': 'controls/fields/main.md'
3031
- FieldRendererHelper: 'controls/fields/FieldRendererHelper.md'

src/IFramePanel.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './controls/iFramePanel/index';
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import * as React from 'react';
2+
import { Panel } from 'office-ui-fabric-react/lib/Panel';
3+
import omit = require('lodash/omit');
4+
import { IFramePanelContent } from './IFramePanelContent';
5+
import { IIFramePanelProps, IIFramePanelState } from '.';
6+
7+
export class IFramePanel extends React.Component<IIFramePanelProps, IIFramePanelState> {
8+
9+
constructor(props: IIFramePanelProps) {
10+
super(props);
11+
12+
this.state = {};
13+
}
14+
15+
/**
16+
* Default React render
17+
*/
18+
public render(): React.ReactElement<IIFramePanelProps> {
19+
const {
20+
height,
21+
allowFullScreen,
22+
iframeOnLoad,
23+
allowTransparency,
24+
name,
25+
sandbox,
26+
scrolling,
27+
seamless
28+
} = this.props;
29+
30+
return (
31+
<Panel {...omit(this.props, 'className')} >
32+
<IFramePanelContent src={this.props.url}
33+
iframeOnLoad={iframeOnLoad}
34+
close={this.props.onDismiss}
35+
height={height}
36+
allowFullScreen={allowFullScreen}
37+
allowTransparency={allowTransparency}
38+
name={name}
39+
sandbox={sandbox}
40+
scrolling={scrolling}
41+
seamless={seamless} />
42+
</Panel>
43+
);
44+
}
45+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
2+
.iFrameDialog {
3+
border: none;
4+
5+
.spinnerContainer {
6+
position: absolute;
7+
left: 50%;
8+
top: 50%;
9+
margin-left: -14px;
10+
margin-top: -14px;
11+
}
12+
}
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import * as React from "react";
2+
import styles from './IFramePanelContent.module.scss';
3+
import { Spinner, SpinnerSize } from 'office-ui-fabric-react/lib/Spinner';
4+
import omit = require('lodash/omit');
5+
import { IIFramePanelContentProps, IIFramePanelContentState } from ".";
6+
7+
/**
8+
* IFrame Panel content
9+
*/
10+
export class IFramePanelContent extends React.Component<IIFramePanelContentProps, IIFramePanelContentState> {
11+
private _iframe: any;
12+
13+
constructor(props: IIFramePanelContentProps) {
14+
super(props);
15+
16+
this.state = {
17+
isContentVisible: false
18+
};
19+
window.onresize = this.resizeIframe;
20+
}
21+
22+
/**
23+
* Resize the iframe element
24+
*/
25+
private resizeIframe = () => {
26+
if (!this.props.height) {
27+
if (this._iframe) {
28+
const mainDiv = this.findParent(this._iframe, "ms-Panel-main");
29+
const commandsDiv = mainDiv.querySelector(".ms-Panel-commands") as HTMLDivElement;
30+
const headerDiv = mainDiv.querySelector("ms-Panel-header") as HTMLDivElement;
31+
const footerDiv = mainDiv.querySelector("ms-Panel-footer") as HTMLDivElement;
32+
33+
let height = this.getTrueHeight(mainDiv);
34+
height = height - this.getTrueHeight(commandsDiv);
35+
height = height - this.getTrueHeight(headerDiv);
36+
height = height - this.getTrueHeight(footerDiv);
37+
height = height - 20; // padding on content div
38+
39+
this._iframe.height = height.toString() + 'px';
40+
}
41+
}
42+
}
43+
44+
/**
45+
* Find the parent element
46+
*
47+
* @param elm
48+
* @param className
49+
*/
50+
private findParent(elm: HTMLElement, className: string) {
51+
while ((elm = elm.parentElement) && !elm.classList.contains(className));
52+
return elm;
53+
}
54+
55+
/**
56+
* Get the element its height
57+
*
58+
* @param elm
59+
*/
60+
private getTrueHeight(elm: HTMLElement): number {
61+
if (elm) {
62+
const style = elm.style || window.getComputedStyle(elm);
63+
let marginTop = parseInt((style.marginTop as string).replace("px", ""));
64+
let marginBottom = parseInt((style.marginTop as string).replace("px", ""));
65+
if (isNaN(marginTop)) {
66+
marginTop = 0;
67+
}
68+
if (isNaN(marginBottom)) {
69+
marginBottom = 0;
70+
}
71+
return elm.offsetHeight + marginTop + marginBottom;
72+
} else {
73+
return 0;
74+
}
75+
}
76+
77+
/**
78+
* On iframe load event
79+
*/
80+
private iframeOnLoad = () => {
81+
try { // for cross origin requests we can have issues with accessing frameElement
82+
this._iframe.contentWindow.frameElement.cancelPopUp = this.props.close;
83+
}
84+
catch (err) {
85+
if (err.name !== 'SecurityError') {
86+
throw err;
87+
}
88+
}
89+
90+
this.resizeIframe();
91+
92+
if (this.props.iframeOnLoad) {
93+
this.props.iframeOnLoad(this._iframe);
94+
}
95+
96+
this.setState({
97+
isContentVisible: true
98+
});
99+
}
100+
101+
/**
102+
* Default React render
103+
*/
104+
public render(): JSX.Element {
105+
return (
106+
<div className={styles.iFrameDialog}>
107+
<iframe ref={(iframe) => { this._iframe = iframe; }} frameBorder={0} onLoad={this.iframeOnLoad} style={{ width: '100%', height: this.props.height, visibility: this.state.isContentVisible ? 'visible' : 'hidden' }} {...omit(this.props, 'height')} />
108+
109+
{
110+
!this.state.isContentVisible && (
111+
<div className={styles.spinnerContainer}>
112+
<Spinner size={SpinnerSize.large} />
113+
</div>
114+
)
115+
}
116+
</div>
117+
);
118+
}
119+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export interface IIFramePanelContentProps extends React.IframeHTMLAttributes<HTMLIFrameElement> {
2+
close: () => void;
3+
iframeOnLoad?: (iframe: any) => void;
4+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export interface IIFramePanelContentState {
2+
isContentVisible?: boolean;
3+
}

0 commit comments

Comments
 (0)