Skip to content

Commit a1a98e6

Browse files
committed
Merge branch 'IFramePanel' of https://github.com/spdavid/sp-dev-fx-controls-react into spdavid-IFramePanel
2 parents 3962e82 + e3ca2b5 commit a1a98e6

File tree

11 files changed

+278
-0
lines changed

11 files changed

+278
-0
lines changed
61.1 KB
Loading
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# IFramePanel control
2+
3+
This control renders a Panel with an iframe as a 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
25+
url={this.state.iFrameUrl}
26+
type={PanelType.medium}
27+
headerText="Panel Title"
28+
closeButtonAriaLabel="Close"
29+
isOpen={this.state.iFramePanelOpened}
30+
onDismiss={this._onDismiss.bind(this)}
31+
iframeOnLoad={this._onIframeLoaded.bind(this)}
32+
/>
33+
```
34+
35+
## Implementation
36+
37+
The IFramePanel component extends the properties from the [Fabric UI IPanelProps](https://developer.microsoft.com/en-us/fabric#/components/panel)
38+
along with the additional following properties:
39+
40+
| Property | Type | Required | Description |
41+
| ---- | ---- | ---- | ---- |
42+
| url | string | yes | iframe Url |
43+
| heigth | string | yes | iframe's height, if empty it will be dynamically set to the full height available in the panel's content area |
44+
| iframeOnload | iframeOnLoad?: (iframe: any) => {} | no | iframe's onload event handler |
45+
| name | string | no | Specifies the name of an iframe |
46+
| allowFullScreen | boolean | no | Specifies if iframe content can be displayed in a full screen |
47+
| allowTransparency | boolean | no | Specifies if transparency is allowed in iframe |
48+
| sandbox | string | no | Enables an extra set of restrictions for the content in an iframe |
49+
| scrolling | string | no | Specifies whether or not to display scrollbars in an iframe |
50+
| 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) |
51+
52+
![](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: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import * as React from 'react';
2+
import { Guid } from "@microsoft/sp-core-library";
3+
import styles from './IFramePanelContent.module.scss';
4+
import { Panel, IPanelProps } from 'office-ui-fabric-react/lib/Panel';
5+
import omit = require('lodash/omit');
6+
import { IFramePanelContent } from './IFramePanelContent';
7+
8+
export interface IIFramePanelProps extends IPanelProps {
9+
/**
10+
* iframe Url
11+
*/
12+
url: string;
13+
/**
14+
* iframe height, if null then hight is calculated
15+
*/
16+
height?: string;
17+
/**
18+
* Specifies if iframe content can be displayed in a full screen.
19+
* Usage: <IFrameDialog allowFullScreen />
20+
*/
21+
allowFullScreen?: boolean;
22+
/**
23+
* iframe's onload event handler
24+
*/
25+
iframeOnLoad?: (iframe: any) => void;
26+
/**
27+
* Specifies if transparency is allowed in iframe
28+
*/
29+
allowTransparency?: boolean;
30+
/**
31+
* Specifies the name of an <iframe>
32+
*/
33+
name?: string;
34+
/**
35+
* Enables an extra set of restrictions for the content in an <iframe>
36+
*/
37+
sandbox?: string;
38+
/**
39+
* Specifies whether or not to display scrollbars in an <iframe>
40+
*/
41+
scrolling?: string;
42+
/**
43+
* When present, it specifies that the <iframe> should look like it is a part of the containing document (no borders or scrollbars)
44+
*/
45+
seamless?: boolean;
46+
}
47+
48+
export interface IIFramePanelState {
49+
}
50+
51+
export class IFramePanel extends React.Component<IIFramePanelProps, IIFramePanelState> {
52+
constructor(props: IIFramePanelProps) {
53+
super(props);
54+
55+
this.state = {
56+
};
57+
}
58+
59+
public render(): React.ReactElement<IIFramePanelProps> {
60+
61+
const {
62+
height,
63+
allowFullScreen,
64+
iframeOnLoad,
65+
allowTransparency,
66+
name,
67+
sandbox,
68+
scrolling,
69+
seamless
70+
} = this.props;
71+
return (
72+
<Panel
73+
{...omit(this.props, 'className')}
74+
>
75+
<IFramePanelContent src={this.props.url}
76+
iframeOnLoad={iframeOnLoad}
77+
close={this.props.onDismiss}
78+
height={height}
79+
allowFullScreen={allowFullScreen}
80+
allowTransparency={allowTransparency}
81+
name={name}
82+
sandbox={sandbox}
83+
scrolling={scrolling}
84+
seamless={seamless}
85+
/>
86+
</Panel>
87+
);
88+
}
89+
}
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: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
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+
6+
export interface IIFramePanelContentProps extends React.IframeHTMLAttributes<HTMLIFrameElement> {
7+
close: () => void;
8+
iframeOnLoad?: (iframe: any) => void;
9+
}
10+
11+
export interface IIFramePanelContentState {
12+
isContentVisible?: boolean;
13+
}
14+
15+
/**
16+
* IFrame Panel content
17+
*/
18+
export class IFramePanelContent extends React.Component<IIFramePanelContentProps, IIFramePanelContentState> {
19+
private _iframe: any;
20+
21+
constructor(props: IIFramePanelContentProps) {
22+
super(props);
23+
24+
this.state = {
25+
isContentVisible: false
26+
};
27+
window.onresize = this.resizeIframe;
28+
}
29+
30+
private resizeIframe = () => {
31+
if (!this.props.height) {
32+
if (this._iframe) {
33+
let mainDiv = this._iframe.parentElement.parentElement.parentElement.parentElement as HTMLDivElement;
34+
let commandsDiv = mainDiv.getElementsByClassName("ms-Panel-commands")[0] as HTMLDivElement;
35+
let headerDiv = mainDiv.getElementsByClassName("ms-Panel-header")[0] as HTMLDivElement;
36+
let footerDiv = mainDiv.getElementsByClassName("ms-Panel-footer")[0] as HTMLDivElement;
37+
38+
let height = this.getTrueHeight(mainDiv);
39+
height = height - this.getTrueHeight(commandsDiv);
40+
height = height - this.getTrueHeight(headerDiv);
41+
height = height - this.getTrueHeight(footerDiv);
42+
height = height - 20; // padding on content div
43+
44+
45+
this._iframe.height = height.toString() + 'px';
46+
}
47+
}
48+
}
49+
50+
private getTrueHeight(element): number {
51+
if (element) {
52+
let style = element.currentStyle || window.getComputedStyle(element);
53+
let marginTop = parseInt((style.marginTop as string).replace("px", ""));
54+
let marginBottom = parseInt((style.marginTop as string).replace("px", ""));
55+
if (isNaN(marginTop)) {
56+
marginTop = 0;
57+
}
58+
if (isNaN(marginBottom)) {
59+
marginBottom = 0;
60+
}
61+
return element.offsetHeight + marginTop + marginBottom;
62+
}
63+
else {
64+
return 0;
65+
}
66+
}
67+
68+
public render(): JSX.Element {
69+
return (<div className={styles.iFrameDialog}>
70+
<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')} />
71+
{!this.state.isContentVisible &&
72+
<div className={styles.spinnerContainer}>
73+
<Spinner size={SpinnerSize.large} />
74+
</div>}
75+
</div>);
76+
}
77+
78+
private _iframeOnLoad = () => {
79+
try { // for cross origin requests we can have issues with accessing frameElement
80+
this._iframe.contentWindow.frameElement.cancelPopUp = this.props.close;
81+
}
82+
catch (err) {
83+
if (err.name !== 'SecurityError') {
84+
throw err;
85+
}
86+
}
87+
88+
this.resizeIframe();
89+
90+
if (this.props.iframeOnLoad) {
91+
this.props.iframeOnLoad(this._iframe);
92+
}
93+
94+
this.setState({
95+
isContentVisible: true
96+
});
97+
}
98+
}

src/controls/iFramePanel/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export * from './IFramePanelContent.module.scss';
2+
export * from './IFramePanelContent';
3+
export * from './IFramePanel';

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export * from './ListItemPicker';
99
export * from './ChartControl';
1010

1111
export * from './IFrameDialog';
12+
export * from './IFramePanel';
1213
export * from './Common';
1314
export * from './Utilities';
1415
export * from './IFrameDialog';

src/webparts/controlsTest/components/ControlsTest.tsx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import { WebPartTitle } from '../../../WebPartTitle';
1414
import { TaxonomyPicker, IPickerTerms } from '../../../TaxonomyPicker';
1515
import { ListPicker } from '../../../ListPicker';
1616
import { IFrameDialog } from '../../../IFrameDialog';
17+
import { IFramePanel } from '../../../IFramePanel';
18+
import { PanelType } from 'office-ui-fabric-react/lib/Panel';
1719
import { Environment, EnvironmentType, DisplayMode } from '@microsoft/sp-core-library';
1820
import { SecurityTrimmedControl, PermissionLevel } from '../../../SecurityTrimmedControl';
1921
import { SPPermission } from '@microsoft/sp-page-context';
@@ -34,6 +36,7 @@ export default class ControlsTest extends React.Component<IControlsTestProps, IC
3436
imgSize: ImageSize.small,
3537
items: [],
3638
iFrameDialogOpened: false,
39+
iFramePanelOpened: false,
3740
initialValues: [],
3841
authorEmails: [],
3942
selectedList: null
@@ -181,6 +184,8 @@ export default class ControlsTest extends React.Component<IControlsTestProps, IC
181184
}
182185
];
183186

187+
188+
184189
// Specify the fields that need to be viewed in the listview
185190
const viewFields: IViewField[] = [
186191
{
@@ -486,6 +491,21 @@ export default class ControlsTest extends React.Component<IControlsTestProps, IC
486491
width={'570px'}
487492
height={'315px'} />
488493
</div>
494+
<div className="ms-font-m">iframe Panel tester:
495+
<PrimaryButton
496+
text="Open iframe Panel"
497+
onClick={() => { this.setState({ iFramePanelOpened: true }); }} />
498+
<IFramePanel
499+
url={iframeUrl}
500+
type={PanelType.medium}
501+
// height="300px"
502+
headerText="iframe panel title"
503+
closeButtonAriaLabel="Close"
504+
isOpen={this.state.iFramePanelOpened}
505+
onDismiss={() => { this.setState({ iFramePanelOpened: false }); }}
506+
iframeOnLoad={(iframe: any) => { console.log('iframe loaded'); }}
507+
/>
508+
</div>
489509
</div>
490510
</div>
491511
</div>

0 commit comments

Comments
 (0)