Skip to content

Commit

Permalink
Merge pull request #4971 from Polymer/html-template-fn
Browse files Browse the repository at this point in the history
`html` tag function for generating templates
  • Loading branch information
dfreedm authored Dec 7, 2017
2 parents f6cc61b + 8a1c76c commit 4915b0a
Show file tree
Hide file tree
Showing 7 changed files with 276 additions and 1 deletion.
3 changes: 3 additions & 0 deletions lib/mixins/element-mixin.html
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,9 @@
let template = /** @type {PolymerElementConstructor} */ (klass).template;
if (template) {
if (typeof template === 'string') {
// TODO(dan): Add link to docs explaining deprecation of string templates
console.warn('String templates are deprecated. ' +
`Please return a <template>, or use Polymer.html\`\` for element <${klass.is}>`);
let t = document.createElement('template');
t.innerHTML = template;
template = t;
Expand Down
63 changes: 63 additions & 0 deletions lib/utils/html-tag.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<!--
@license
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<link rel="import" href="boot.html">
<script>
(function() {
'use strict';

/**
* @param {*} value Object to stringify into HTML
* @return {string} HTML stringified form of `obj`
*/
function htmlValue(value) {
if (value instanceof HTMLTemplateElement) {
return /** @type {!HTMLTemplateElement} */(value).innerHTML;
} else {
return String(value);
}
}

/**
* A template literal tag that creates an HTML <template> element from the contents of the string.
*
* This allows you to write a Polymer Template in JavaScript.
*
* Interpolated values are converted to strings when the template is created,
* they are not intended as a replacement for Polymer data-binding.
*
* There is special support for HTMLTemplateElement values,
* allowing for easy composition of superclass or partial templates.
*
* Example:
*
* static get template() {
* return Polymer.html`
* <style>:host{ content:"..." }</style>
* <div class="shadowed">${this.partialTemplate}</div>
* ${super.template}
* `;
* }
* static get partialTemplate() { return Polymer.html`<span>Partial!</span>`; }
*
* @memberof Polymer
* @param {!Array<string>} strings Constant parts of tagged template literal
* @param {...*} values Variable parts of tagged template literal
* @return {!HTMLTemplateElement} Constructed HTMLTemplateElement
*/
Polymer.html = function html(strings, ...values) {
// use raw strings to preserve literal escapes in strings
const rawStrings = strings.raw;
const template = /** @type {!HTMLTemplateElement} */(document.createElement('template'));
template.innerHTML = values.reduce((acc, v, idx) =>
acc + htmlValue(v) + rawStrings[idx + 1], rawStrings[0]);
return template;
};
})();
</script>
5 changes: 5 additions & 0 deletions polymer-element.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<link rel="import" href="lib/mixins/element-mixin.html">
<!-- import html-tag to export html -->
<link rel="import" href="lib/utils/html-tag.html">
<script>
(function() {
'use strict';
Expand Down Expand Up @@ -35,5 +37,8 @@
* @extends {HTMLElement}
*/
Polymer.Element = Element;

// NOTE: this is here for modulizer to export `html` for the module version of this file
Polymer.html = Polymer.html;
})();
</script>
5 changes: 5 additions & 0 deletions polymer.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,12 @@
<link rel="import" href="lib/elements/custom-style.html">
<!-- bc behaviors -->
<link rel="import" href="lib/legacy/mutable-data-behavior.html">
<!-- import html-tag to export html -->
<link rel="import" href="lib/util/html-tag.html">
<script>
// bc
Polymer.Base = Polymer.LegacyElementMixin(HTMLElement).prototype;

// NOTE: this is here for modulizer to export `html` for the module version of this file
Polymer.html = Polymer.html;
</script>
3 changes: 2 additions & 1 deletion test/runner.html
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
'unit/render-status.html',
'unit/dir.html',
'unit/shady-unscoped-style.html',
'unit/html-tag.html'
// 'unit/multi-style.html'
];

Expand Down Expand Up @@ -115,7 +116,7 @@
// ce + sd becomes a single test iteration.
matrix.push('wc-ce=true&wc-shadydom=true');
}

// economize testing by testing css shimming
// only against 1 environment (native or polyfill).
if (window.CSS && CSS.supports && CSS.supports('box-shadow', '0 0 0 var(--foo)')) {
Expand Down
64 changes: 64 additions & 0 deletions test/smoke/html-tag.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<!DOCTYPE html>
<!--
@license
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<html>
<head>
<script src="../../../webcomponentsjs/webcomponents-lite.js"></script>
<link rel="import" href="../../polymer-element.html">
</head>
<body>
<script>
const html = Polymer.html;
class SuperClass extends Polymer.Element {
static get is() {return 'super-class';}
static get template() {
return html`
<style>#name {color: red}</style>
<h3 id="name">${this.is}</h3>
<div>${this.headerTemplate}</div>
[[myProp.stuffThatGoesHere]]
<div>${this.footerTemplate}</div>
`;
}
static get headerTemplate() { return html`<h1>Header</h1>`; }
static get footerTemplate() { return html`<h1>Footer</h1>`; }
}
customElements.define(SuperClass.is, SuperClass);
class SubClass extends SuperClass {
static get is() {return 'sub-class';}
static get template() {
return html`
<style>.frame {font-style: italic}</style>
<div class="frame">${super.template}</div>
<div>\</div>
`;
}
constructor() {
super();
this.myProp = {stuffThatGoesHere: '!stuff that goes here!'};
}
static get headerTemplate() { return html`<h2>Sub-header</h2>`; }
static get footerTemplate() { return html`<h2>Sub-footer</h2>`; }
}
customElements.define(SubClass.is, SubClass);
</script>
<script>
class XString extends Polymer.Element {
static get is() {return 'x-string'};
static get template() {
return 'string template!';
}
}
customElements.define(XString.is, XString);
</script>
<super-class></super-class>
<sub-class></sub-class>
<x-string></x-string>
</body>
134 changes: 134 additions & 0 deletions test/unit/html-tag.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
<!doctype html>
<!--
@license
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<html>
<head>
<meta charset="utf-8">
<script src="../../../webcomponentsjs/webcomponents-lite.js"></script>
<script src="../../../web-component-tester/browser.js"></script>
<link rel="import" href="../../polymer.html">
</head>
<body>
<script>
suite('html function', function() {
test('html makes an HTML template', function() {
let t = Polymer.html``;
assert.instanceOf(t, HTMLTemplateElement);
});
test('template output has elements', function() {
let t = Polymer.html`<div><span></span></div><!-- hi -->`;
assert(t.content.querySelector('div'));
assert(t.content.querySelector('span'));
assert.instanceOf(t.content.lastChild, Comment);
});
test('escaping works as expected', function() {
let t = Polymer.html`<span>\f\o\o</span>`;
assert.equal(t.innerHTML, '<span>\\f\\o\\o</span>');
});
test('variables work', function() {
let a = 3;
let b = 'foo';
let t = Polymer.html`<div>${a} ${b}</div>`;
let inst = document.createElement('div');
inst.appendChild(t.content.cloneNode(true));
assert.equal(inst.textContent, `${a} ${b}`);
});
test('template variables only include the template contents', function() {
let t1 = Polymer.html`<div></div>`;
let t2 = Polymer.html`<span>${t1}</span>`;
assert.equal(t2.innerHTML, '<span><div></div></span>');
});
});
suite('Polymer + html fn', function() {
suiteSetup(function() {

class HtmlFn extends Polymer.Element {
static get is() {return 'html-fn';}
static get template() {
return Polymer.html`
<style>
:host {
display: block;
}
</style>
<div id="child">
[[databoundProperty]]
</div>
`;
}
static get properties() {
return {
databoundProperty: String
};
}
ready() {
super.ready();
this.databoundProperty = 'first';
}
}

customElements.define(HtmlFn.is, HtmlFn);

class HtmlFnSub extends HtmlFn {
static get is() {return 'html-fn-sub';}
static get template() {
return Polymer.html`
<div id="super">
${super.template}
</div>
`;
}
ready() {
super.ready();
this.databoundProperty = 'second';
}
}

customElements.define(HtmlFnSub.is, HtmlFnSub);

Polymer({
is: 'html-legacy',
_template: Polymer.html`
<div id="child">[[databoundProperty]]</div>
`,
properties: {
databoundProperty: {
type: String,
value: 'legacy'
}
}
});
});

test('Polymer elements can use html fn', function () {
let el = document.createElement('html-fn');
document.body.appendChild(el);
assert(el.shadowRoot);
assert.equal(el.$.child.textContent.trim(), 'first');
});

test('subclass elements can embed the superclass template', function() {
let el = document.createElement('html-fn-sub');
document.body.appendChild(el);
assert(el.$.super);
assert(el.$.child);
assert.equal(el.$.child.textContent.trim(), 'second');
});

test('legacy element works with html fn', function() {
let el = document.createElement('html-legacy');
document.body.appendChild(el);
assert(el.shadowRoot);
assert.equal(el.$.child.textContent.trim(), 'legacy');
});
});
</script>
</body>
</html>

0 comments on commit 4915b0a

Please sign in to comment.