How to inject template using slot with data-binding in Polymer2 - polymer

I'd like to inject a rendering template from a parent component to a child component using <slot> insertion points. The injected template contains data-binding on a property of the child component (my-child.data in this case).
<dom-module id="my-parent">
<template>
<my-child>
<template>
<div>Child's data property: [[data]]</div>
</template>
</my-child>
</template>
...
The rendering child component basically looks like this:
<dom-module id="my-child">
<template>
<header></header>
<slot></slot>
<footer></footer>
</template>
<script>
class MyChild extends Polymer.Element {
static get is() { return 'my-child'; }
static get properties() {
return {
data: { ... }
};
}
...
I'm not sure whether this is possible at all with Polymer2. Vue2 has a concept called "scoped slot" to achieve this. Thanks in advance for any feedback!

Data binding is by default tied within the current scope of the binding. If you wish to change the scope, you must put your markup inside a <template> tag and stamp in it inside a different scope.
Your HTML code in the question is already OK - you actually wrap the light DOM inside a <template>, but you then use that <template> incorrectly. You must not use <slot>, but must stamp that template manually and insert it somewhere inside the my-child element's shadow DOM.
Here you have a working demo on how to achieve this: http://jsbin.com/loqecucaga/1/edit?html,console,output
I have even added the data property binding to an input element in order to demonstrate that property changes also affect the stamped template.
The stamping is relatively simple and is done inside the connectedCallback method:
var template = this.querySelector('template');
this.__instance = this._stampTemplate(template);
this.$.content.appendChild(this.__instance);
The stamped template is put inside a placeholder div element, which you put somewhere inside the my-child's template:
<div id="content"></div>
To sum up, here is the full code from the demo:
<link href="polymer/polymer-element.html" rel="import"/>
<link href="polymer/lib/mixins/template-stamp.html" rel="import"/>
<dom-module id="my-parent">
<template>
<my-child>
<template>
<div>Child's data property: [[data]]</div>
</template>
</my-child>
</template>
<script>
class MyParent extends Polymer.Element {
static get is() { return 'my-parent'; }
}
window.customElements.define(MyParent.is, MyParent);
</script>
</dom-module>
<dom-module id="my-child">
<template>
<header>Header</header>
<div id="content"></div>
<footer>Footer</footer>
<input type="text" value="{{data::input}}" />
</template>
<script>
class MyChild extends Polymer.TemplateStamp(Polymer.Element) {
static get is() { return 'my-child'; }
static get properties() {
return {
data: {
type: String,
value: 'Hello, World!'
},
};
}
connectedCallback() {
super.connectedCallback();
var template = this.querySelector('template');
this.__instance = this._stampTemplate(template);
this.$.content.appendChild(this.__instance);
}
}
window.customElements.define(MyChild.is, MyChild);
</script>
</dom-module>
<my-parent></my-parent>

Related

Vaadin 10 slot in vaadin-dialog

I want to create a reusable dialog in Vaadin 10. Therefore I thought of using the tag in vaadin-dialog. I created a html file containing the templated vaadin-dialog.
<dom-module id="show-sera-dialog">
<template>
<vaadin-dialog opened="opened">
<sera-field></sera-field>
<slot></slot>
</vaadin-dialog>
<template>
</dom-module>
And I try to use it like this.
<show-sera-dialog opened="{{showSera}}">
It worked!
</show-sera-dialog>
The dialog will be opened and the sera-field displayed, but the text is never displayed. Is there an error withing these lines? Am I using vaadin-dialog the wrong way?
PS:
It works with this button:
<dom-module id="one-shot-button">
<template>
<vaadin-button on-click="_disable" theme="raised primary" disabled={{disabled}}>
<slot></slot>
</vaadin-button>
</template>
<script>
class OneShotButton extends I18nMixin(Polymer.Element) {
static get is() {
return 'one-shot-button'
}
static get properties() {
return {
disabled: {type: Boolean, notify: true}
}
}
_disable() {
this.disabled = true;
this.onClick();
}
}
customElements.define(OneShotButton.is, OneShotButton);
</script>
You are putting a <slot> inside a <template>. Template means that web component will do whatever it needs when rendering it, e.g. by creating multiple instances like cells in grid, etc.
In this case vaadin-dialog teleports the content to the body, so as it escapes any stacking context. Thus it makes slots not work because they are not in the same DOM hierarchy.
One way to create a reusable dialog would be to create a component like this
<dom-module id="show-sera-dialog">
<template>
<vaadin-dialog opened={{opened}}>
<template>
[[text]]
</template>
</vaadin-dialog>
</template>
<script>
class ShowSeraDialog extends Polymer.Element {
static get is() { return 'show-sera-dialog'; }
static get properties() {
return {
"text" : String,
"opened" : Boolean
}
}
}
window.customElements.define(ShowSeraDialog.is, ShowSeraDialog);
</script>
</dom-module>
And use it like this
<link rel="import" href="../../bower_components/polymer/polymer-element.html">
<link rel="import" href="./show-sera-dialog.html">
<dom-module id="polymer-test-app">
<template>
<show-sera-dialog id="dialog1" text="It worked!"></show-sera-dialog>
<button on-click="showDialog">Show dialog</button>
</template>
<script>
class PolymerTestApp extends Polymer.Element {
static get is() { return 'polymer-test-app'; }
showDialog() {
this.$.dialog1.opened = true;
}
}
window.customElements.define(PolymerTestApp.is, PolymerTestApp);
</script>
</dom-module>

Polymer 2.0 Custom Event Handling

So I want to dispatch custom event from the child element.
<dom-module id="layout-dashboard">
<template>
<style></style>
</template>
<script>
class LayoutDashboard extends Polymer.Element {
connectedCallback() {
this.dispatchEvent(new CustomEvent('kick-start', {detail : {}, bubble: true, composed : true}))
}
}
</script>
</dom-module>
And then handle the event from the parent.
<dom-module id="layout-parent">
<template>
<style></style>
<layout-dashboard on-kick-start="handleKick"></layout-dashboard>
</template>
<script>
class LayoutParent extends Polymer.Element {
handleKick(){
console.log("test");
}
}
</script>
</dom-module>
Any idea why handleKick() is not executed?
Your code structure for defining custom-element is wrong. Please read custom element concepts and quick tour of polymer.
The basic structure is like this:
<link rel="import" href="...">
<dom-module id='my-element'>
<template>
<style></style>
</template>
<script>
class MyElement extends Polymer.Element {
static get is() {
return "my-element";
}
}
customElements.define(MyElement.is, MyElement);
</script>
</dom-module>
The mistakes you have done:
<script></script> and then you wrote your scripts.
<script></script> is inside <template> that should be outside <template>and inside <dom-module>.
For the custom element lifecycle you need call the superclass method. This is required so Polymer can hook into the element's lifecycle. i.e:
connectedCallback() {
super.connectedCallback();
// …
}
You need to register the new element with the browser
customElements.define(MyElement.is, MyElement);
I have made a plnkr demo to solve the issue. Please have a look at : http://plnkr.co/edit/p5qvt4cIEJBobrmY8QDg?p=preview .
Dashes in the event name are fine. Start by fully defining and registering your elements.
Example from the Polymer docs here. Note the is() getter and the customElements.define at the end which registers your element.
<script>
class IconToggle extends Polymer.Element {
static get is() {
return "icon-toggle";
}
constructor() {
super();
}
}
customElements.define(IconToggle.is, IconToggle);
</script>
It's not specified in the Polymer doc, but I suppose it's because the event name contains a dash. If you want to handle this event, I suggest you to add a listener once connect.
Add a listener to the LayoutParent ready function:
ready() {
super.ready();
window.addEventListener('kick-start', (e) => this.handleKick(e));
}
nb; for Polymer 2.0 (as described in #Ofisora's post)

Polymer2.0- Trying to download DIV content of custom element

I am trying to download div content of custom element using document.getElementById of the div and trying to implement download option from the JS FIddle - http://jsfiddle.net/evx9stLb/
From console, I am getting below error
pen.js:6 Uncaught TypeError: Cannot read property 'innerHTML' of null
at download (pen.js:6)
at HTMLButtonElement.onclick (index.html:15)
HTML:
<head>
<base href="https://polygit.org/polymer+v2.0.0/shadycss+webcomponents+1.0.0/components/">
<link rel="import" href="polymer/polymer.html">
<link rel="import" href="iron-collapse/iron-collapse.html">
</head>
<body>
<x-foo></x-foo>
<button onClick="download()">Download</button>
<dom-module id="x-foo">
<template>
<button on-click="toggle">toggle collapse</button>
<div id="content">
<iron-collapse id="collapse">
<div>Content goes here...</div>
</iron-collapse>
</div>
</template>
</dom-module>
</body>
JS:
function download(){
var a = document.body.appendChild(
document.createElement("a")
);
a.download = "export.html";
a.href = "data:text/html," + document.getElementById("content").innerHTML;
a.click();
}
class XFoo extends Polymer.Element {
static get is() { return 'x-foo'; }
static get properties() {
return {};
}
toggle() {
this.$.collapse.toggle();
}
}
customElements.define(XFoo.is, XFoo);
Code which I am below - https://codepen.io/nagasai/pen/ZyRKxj
Make some updates, and help this would help,
HTML
<head>
<base href="https://polygit.org/polymer+v2.0.0/shadycss+webcomponents+1.0.0/components/">
<link rel="import" href="polymer/polymer.html">
<link rel="import" href="iron-collapse/iron-collapse.html">
</head>
<body>
<x-foo></x-foo>
<dom-module id="x-foo">
<template>
<button on-click="download">Download</button>
<button on-click="toggle">toggle collapse</button>
<div id="content">
<iron-collapse id="collapse">
<div>Content goes here...</div>
</iron-collapse>
</div>
</template>
</dom-module>
</body>
JS
class XFoo extends Polymer.Element {
static get is() { return 'x-foo'; }
static get properties() {
return {};
}
toggle() {
this.$.collapse.toggle();
}
download(){
var a = document.body.appendChild(
document.createElement("a")
);
a.download = "export.html";
a.href = "data:text/html," + this.$.content.innerHTML;
a.click();
console.log(this.$.content.innerHTML);
}
}
customElements.define(XFoo.is, XFoo);
https://codepen.io/renfeng/pen/BZOQro
a document query (on light dom) won't pierce the shadowDom. To do that you have to specifically select the element and query it's shadowRoot.
it would look something like this
a.href = "data:text/html," + document.getElementsByTagName('x-foo')[0].shadowRoot.querySelector('#content').innerHTML;
BUT only do this if you can't modify the element itself. It's not nice to stir around in someone else's shadowRoot.
As shown by Frank R. its far better to modify the element itself and provide a download functionality.
You can trigger this easily from an external element with something like
document.getElementsByTagName('x-foo')[0].download();
DOM under the shadow Root, or the shadow DOM, can not be accessed via innerHTML. It is not supposed to be. Just the way it is.
So, No, you simply can not get the shadow DOM contents via innerHTML.
There used to be access now deprecated to shadowDOM via vanilla javascript earlier
and also discussed here
However, with shadow DOM V1 beig the norm now, you may have to just wait and watch if you can pierce the shadowDOM
An alternative would be, to move your entire DOM in the custom element, outside of it, Using Slots.
Slots distribute content, so, the page that uses your element, can access it via innerHTML.
You could possibly try hacky ways like the one mentioned here

Polymer 2.0 - Trying to create HTML template from JSON with events

I am trying to create HTML templates from JSON object and able to render elements but the events are not getting added to the element and not showing up in the developer tools/Shadow DOM.
Codepen for reference - https://codepen.io/nagasai/pen/bRjWbd
Issue: Events - onkeypress, onkeyup,onchange are not showing on input and checkbox elements and couldn't add them but other options are showing up like name, type(Again type is getting displayed only for checkbox but not for textbox)
Screenshot for actual issue
HTML:
<head>
<base href="https://polygit.org/polymer+v2.0.0/shadycss+webcomponents+1.0.0/components/">
<link rel="import" href="polymer/polymer.html">
<link rel="import" href="iron-collapse/iron-collapse.html">
</head>
<body>
<x-foo attr='[{
"type":"text",
"title":"Textbox Name",
"name":"temp",
"requried":"requried",
"onkeypress":"testKeyPress()",
"onkeyup":"testKeyUp()",
"onchange":""
},{
"type":"checkbox",
"title":"CheckBox Name",
"name":"temp",
"requried":"requried",
"disabled":"disabled",
"onkeypress":"",
"onkeyup":"",
"onchange":"testChange()"
}]'></x-foo>
<dom-module id="x-foo">
<template>
<template is="dom-repeat" items="{{attr}}">
<label>{{item.title}}</label>
<input type="{{item.type}}"
required="{{item.required}}"
name="{{item.name}}"
onchange="{{item.onchange}}"
onkeypress="{{item.onkeypress}}"
onkeyup="{{item.onkeyup}}()"
>
</template>
</template>
</dom-module>
</body>
JS:
class XFoo extends Polymer.Element {
static get is() { return 'x-foo'; }
static get properties() {
return {
attr:{
type:Array
}
};
}
}
customElements.define(XFoo.is, XFoo);
Correct me if I'm wrong but it should be:
on-change="{{item.onchange}}"
on-keypress="{{item.onkeypress}}"
on-keyup="{{item.onkeyup}}()"
Reference: https://www.polymer-project.org/2.0/docs/devguide/gesture-events

Can I select any element in the document using Polymer's Automatic Node Finding?

I am working on a Polymer project and I a few custom elements nested. For example, I have a custom element called <example-main>. Suppose that this element has a function called displayMessage()
I have another custom element defined inside <example-main> called <example-alerts>. I want the alerts element to be able to use the function in its parent, main, to display a message.
To achieve this, I am trying to select the element <example-main id='mainEl'> by using the Polymer's Automatic Node Finding hash, this.$.mainEl and using the function like so: this.$.mainEl.displayMessage();
However, I get this error message:
Uncaught TypeError: Cannot read property 'displayMessage' of undefined
Am I making a wrong approach of having Polymer elements call functions in other Polymer objects?
To visualize things a little better, the index.html would look something like:
...
<body>
<example-main id='mainEl'></example-main>
</body>
...
while example-main.html looks something like:
<polymer-element name='example-main'>
<template>
...
<example-alerts>
</template>
<script>
Polymer(example-element, {
displayMessage: function(){
//do message showing magic
}
});
</script>
</polymer-element>
and lastly, example-alerts.html looking like:
<polymer-element name='example-alerts'>
<template>
...
<paper-button on-tap='{{callFunction}}'></button>
</template>
<script>
Polymer(example-alerts, {
callFunction: function(){
//call the function in parent
this.$.mainEl.displayMessage();
}
});
</script>
</polymer-element>
You are not doing this correct way , you cannot find nodes this way .
Node finding just search within same element defined in elements outermost template.
You should either change your approach or you can use events to handle the situation pls see the sample demo which can work for you.
<polymer-element name='example-alerts'>
<template>
...
<paper-button on-tap='{{callFunction}}'></button>
</template>
<script>
Polymer(example-alerts, {
callFunction: function(){
//call the function in parent
this.fire('ouch', {msg: 'That hurt!'});
}
});
</script>
</polymer-element>
--Second Element
<polymer-element name='example-main'>
<template>
...
<example-alerts on-ouch='{{displayMessage}}'>
</template>
<script>
Polymer(example-element, {
displayMessage: function(){
//do message showing magic
}
});
</script>
</polymer-element>