Skip to content
This repository has been archived by the owner on Feb 13, 2019. It is now read-only.

<script is="lazy-template"> (ready for review) #165

Merged
merged 6 commits into from Sep 30, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
37 changes: 37 additions & 0 deletions elements/lazy-template/lazy-template-examples.js
@@ -0,0 +1,37 @@
export default {
element: 'lazy-template',
examples: {
'Load On Page Load': {
render () {
return `
<script type="text/html" is="lazy-template" load-on="page-load">
<h1>Damn Son, I Am Lazy Loaded</h1>
<img src="http://www.fillmurray.com/g/200/300" alt="Billy Murray">
</script>
`;
},
},
'Load On In View': {
render () {
return `
<div
style="
overflow: auto;
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
"
>
<div style="height: 200%">SCROLL DOWN TO LOAD STUFF</div>
<script type="text/html" is="lazy-template" load-on="in-view">
<h1>Damn Son, I Am Lazy Loaded</h1>
<img src="http://www.fillmurray.com/g/200/300" alt="Billy Murray">
</script>
</div>
`;
},
},
},
};
68 changes: 68 additions & 0 deletions elements/lazy-template/lazy-template.js
@@ -0,0 +1,68 @@
import { registerElement } from 'bulbs-elements/register';
import { InViewMonitor } from 'bulbs-elements/util';
import invariant from 'invariant';
import './lazy-template.scss';

function BulbsHTMLScriptElement () {}
BulbsHTMLScriptElement.prototype = HTMLScriptElement.prototype;

class LazyTemplate extends BulbsHTMLScriptElement {
get loadOn () {
return this.getAttribute('load-on');
}

attachedCallback () {
invariant(this.hasAttribute('load-on'),
'<script is="lazy-template"> MUST specify a "load-on" attribute (either "page-load" or "in-view").');

invariant(this.getAttribute('type') === 'text/html',
'<script is="lazy-template"> MUST set the attribute type="text/html".');

if (this.loadOn === 'page-load') {
this.setupLoadOnPageLoad();
}
else if (this.loadOn === 'in-view') {
this.setUpLoadOnInView();
}

this.replaceWithContents = this.replaceWithContents.bind(this);
this.handleEnterViewport = this.handleEnterViewport.bind(this);
}

detachedCallback () {
if (this.loadOn === 'in-view') {
this.tearDownLoadOnInView();
}
}

setupLoadOnPageLoad () {
if (document.readyState === 'complete') {
this.replaceWithContents();
}
else {
window.addEventListener('load', () => this.replaceWithContents());
}
}

setUpLoadOnInView () {
InViewMonitor.add(this);
this.addEventListener('enterviewport', this.handleEnterViewport);
}

handleEnterViewport () {
InViewMonitor.remove(this);
this.replaceWithContents();
}

tearDownLoadOnInView () {
InViewMonitor.remove(this);
}

replaceWithContents () {
this.outerHTML = this.textContent;
}
}

LazyTemplate.extends = 'script';

registerElement('lazy-template', LazyTemplate);
11 changes: 11 additions & 0 deletions elements/lazy-template/lazy-template.scss
@@ -0,0 +1,11 @@
// we're using getBoundingClientRect to determine when our script
// is in-view. Typically scripts are not part of the render tree
// and their client rect is not meaningful. We can force them to be
// an invisible 0x0 block element like this. Then we can meaninfully
// track their position.
script[is="lazy-template"] {
display: block;
width: 0px;
height: 0px;
overflow: hidden;
}
97 changes: 97 additions & 0 deletions elements/lazy-template/lazy-template.test.js
@@ -0,0 +1,97 @@
import './lazy-template';

describe.only('<script is="lazy-template">', () => {
let sandbox;
let subject;
let container;

beforeEach(() => {
sandbox = sinon.sandbox.create();
subject = makeTemplate(`
<h1>Cool Template</h1>
<p>It is lazy!</p>
`);
container = document.createElement('div');
document.body.appendChild(container);
});

afterEach(() => {
sandbox.restore();
container.remove();
});

function makeTemplate (innerHTML) {
let template = document.createElement('script', 'lazy-template');
template.setAttribute('type', 'text/html');
template.setAttribute('load-on', 'page-load');
template.innerHTML = innerHTML;
return template;
}

it('requires a load-on attribute', () => {
subject.removeAttribute('load-on');
expect(() => {
subject.attachedCallback();
}).to.throw('<script is="lazy-template"> MUST specify a "load-on" attribute (either "page-load" or "in-view").');
});

it('requires a type set to "text/html"', () => {
subject.setAttribute('type', 'text/whatever');
expect(() => {
subject.attachedCallback();
}).to.throw('<script is="lazy-template"> MUST set the attribute type="text/html".');
});

it('requires a type attribute', () => {
subject.removeAttribute('type');
expect(() => {
subject.attachedCallback();
}).to.throw('<script is="lazy-template"> MUST set the attribute type="text/html".');
});

describe('replaceWithContents', () => {
it('replaces itself with the content html', () => {
container = document.createElement('div');
container.appendChild(subject);
subject.replaceWithContents();

expect(container.children[0].outerHTML).to.eql('<h1>Cool Template</h1>');
expect(container.children[1].outerHTML).to.eql('<p>It is lazy!</p>');
});
});

context('load-on="page-load"', () => {
beforeEach(() => subject.setAttribute('load-on', 'page-load'));

xit('sets content when page load event fires', () => {
// document.readyState is 'complete' by the time this test starts
// and it can't be overwritten, not sure how to test this
});

it('sets content immediately if page load event has fired', (done) => {
container.appendChild(subject);

setImmediate(() => {
expect(container.children[0].outerHTML).to.eql('<h1>Cool Template</h1>');
expect(container.children[1].outerHTML).to.eql('<p>It is lazy!</p>');
done();
});
});
});

context('load-on="in-view"', () => {
beforeEach(() => subject.setAttribute('load-on', 'in-view'));

it('sets content when enterviewport event fires', (done) => {
container.appendChild(subject);

setImmediate(() => {
expect(container.children[0].tagName).to.eql('SCRIPT');
subject.dispatchEvent(new CustomEvent('enterviewport'));
expect(container.children[0].outerHTML).to.eql('<h1>Cool Template</h1>');
expect(container.children[1].outerHTML).to.eql('<p>It is lazy!</p>');
done();
});
});
});
});