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

Iframes#978 #984

Merged
merged 21 commits into from
Oct 15, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
e80f303
C:/Program Files/Git/igs/1 will render just the projects of a group i…
ddfridley Jun 26, 2023
d72bfce
projects work in iframes, iframe-resizer works, CACHES is disabled in…
ddfridley Jul 8, 2023
7fd27ac
merged with master - kept changes to settings.py
ddfridley Aug 3, 2023
b6ac4d7
Merge remote-tracking branch 'origin/master' into iframes#978
ddfridley Aug 8, 2023
a00120f
move test helper files to tools dir
ddfridley Aug 11, 2023
ad58ed0
factor isWithinIframe and change path to /inframe
ddfridley Aug 11, 2023
c1d3b23
spelling of suppressHeader
ddfridley Aug 11, 2023
b7936b1
IframeResizerInParent React Component factored
ddfridley Aug 11, 2023
0be2a23
Merge remote-tracking branch 'origin/master' into iframes#978
ddfridley Aug 14, 2023
017db42
Merge branch 'master' into iframes#978
marlonkeating Sep 4, 2023
fe1040e
urlHelper instead of window.location
ddfridley Sep 5, 2023
c2ca756
util/iframe.js is collection of iframe utilities
ddfridley Sep 5, 2023
dab7704
removed redundant IFrameProjectController and ...Display
ddfridley Sep 5, 2023
c3fafd2
fix size of projecs in iframes on small and large
ddfridley Sep 15, 2023
53cdb7f
.override-breakpoint-max-width
ddfridley Sep 16, 2023
3f69856
Merge branch 'master' into iframes#978
marlonkeating Sep 17, 2023
6d7500f
base target in iframed project
ddfridley Sep 26, 2023
d86ec50
Merge remote-tracking branch 'origin/master' into iframes#978
ddfridley Sep 26, 2023
7515ce6
Merge remote-tracking branch 'origin/iframes#978' into iframes#978
ddfridley Sep 26, 2023
399640b
login return to non iframed about project
ddfridley Sep 28, 2023
1c598ff
Unit test for project iframe redirect
marlonkeating Oct 8, 2023
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
57 changes: 57 additions & 0 deletions common/components/common/IframeResizerInParent.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import React from "react";
import isWithinIframe from "../utils/isWithinIframe.js";

// This is a wrapper component that works with https://www.npmjs.com/package/iframe-resizer
// to allow the components detect that they are within an iframe who's parent is running iframeresizer
// so they can change styles and act differently

export default class IframeResizerInParent extends React.Component {
// iframe-resizer will create window.iFrameResizer
// this code adds additional properties to that

static inParent(){ // true if component is running in an iframe and the parent is running iframe-resizer
return window.iFrameResizer?.inParent;
}

static onInParent(cb){ // when iframe-resizer is started by the parent, execute the call back so the component can rerender or something
// iframe-resizer will create window.iFrameResize when it runs, but it may not exist yet
if(!window.iFrameResizer) window.iFrameResizer={};
if(!window.iFrameResizer.onInParent) window.iFrameResizer.onInParent=[];
window.iFrameResizer.onInParent.push(cb);
}

static _detected(...args){
// we are running in an iframe, and the parent has just started iframe-resizer
if(!args[0].data.includes('[iFrameSizer]')) return;
if(!window.iFrameResizer) window.iFrameResizer={};
window.iFrameResizer.inParent=true;
/*There could be multiple components needing to be re-rendered after iframe resizer starts, each will add a callback
* to the array: onInParent.
* Components may need to rerender after iframeresizer starts because their style needs to change if rendering
* in an iframe that resizes v. rendering in an iframe that doesn't resize.
*/
let func;
while((func=window.iFrameResizer.onInParent?.shift()))
func();
window.removeEventListener('message',IframeResizerInParent._detected);
}

componentDidMount(){
// if page is not running within an iframe, this component is just a pass through
if (window && document && isWithinIframe()) {
// we are running within an iframe
// allow parent frame to determine background color
document.getElementsByTagName('body')[0].style.backgroundColor='transparent';
// be prepared to detect IframeResizer has been run in parent window
window.addEventListener('message', IframeResizerInParent._detected);
// add the child side (in the iframe) javascript code to this page -- it's only relevant if running within an iframe
const script = document.createElement('script');
const body = document.getElementsByTagName('body')[0];
script.src = '/static/iframeResizer.contentWindow.min.js';
body.appendChild(script);
}
}
render(): Array<React$Node> {
return this.props.children;
}
}
2 changes: 1 addition & 1 deletion common/components/common/groups/IframeGroupDisplay.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class IframeGroupDisplay extends AboutGroupDisplay<Props, State> {
<div className="row">
<ProjectCardsContainer
showSearchControls={false}
supressHeader={true}
suppressHeader={true}
staticHeaderText=""
fullWidth={true}
selectableCards={false}
Expand Down
3 changes: 2 additions & 1 deletion common/components/common/projects/AboutProjectDisplay.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import Tabs from "react-bootstrap/Tabs";
import Tab from "react-bootstrap/Tab";
import Button from "react-bootstrap/Button";
import AllowMarkdown from "../richtext/AllowMarkdown.jsx";
import isWithinIframe from "../../utils/isWithinIframe.js";

type Props = {|
project: ?ProjectDetailsAPIData,
Expand Down Expand Up @@ -108,7 +109,7 @@ class AboutProjectDisplay extends React.PureComponent<Props, State> {

render(): React$Node {
const project = this.state.project;
const widthModifier=window.parent !== window ? ' use-parent-width' : '';
const widthModifier=isWithinIframe() ? ' use-parent-width' : '';
return (
<div className={"container Profile-root" + widthModifier }>
{this._renderHeader(project)}
Expand Down
15 changes: 5 additions & 10 deletions common/components/componentsBySection/FindProjects/ProjectCard.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import FavoriteToggle from "./FavoriteToggle.jsx";
import CurrentUser from "../../utils/CurrentUser.js";
import type { Dictionary } from "../../types/Generics.jsx";
import JoinConferenceButton from "../../common/event_projects/JoinConferenceButton.jsx";
import isWithinIframe from "../../utils/isWithinIframe";
import IframeResizerInParent from "../../common/IframeResizerInParent.jsx";

type Props = {|
project: ProjectData,
Expand All @@ -39,10 +41,7 @@ class ProjectCard extends React.PureComponent<Props, State> {
}),
showModal: false,
};
// iFrameResizer will startup after the page is rendered, so we need to rerener if it does
if(!window.iFrameResizer) window.iFrameResizer={}
if(!window.iFrameResizer.onInParent) window.iFrameResizer.onInParent=[]
window.iFrameResizer.onInParent.push(this.onIframeResizer.bind(this))
IframeResizerInParent.onInParent(this.forceUpdate.bind(this));
}

onClickShowVideo(event: SyntheticMouseEvent): void {
Expand All @@ -56,12 +55,8 @@ class ProjectCard extends React.PureComponent<Props, State> {
this.forceUpdate();
}

onIframeResizer(){
this.forceUpdate()
}

render(): React$Node {
const url: string = (window.location.pathname.includes('igs') && window.iFrameResizer?.inParent) ? '/ips/'+this.props.project.id :
const url: string = (window.location.pathname.includes('/groups/inframe/') && IframeResizerInParent.inParent()) ? '/projects/inframe/'+this.props.project.id :
(this.props.project.cardUrl ||
urlHelper.section(Section.AboutProject, {
id: this.props.project.slug || this.props.project.id,
Expand All @@ -76,7 +71,7 @@ class ProjectCard extends React.PureComponent<Props, State> {
videoTitle={this.props.project.name}
/>
)}
<a href={url} rel="noopener noreferrer" target={window.parent !== window && !window?.iFrameResizer?.inParent ? 'blank' : ''}>
<a href={url} rel="noopener noreferrer" target={isWithinIframe() && !IframeResizerInParent.inParent() ? 'blank' : ''}>
{this._renderLogo()}
{this._renderSubInfo()}
{this._renderTitleAndIssue()}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ type Props = {|
selectableCards: ?boolean,
alreadySelectedProjects: ?List<string>, // todo: proper state management
handleEmptyProject: ?Function,
suppressHeader: ?boolean,
|};

type State = {|
Expand Down Expand Up @@ -71,7 +72,7 @@ class ProjectCardsContainer extends React.Component<Props, State> {
</React.Fragment>
) : null}
<div className="row">
{!_.isEmpty(this.state.projects) && !this.props.supressHeader && (
{!_.isEmpty(this.state.projects) && !this.props.suppressHeader && (
<h3 className="ProjectCardContainer-header">
{this._renderCardHeaderText()}
</h3>
Expand Down
64 changes: 22 additions & 42 deletions common/components/controllers/MainController.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import SponsorFooter from "../chrome/SponsorFooter.jsx";
import SiteFooter from "../chrome/SiteFooter.jsx";
import url from "../../components/utils/url.js";
import { loadHeap } from "../utils/heapApi.js";
import IframeResizerInParent from "../common/IframeResizerInParent.jsx";

type State = {|
headerHeight: number,
Expand All @@ -22,35 +23,12 @@ class MainController extends React.Component<{||}, State> {
headerHeight: 0,
currentSection: null,
};
}

static iframeresizerInParent(...args){
// if we are running within an iframe, and the parent is running IframeResizer then do this
if(!args[0].data.includes('[iFrameSizer]')) return
// let the height go auto rather than being the height defined by the parent - since we will be resizing
const style = document.createElement("style")
style.textContent = ".Profile-primary-container.frame-full .AboutGroup-card-container .row .ProjectCardContainer .row {height: auto!important;}"
document.head.appendChild(style)
if(!window.iFrameResizer) window.iFrameResizer={}
window.iFrameResizer.inParent=true
let func
while((func=window.iFrameResizer.onInParent?.shift()))
func();
window.removeEventListener('message',MainController.iframeresizerInParent)
}

componentDidMount(){
if (window && document && window.parent !== window) {
// we are running within an iframe
// allow parent frame to determine background color
document.getElementsByTagName('body')[0].style.backgroundColor='transparent'
// check for IframeResizer in parent window
window.addEventListener('message', MainController.iframeresizerInParent)
const script = document.createElement('script')
const body = document.getElementsByTagName('body')[0]
script.src = '/static/iframeResizer.contentWindow.min.js'
body.appendChild(script)
}
IframeResizerInParent.onInParent(()=>{
//if we are running within a resizable iframe, let the height go auto rather than being the height defined by the parent - since we will be resizing
const style = document.createElement("style");
style.textContent = ".Profile-primary-container.frame-full .AboutGroup-card-container .row .ProjectCardContainer .row {height: auto!important;}";
document.head.appendChild(style);
})
}

componentWillMount(): void {
Expand Down Expand Up @@ -78,19 +56,21 @@ class MainController extends React.Component<{||}, State> {
}

render(): Array<React$Node> {
const ShowHeadAndFoot=!(window.location.pathname.includes('igs')||window.location.pathname.includes('ips'))
return <>
{ShowHeadAndFoot && <MainHeader
key="main_header"
onMainHeaderHeightChange={this._mainHeaderHeightChange.bind(this)}
/>}
<SectionController
key="section_controller"
headerHeight={this.state.headerHeight}
/>
{ShowHeadAndFoot && <SponsorFooter key="sponsor_footer" />}
{ShowHeadAndFoot && <SiteFooter key="site_footer" />}
</>
const ShowHeadAndFoot=!(window.location.pathname.includes('/groups/inframe')||window.location.pathname.includes('/projects/inframe'));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This statement is used a couple times, so should live in a helper method. Can also use helper methods to implement the logic.

Suggested change
const ShowHeadAndFoot=!(window.location.pathname.includes('/groups/inframe')||window.location.pathname.includes('/projects/inframe'));
const ShowHeadAndFoot=!(urlHelper.atSection(Section.IframeProject)||urlHelper.atSection(Section.IframeGroup));

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made the change to urlHelper both in this file and in ProjectCard. - but the code is different between the two and a common helper method wasn't possible. Plus both places read well with the urlHelper change. The ProjectCard code looks like this now:

    const url: string = (urlHelper.atSection(Section.IframeGroup) && IframeResizerInParent.inParent()) ? 
        urlHelper.section(Section.IframeProject, {
          id: this.props.project.slug || this.props.project.id,
        }) :
        ( this.props.project.cardUrl ||
          urlHelper.section(Section.AboutProject, {
            id: this.props.project.slug || this.props.project.id,
        })
      );

return (
<IframeResizerInParent>
{ShowHeadAndFoot && <MainHeader
key="main_header"
onMainHeaderHeightChange={this._mainHeaderHeightChange.bind(this)}
/>}
<SectionController
key="section_controller"
headerHeight={this.state.headerHeight}
/>
{ShowHeadAndFoot && <SponsorFooter key="sponsor_footer" />}
{ShowHeadAndFoot && <SiteFooter key="site_footer" />}
</IframeResizerInParent>
)
}
}

Expand Down
6 changes: 3 additions & 3 deletions common/components/urls/urls_v2.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
{"name": "CreateEventProject", "pattern": "events/(?P<event_id>[\\w\\-]+)/projects/create/(?P<project_id>[\\w\\-]*)"},
{"name": "AboutEventProject", "pattern": "events/(?P<event_id>[\\w\\-]+)/projects/(?P<project_id>[\\w\\-]+)"},
{"name": "CreateProject", "pattern": "projects/create/(?P<id>[\\w\\-]*)"},
{"name": "IframeProject", "pattern": "ips/(?P<id>[\\w\\-]+)"},
{"name": "IframeProject", "pattern": "projects/inframe/(?P<id>[\\w\\-]+)"},
{"name": "AboutProject", "pattern": "projects/(?P<id>[\\w\\-]+)"},
{"name": "FindProjects", "pattern": "projects"},
{"name": "Companies", "pattern": "companies"},
Expand All @@ -21,6 +21,7 @@
{"name": "Terms", "pattern": "about/terms"},
{"name": "AboutUs", "pattern": "about"},
{"name": "Home", "pattern": "^$"},
{"name": "IframeGroup", "pattern": "groups/inframe/(?P<id>[\\w\\-]+)"},
{"name": "CreateGroup", "pattern": "groups/create/(?P<id>[\\w\\-]*)"},
{"name": "AboutGroup", "pattern": "groups/(?P<id>[\\w\\-]+)"},
{"name": "FindGroups", "pattern": "groups"},
Expand All @@ -36,6 +37,5 @@
{"name": "Donate", "pattern": "donate"},
{"name": "ContactUs", "pattern": "contact"},
{"name": "VideoOverview", "pattern": "videos/(?P<id>[\\w\\-]*)"},
{"name": "Error", "pattern": "error"},
{"name": "IframeGroup", "pattern": "igs/(?P<id>[\\w\\-]+)"}
{"name": "Error", "pattern": "error"}
]
4 changes: 4 additions & 0 deletions common/components/utils/isWithinIframe.js
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I'd rename this file as just iframe.js so we can have a util file dedicated to iframes.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// true if component is rendering within an iframe of another web page
export default function isWithinIframe(){
return window?.parent !== window
}
26 changes: 0 additions & 26 deletions iframe-test-resizer.html
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a demonstration file for clients wanting to embed projects/groups? If so, add comments.
Also, these html files should probably live in a separate folder.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These two files are intended for use be developers in unit testing. I couldn't figure out a directory for putting them in. But they should be retained for future testing of this feature. And I will put comments in them explaining that's what the are for.

But how would you feel about a tools directory at the top level and I would put them in there. It's doesn't feel right to call it a test directory because that kind of implies automated tests. Just leaving them at the root isn't very clean. Or I am open to other ideas.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I created a tools directory and put them there.

This file was deleted.

20 changes: 0 additions & 20 deletions iframe-test.html

This file was deleted.

36 changes: 36 additions & 0 deletions tools/iframe-test-resizer.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<!-- this file can be loaded directly from a browser and used to test embedding groups and projects within iframes
see https://github.com/DemocracyLab/CivicTechExchange/issues/978
change groups/inframe to projects/inframe below to test embedding a project
This if for testing with iframe resizer
-->
<!DOCTYPE html>
<html lang="en" style="height: 100%">
<head>
<meta charset="utf-8" />
PeterBreen marked this conversation as resolved.
Show resolved Hide resolved
<title>iframe test</title>
</head>
<body>
<div style="width: 100vw;">
<h1>This is a landing page with Group projects:</h1>
<style>
iframe {
width: 1px;
min-width: 100%;
PeterBreen marked this conversation as resolved.
Show resolved Hide resolved
}
</style>
<script src="http://localhost:8000/static/iframeResizer.min.js"></script>
<iframe id="democracylab-groups"
src="http://localhost:8000/groups/inframe/1"
></iframe>
<h1>This is below the group</h1>
<h1>This is a project</h1>
<iframe id="democracylab-project"
src="http://localhost:8000/projects/inframe/1"
></iframe>
<h1>This is below the project. </h1>
<script>
iFrameResize({ log: true }, '#democracylab-groups,#democracylab-project')
</script>
</div>
</body>
</html>
32 changes: 32 additions & 0 deletions tools/iframe-test.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<!-- this file can be loaded directly from a browser and used to test embedding groups and projects within iframes
see https://github.com/DemocracyLab/CivicTechExchange/issues/978
change groups/inframe to projects/inframe below to test embedding a project
This if for testing without using iframe resizer
-->
<!DOCTYPE html>
<html lang="en" style="height: 100%">
<head>
<meta charset="utf-8" />
PeterBreen marked this conversation as resolved.
Show resolved Hide resolved
<title>iframe test</title>
</head>
<body>
<div style="width: 100vw;">
PeterBreen marked this conversation as resolved.
Show resolved Hide resolved
<h1>This is a landing page with Group projects:</h1>
<!-- see https://www.w3schools.com/howto/howto_css_responsive_iframes.asp -->
<div style="position: relative; overflow: hidden; width: auto; padding-top: 40%; margin-left: 5rem; margin-right: 5rem; border-radius:1rem; border: 1px solid black;">
<iframe
src="http://localhost:8000/groups/inframe/1"
style="position: absolute; top: 0; left: 0; bottom: 0; right: 0; width: 100%; height: 100%; border: none; border-radius: 1rem;"
></iframe>
</div>
<h1>This is the footer below the projects</h1>
<h1>This is a project</h1>
<div style="position: relative; overflow: hidden; width: auto; padding-top: 40%; margin-left: 5rem; margin-right: 5rem; border-radius:1rem; border: 1px solid black;">
<iframe
src="http://localhost:8000/projects/inframe/1"
style="position: absolute; top: 0; left: 0; bottom: 0; right: 0; width: 100%; height: 100%; border: none; border-radius: 1rem;"
></iframe>
</div>
</div>
</body>
</html>