Skip to content

Commit

Permalink
Added support for calculating height of body containing absolute posi…
Browse files Browse the repository at this point in the history
…tioned elements
  • Loading branch information
kafkahw committed Mar 22, 2018
1 parent b793a6a commit 7922a4a
Show file tree
Hide file tree
Showing 6 changed files with 123 additions and 8 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ XFC.Consumer.mount(document.body, 'http://localprovider.com:8080/example/provide
| heightCalculationMethod | string | `'bodyOffset'` | Accepted values:<br> `'bodyOffset'` - use `document.body.offsetHeight`<br><br> `'bodyScroll'` - use `document.body.scrollHeight`<br><br> `'documentElementOffset'` - use `document.documentElement.offsetHeight`<br><br> `'documentElementScroll'` - use `document.documentElement.scrollHeight`<br><br> `'max'` - max of all of above options.<br><br> `'min'` - min of all of above options.|
| widthCalculationMethod | string | `'scroll'` | Accepted values:<br> `'bodyOffset'` - use `document.body.offsetWidth`<br><br> `'bodyScroll'` - use `document.body.scrollWidth`<br><br> `'documentElementOffset'` - use `document.documentElement.offsetWidth`<br><br> `'documentElementScroll'` - use `document.documentElement.scrollWidth`<br><br> `'scroll'` - max of `bodyScroll` and `documentElementScroll`<br><br> `'max'` - max of all of above options.<br><br> `'min'` - min of all of above options.|
| customCalculationMethod | function | null | When specified, XFC will use the given method to update iframe's size when necessary (e.g. dom changes, window resized, etc.)<br><br> NOTE: context `this` is provided as iframe to this method, so in the method you can access the iframe by accessing `this` |
| targetSelectors | string | null | When the embedded page contains elements styled with `position: absolute`, the iframe resizing logic won't calculate the height of the embedded page correctly because those elements are removed from normal document flow.<br><br>In this case, targetSelectors can be used to specify those absolute positioned elements so that they will be taken into consideration when calculating the height of the embedded page. Multiple selectors may be specified by separating them using commas.<br><br>If not specified, normal resizing logic will be used.<br><br> NOTE: this attribute can be also specified from Provider's side, e.g. `XFC.Provider.init({targetSelectors: '#target'})`|


### Setting Custom Attributes on Iframe
Expand Down
5 changes: 5 additions & 0 deletions example/resizing/4_a_index.html
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,11 @@ <h2>authorized app</h2>
document.body,
'http://localprovider.com:8080/example/resizing/4_a_provider.html'
);
XFC.Consumer.mount(
document.body,
'http://localprovider.com:8080/example/resizing/4_a_provider_2.html',
{ resizeConfig: { targetSelectors: '#ui-selectmenu-menu' } }
);
</script>
</body>
</html>
21 changes: 17 additions & 4 deletions example/resizing/4_a_provider.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,24 @@
<h1>Content will change after 1 second.</h1>
<p>This page simulates the situation where the embedded content changes after authorization.</p>
</div>
<div id="wrapper">
<div id="ui-selectmenu-menu" style="position: absolute;">
<ul style="width: 887px;">
<li role="option">Item 1</li>
<li role="option">Item 2</li>
<li role="option">Item 3</li>
<li role="option">Item 4</li>
<li role="option">Item 5</li>
<li role="option">Item 6</li>
<li role="option">Item 7</li>
<li role="option">Item 8</li>
</ul>
</div>
</div>
<script type="text/javascript">
XFC.Provider.init({ acls: ['http://localconsumer.com:8080' ] });
var insertedDiv = document.createElement('div');
insertedDiv.innerHTML = 'This is a long line. This is a long line. This is a long line. This is a long line. This is a long line. This is a long line. This is a long line. This is a long line. This is a long line. This is a long line. This is a long line. This is a long line. This is a long line. This is a long line. This is a long line. This is a long line. This is a long line. This is a long line. This is a long line. This is a long line. This is a long line. This is a long line. This is a long line. This is a long line. This is a long line. This is a long line. This is a long line. This is a long line. This is a long line. This is a long line. This is a long line. This is a long line. This is a long line. This is a long line. This is a long line. This is a long line. This is a long line. This is a long line. This is a long line. This is a long line. This is a long line. This is a long line. This is a long line. This is a long line. This is a long line. This is a long line. This is a long line. This is a long line. This is a long line. This is a long line. This is a long line. This is a long line. This is a long line. This is a long line. ';
setTimeout(function() { document.body.appendChild(insertedDiv); }, 1000);
XFC.Provider.init({ acls: ['http://localconsumer.com:8080'], targetSelectors: '#ui-selectmenu-menu' });
var wrapper = document.getElementById('wrapper');
setTimeout(function() { wrapper.removeChild(document.getElementById('ui-selectmenu-menu')); }, 1000);
</script>
</body>
</html>
39 changes: 39 additions & 0 deletions example/resizing/4_a_provider_2.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<!DOCTYPE html>
<html lang="en-us">
<head>
<title>HealtheLife Trusted Site</title>
<meta charset="utf-8" />
<style>
body {
padding-bottom: 5px;
}
html[hidden] { display: none; }
</style>
<script src="http://localhost:8080/xfc.js"></script>
</head>
<body>
<div>
<h1>Content will change after 2 second.</h1>
<p>This page simulates the situation where the embedded content changes after authorization.</p>
</div>
<div id="wrapper">
<div id="ui-selectmenu-menu" style="position: absolute;">
<ul style="width: 887px;">
<li role="option">Item 1</li>
<li role="option">Item 2</li>
<li role="option">Item 3</li>
<li role="option">Item 4</li>
<li role="option">Item 5</li>
<li role="option">Item 6</li>
<li role="option">Item 7</li>
<li role="option">Item 8</li>
</ul>
</div>
</div>
<script type="text/javascript">
XFC.Provider.init({ acls: ['http://localconsumer.com:8080'] });
var wrapper = document.getElementById('wrapper');
setTimeout(function() { wrapper.removeChild(document.getElementById('ui-selectmenu-menu')); }, 2000);
</script>
</body>
</html>
34 changes: 33 additions & 1 deletion src/lib/dimension.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ const getWidth = {
min: () => Math.min(...getAllMeasures(getWidth)),
};

export function calculateHeight(calMethod = 'bodyScroll') {
export function calculateHeight(calMethod = 'bodyOffset') {
if (!(calMethod in getHeight)) {
logger.error(`'${calMethod}' is not a valid method name!`);
}
Expand All @@ -62,3 +62,35 @@ export function calculateWidth(calMethod = 'scroll') {
}
return getWidth[calMethod]();
}

/**
* This function returns the offset height of the given node relative to the top of document.body
*/
export function getOffsetToBody(node, offset = 0) {
// If the given node is body or null, return 0
if (!node || node === window.document.body) {
return 0;
}

// Stops if the offset parent node is body;
// Otherwise keep searching up
// NOTE: offsetParent will return null on Webkit if the element is hidden
// (the style.display of this element or any ancestor is "none") or
// if the style.position of the element itself is set to "fixed"
// See reference at https://developer.mozilla.org/en-US/docs/Web/API/HTMLelement/offsetParent#Compatibility
const calculatedOffset = node.offsetTop + offset;
const offsetParent = node.offsetParent;

if (offsetParent === window.document.body) {
return calculatedOffset;
}

return getOffsetToBody(offsetParent, calculatedOffset);
}

/**
* This function returns the offset height of the given node relative to the top of document.body
*/
export function getOffsetHeightToBody(node) {
return !node ? 0 : getOffsetToBody(node) + node.offsetHeight;
}
31 changes: 28 additions & 3 deletions src/provider/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,26 @@ import { fixedTimeCompare } from '../lib/string';
import { EventEmitter } from 'events';
import URI from '../lib/uri';
import logger from '../lib/logger';
import { calculateHeight, calculateWidth } from '../lib/dimension';
import { getOffsetHeightToBody, calculateHeight, calculateWidth } from '../lib/dimension';
import MutationObserver from 'mutation-observer';


/** Application class which represents an embedded application. */
class Application extends EventEmitter {
init({ acls = [], secret = null, onReady = null }) {
/**
* init method
* @param options.acls An array that contains white listed origins
* @param options.secret A string or function used for authorization with Consumer
* @param options.onReady A function that will be called after App is authorized
* @param options.targetSelectors A DOMString containing one or more selectors to match against.
* This string must be a valid CSS selector string; if it's not,
* a SyntaxError exception is thrown.
*/
init({ acls = [], secret = null, onReady = null, targetSelectors = '' }) {
this.acls = [].concat(acls);
this.secret = secret;
this.onReady = onReady;
this.targetSelectors = targetSelectors;
this.resizeConfig = null;
this.requestResize = this.requestResize.bind(this);
this.handleConsumerMessage = this.handleConsumerMessage.bind(this);
Expand Down Expand Up @@ -73,7 +83,22 @@ class Application extends EventEmitter {
const width = calculateWidth(this.resizeConfig.WidthCalculationMethod);
this.JSONRPC.notification('resize', [null, `${width}px`]);
} else {
const height = calculateHeight(this.resizeConfig.heightCalculationMethod);
let height = calculateHeight(this.resizeConfig.heightCalculationMethod);

// If targetSelectors is specified from Provider or Consumer or both,
// need to calculate the height based on specified target selectors
if (this.targetSelectors || this.resizeConfig.targetSelectors) {
// Combines target selectors from two sources
const targetSelectors = [this.targetSelectors, this.resizeConfig.targetSelectors]
.filter(val => val)
.join(', ');

const heights = [].slice.call(document.querySelectorAll(targetSelectors))
.map(getOffsetHeightToBody);

height = Math.max(...heights, height);
}

this.JSONRPC.notification('resize', [`${height}px`]);
}
}
Expand Down

0 comments on commit 7922a4a

Please sign in to comment.