-
Notifications
You must be signed in to change notification settings - Fork 127
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
A proposal for HTML Widgets #87
base: main
Are you sure you want to change the base?
Changes from all commits
d7802dc
69f806e
67637b1
9669d00
0af5aae
09fd137
ab726bf
ca05b10
ede1e3c
31d3248
6702aca
a05e359
91e277c
0ccb233
5e2ef15
6e34518
201a5da
7a9e246
2e7058c
ff513fd
979897b
2fe59c1
cf281d9
8c73b48
02cf582
2059d22
71338b6
675746d
2379ab3
2e399bb
1ce5867
a396f8a
6df5777
b703670
5cbafb0
000bbad
c380bfb
e474a58
8f12b00
cfba64b
dcfe7ad
d26d4a2
d7b1ba9
80c2606
1c840a2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
// An auto-scaling iframe | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. FWIW I used iFrame Resizer in Distill and was quite happy with it: http://davidjbradshaw.github.io/iframe-resizer/ There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was unaware of iFrame Resizer. This looks awesome! I will give it a try. Thanks! |
||
// This object emits the following events: | ||
// - initialize: when the iframe is ready to load a document | ||
// - clear: when the iframe sources are removed | ||
// - load: this is the same event as the iframe | ||
// - resize: this event fires when the auto-scaling has finished | ||
// | ||
// TODO crosstalk support | ||
// setters/getters for width/height | ||
if (customElements) {customElements.define('autoscaling-iframe', | ||
class extends HTMLElement { | ||
constructor() { | ||
super(); // compulsory | ||
let shadowRoot = this.attachShadow({mode: 'open'}); | ||
// Populate the shadow DOM: | ||
shadowRoot.innerHTML = ` | ||
<style> | ||
:host { | ||
break-inside: avoid; | ||
display: block; | ||
position: relative; | ||
overflow: hidden; | ||
} | ||
iframe { | ||
transform-origin: top left; | ||
position: absolute; | ||
top: 0; | ||
left: 0; | ||
} | ||
</style> | ||
<iframe frameborder="0"> | ||
</iframe> | ||
`; | ||
let iframe = shadowRoot.querySelector('iframe'); | ||
|
||
// the first load event throws the initialize event | ||
iframe.addEventListener( | ||
'load', | ||
() => this.dispatchEvent(new Event('initialize')), | ||
{once: true} | ||
); | ||
|
||
this.initialized = new Promise(resolve => { | ||
if (this.hasAttribute('initialized')) { | ||
resolve(this); | ||
} else { | ||
this.addEventListener('initialize', e => { | ||
this.setAttribute('initialized', ''); | ||
resolve(e.currentTarget); | ||
}); | ||
} | ||
}); | ||
} | ||
|
||
connectedCallback() { | ||
// Be aware that the connectedCallback() function can be called multiple times, | ||
// see https://developer.mozilla.org/docs/Web/Web_Components/Using_custom_elements#Using_the_lifecycle_callbacks | ||
this.ready = new Promise($ => this.addEventListener('resize', e => $(e.currentTarget), {once: true})); | ||
return this.initialized.then(() => this.clear()) | ||
.then(() => this.loadSource()) | ||
.then(() => this.resize()); | ||
} | ||
|
||
clear() { | ||
let iframe = this.shadowRoot.querySelector('iframe'); | ||
|
||
const clearSource = (attr) => { | ||
let pr; | ||
if (iframe.hasAttribute(attr)) { | ||
pr = new Promise($ => iframe.addEventListener('load', e => $(e.currentTarget), {once: true, capture: true})); | ||
iframe.removeAttribute(attr); | ||
} else { | ||
pr = Promise.resolve(this); | ||
} | ||
return pr; | ||
}; | ||
|
||
// clear srcdoc first (important) | ||
let res = clearSource('srcdoc').then(() => clearSource('src')); | ||
res.then(() => this.dispatchEvent(new Event('clear'))); | ||
return res; | ||
} | ||
|
||
loadSource() { | ||
let iframe = this.shadowRoot.querySelector('iframe'); | ||
|
||
const load = (attr) => { | ||
let pr; | ||
if (this.hasAttribute(attr)) { | ||
pr = new Promise($ => iframe.addEventListener('load', e => $(e.currentTarget), {once: true, capture: true})); | ||
iframe.setAttribute(attr, this.getAttribute(attr)); | ||
} else { | ||
pr = Promise.resolve(); | ||
} | ||
return pr; | ||
}; | ||
|
||
// load src first (important) | ||
const res = load('src').then(() => load('srcdoc')); | ||
res.then(() => this.dispatchEvent(new Event('load'))); | ||
return res; | ||
} | ||
|
||
resize() { | ||
let iframe = this.shadowRoot.querySelector('iframe'); | ||
let contentHeight, contentWidth; | ||
try { | ||
// this works only with a same-origin url | ||
// with a cross-origin url, we get an error | ||
let docEl = iframe.contentWindow.document.documentElement; | ||
contentWidth = docEl.scrollWidth; | ||
contentHeight = docEl.scrollHeight; | ||
} | ||
catch(e) { | ||
// cross-origin url: | ||
// we cannot find the size of the html page | ||
// use a default resolution | ||
contentWidth = 1024; | ||
contentHeight = 768; | ||
} | ||
finally { | ||
let widthScaleFactor = this.clientWidth / contentWidth; | ||
let heightScaleFactor = this.clientHeight / contentHeight; | ||
let scaleFactor = Math.min(widthScaleFactor, heightScaleFactor); | ||
scaleFactor = Math.floor(scaleFactor * 1e6) / 1e6; | ||
iframe.style.transform = "scale(" + scaleFactor + ")"; | ||
iframe.width = contentWidth; | ||
iframe.height = contentHeight; | ||
|
||
this.style.width = iframe.getBoundingClientRect().width + 'px'; | ||
this.style.height = iframe.getBoundingClientRect().height + 'px'; | ||
this.style.boxSizing = "content-box"; | ||
} | ||
this.dispatchEvent(new Event('resize')); | ||
return Promise.resolve(this); | ||
} | ||
} | ||
);} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's an interesting hack. Since eventually you'd need a list, perhaps we could document it that
out.extra
is expected to be a list? So that we don't need to introduce a new dependency or use a hack.I can also support list values in knitr. It is easier to go from a list to a string than the other way around, e.g., I can generate
id="foo" class="bar"
fromout.extra = list(id = "foo", class = "bar")
in knitr.BTW, what was the use case on your mind for
out.extra
? If you don't have a significant use case yet, perhaps we can support this option in the future as users request for it.