I think this post relates to Polymer 0.5 and seems not to work in Polymer 1.0. For a beginner like me, I see no clear implementation; just the actual function code and not a "How to". Here is my simple setup that does not work (assume I have imported all elements):
<dom-element id="my-app">
<template>
<paper-button raised id="toggleDialog"></paper-button>
<paper-dialog entry-animation="scale-up-animation"
exit-animation="fade-out-animation" onclick="{{toggleDialog}}">
<h2>Header</h2>
<div>Dialog body</div>
</paper-dialog>
</template>
<script>
Polymer({
is: "my-app",
properties: {
type: String,
observer: "" // not important for this example so it's empty.
},
// should I put the function here?
toggleDialog: function() {
this.$.dialog.toggle();
}
});
</script>
</dom-element>
I hope I am not doing something wrong.
Replace:
this.$.dialog.toggle();
with:
this.$.toggleDialog.toggle();
because:
The string after the cash sign $ needs to match the id.
And, finally:
Move the id="toggleDialog" attribute from the <paper-button> tag to the <paper-dialog> tag. Because you want to target the <paper-dialog> element with the .toggle() method. Not the <paper-button> itself.
Related
Update
Apparently when using <template> the reading of innerHTML will return all attributes in lower case. Angular2 will not understand ngfor or ngif as of this beta 9 version and throws error. <script> is treated as a text fragment rather than DOM, which means attributes stay as they are.
Here:
https://groups.google.com/forum/#!topic/angular/yz-XdYV2vYw
Originial
Taking the following html and angular2 beta 9 component:
HTML CODE
<my-page>Loading...</my-page>
<script type="text/html" id="my-component-template1">
<select [(ngModel)]="SelectedType">
<option *ngFor="#someType of MyTypes" [selected]="SelectedType == someType" [value]="someType">{{someType}}</option>
</select>
</script>
<template id="my-component-template2">
<select [(ngModel)]="SelectedType">
<option *ngFor="#someType of MyTypes" [selected]="SelectedType == someType" [value]="someType">{{someType}}</option>
</select>
</template>
JS CODE
var myComponent =
ng.core.Component({
selector: 'my-page',
//complains if i use #my-component-template2
template: document.querySelector('#my-component-template1').innerHTML
})
.Class({
constructor: function () {
var self = this;
self.MyTypes = ['first', 'second'];
self.SelectedType = self.MyTypes[0];
}
});
document.addEventListener('DOMContentLoaded', function () {
ng.platform.browser.bootstrap(myComponent);
});
If i use my-component-template1 it works fine, but if i choose my-component-template2 it complains that ngModel and ngForOf are not a known native properties.
I tested a div as a template and apparently that won't work either with the same errors. So question is, why is it breaking if the template is part of the DOM? Furthermore, I really don't want to use the script text/html hack. Assuming this is why <template> was added in html5 specification. Why is this happening and how can i fix it?
The <template> tag is only used by Angular 2 structural directives (like the built-in ngIf, ngFor, ngSwitch, etc) - its use is somehow similar with the html5 specification since it defines content which is stored for subsequent use.
The * in front of a structural directive is just syntactic sugar which allows us to skip the <template> tag and focus directly on the HTML element that we are including, excluding, or repeating - you can read more about it here.
An example from Angular 2 docs which showcases this:
<!-- Examples (A) and (B) are the same -->
<!-- (A) *ngIf paragraph -->
<p *ngIf="condition">
Our heroes are true!
</p>
<!-- (B) [ngIf] with template -->
<template [ngIf]="condition">
<p>
Our heroes are true!
</p>
</template>
At the moment, I'm not sure if there's a way of defining inline Angular 2 HTML templates like there's in AngularJS 1. Your hack, as you put it, seems to do its job.
Angular handles <template> tags itself and doesn't simply add them to the DOM. If you inject TemplateRef into the constructor of your component you should get a reference to the template.
class MyComponent {
constructor(private tmplRef:TemplateRef) {
}
}
So I have an iron-list element for a user's data history. The iron-list is not part of a custom element. It is simply on the page. I want to populate once the user has successfully logged in. Perhaps it is just my inexperience with polymer, but there doesn't seem to be a straightforward way to do this. First attempt (simplified for reading, e.g. I don't actually use jquery, there's lots of error-handling code I'm omitting, etc):
<iron-list as="item" style='height: 100%;' id='history-list'>
<template>
<div style='min-height: 140px;'>
<ul>
<!-- various fields for each record as list items -->
</ul>
</div>
</template>
</iron-list>
<script>
//once user is logged in
var items = $.getJSON('userhistoryscript');
//setAttribute doesn't work either
document.getElementById('history-list').items = items;
</script>
I would swear this worked in an earlier version of Polymer. But it doesn't seem to work now, which is fine, but I need an alternative.
Some alternatives I've considered:
Have iron-ajax element in same DOM scope and set '
the URL once the user is logged in to trigger the
xhr request. I'm not sure whether or not that'd work.
Wrap the list in a custom element and use an
iron-meta-query per chrisW's answer.
Those options are terrible. I cannot believe there is no simpler way to accomplish this feat. How do I conditionally fetch data based on user input and dynamically add an iron-list to the page (or update one that's already there)? Is there really no API for this use case?
UPDATE
Thank you for your answers. Turns out that my original code actually works fine: it was actually a build process issue. For some reason iron-list did not get installed when I installed the project dependencies through bower. I took out the vulcanized import (which must not have contained a ref to iron-list either) and imported all the elements directly, then I got the 404 and figured out what had happened.
I think that for best practices, you should use this.$.historyList to refeer id on this element. Anyway, when you get data to populate iron-listyou should use this.set('items', data); An example using your element looks like:
<iron-list>
<template is="dom-repeat" items="{{data}}" as="history">
<!--history.property-->
</template>
</iron-list>
<script>
Polymer({
properties:{
data:{type:Array, value:[],}
},
_functionToSetDataWhenUserIsLoggedIn: function(data){
this.set('data',data);
}
});
</script>
Edit
An example of iron-list
<template is="dom-bind">
<iron-ajax url="data.json" last-response="{{data}}" auto></iron-ajax>
<iron-list items="[[data]]" as="item">
<template>
<div>
Name: <span>[[item.name]]</span>
</div>
</template>
</iron-list>
</template>
This example is using an ajax call that executes automatically and populates the iron-listwithout the need to create a customized element.
More about iron-list on:
https://elements.polymer-project.org/elements/iron-list
I didn't entirely understand your question. Hope this helps.
<iron-list items="[[data]]" as="item">
<template>
<div>
Name: <span>[[item.name]]</span>
</div>
</template>
</iron-list>
properties:{
data:{type:Array, value:[],}
},
// the attached function is automatically called
attached: function() {
// Use an iron meta in the element that you keep track in of login information
// or create an onLogin listener
var isLoggedIn = new Polymer.IronMetaQuery({key: 'isLoggedIn'}).value,
if (isLoggedIn) {
var jsonData = $.getJSON('userhistoryscript');
this.set('data',jsonData);
}
}
Side note, when access elements by ids in Polymer elements, make sure you do it this way:
this.$.elementId
or
Polymer.dom('#elementId')
Edit since you don't want to create a custom polymer element
Source Code
<template is="dom-bind">
<iron-list id="list">
</iron-list>
</template>
<script>
document.addEventListener('onLogin', function(event) {
var list = document.getElementById('#list');
var jsonDataObjects = $.getJSON('userhistoryscript');
for (var i = 0; i < jsonDataObjects.length; i++) {
var div = document.createElement('div');
div.textContent = jsonDataObjects[i].info; // change this line
list.appendChild(div);
}
});
</script>
Hello what is the difference of doing adding a child polymer element like this:
<dom-module id="app-element">
<template>
<h1>Hello</h1>
<test-element></test-element>
</template>
<script>
Polymer({
is: "app-element"
});
</script>
</dom-module>
<app-element></app-element>
This works just fine.
The effect of adding html code (including other polymer elements) inside the app-element tag
<app-element>some html here</app-element>
like this:
<dom-module id="app-element">
<template>
<h1>Hello</h1>
</template>
<script>
Polymer({
is: "app-element"
});
</script>
</dom-module>
<app-element>
<test-element></test-element>
</app-element>
This ignores the test-element code. So in which cases can I add html code inside a polymer element? When will it be ignored? What would be the case where you want to add polymer elements inside other polymer elements inside the html code like this:
<app-element>
<test-element></test-element>
</app-element>
?? Thank you
In your first case you are using local dom, in the second light dom.
In local dom, the custom element that contains it is responsible for the content (in this case app-element). So the creator of the custom element decides the content of the local dom. In contrast, using light dom provides the user of the custom element with the option to specify the content. The creator of the custom element can specify where the light dom should go inside the custom element using the <content></content> tag. So to make your second example work you would need something like this:
<dom-module id="app-element">
<template>
<h1>Hello</h1>
<content></content>
</template>
<script>
Polymer({
is: "app-element"
});
</script>
</dom-module>
<app-element>
<test-element></test-element>
</app-element>
An example use case for light dom is the paper-dialog. Using light dom, the user of the dialog can decide the content of the dialog. For example, the specific buttons to use, the main content of the dialog, etc.
Have a look at this page in the documentation for more information on local and light dom.
I am using Polymer 1.0 and I am building a small accordion example. I have data binding to the accordion text fine, I just want to change the icon of the accordion when I click it.
Below is my code
<dom-module id="ab-accordion">
<template>
<iron-ajax
auto
handle-as="json"
on-response="handleResponse"
debounce-duration="300"
id="ajaxreq"></iron-ajax>
<template is="dom-repeat" id="accordion" items="{{items}}" as="item">
<div class="accordion" on-click="toggleParentAcc">
<div id="accordion_header" class="accordion__header is-collapsed">
<i class="icon icon--chevron-down"></i>
<span>{{item.name}}</span>
</div>
<div id="standard_accordion_body" class="accordion__body can-collapse">
<div class="accordion__content">
<content id="content"></content>
</div>
</div>
</div>
</template>
</template>
<script>
Polymer({
is: "ab-accordion",
//Properties for the Element
properties: {
accordian: Object,
childaccordions: Object,
// Param passed in from the element - Set if the accordion is open by default.
open: String,
data: String,
reqUrl: {
type: String,
value: "https://example.com/service.aspx"
},
},
ready: function () {
this.items = [];
},
attached: function () {
// Run once the element is attached to the DOM.
},
toggleParentAcc: function (event) { // Toggle the classes of the accordions
//This is where I want to toggle the class
this.$.accordion_header.classList.toggle('is-collapsed');
if (typeof event !== 'undefined') {
event.stopPropagation(); // Stop the click from going up to the parent.
}
},
handleResponse: function (e) {
this.items = e.detail.response.sports;
}
});
</script>
</dom-module>
Basically inside the toggleParentAcc function I want to toggle the class of the div with ID accordion_header. But I just get undefined or null.
I have tried the following two lines:
this.$.accordion_header // #1
this.$$('#accordion_header') // #2
How I access that element inside the dom-repeat?
UPDATE: I can't even access the elements within the when inside the attached function.
attached: function(){
this.$.accordion_header // This is null?!
this.$$('#accordion_header'); // this is also null!
}
https://www.polymer-project.org/1.0/docs/devguide/local-dom.html#node-finding
Note: Nodes created dynamically using data binding (including those in dom-repeat and dom-if templates) are not added to the this.$ hash. The hash includes only statically created local DOM nodes (that is, the nodes defined in the element’s outermost template).
I think it would be better if you'd use Polymer.dom(this.root) instead. Also I'd advice you to not use static IDs in dom-repeat as they are meant to be unique. Use classes instead.
Looks like you might be encountering Event Retargeting which happens when events "bubble" their way up the DOM tree. Read this documentation to learn more.
When I encountered this, I solved it by using something like:
var bar = Polymer.dom(event).path[2].getAttribute('data-foo');
inside my Polymer() function.
To figure it out in your case, you should go to the console and search the DOM tree / event log to locate your target. If you have trouble locating the correct area of the console, post a comment and I might be able to help further.
I eventually figured out a way of doing this without having to select elements in the nested template.
<template id="accord_template" is="dom-repeat" items="{{items}}" as="item">
<ab-accordion-row id="[[item.id]]" name="[[item.name]]" open="[[item.open]]">
</ab-accordion-row>
</template>
ab-accordion is another element, I just feed it the data and I can then change the classes based on the params.
<div id="accordion" class="accordion" on-click="toggleAccordion">
<div class$="{{getClassAccordionHeader(open)}}">
<i class="icon icon--chevron-down"></i>
<span>{{name}}</span>
</div>
<div id="standard_accordion_body" class$="{{getClassAccordionChild(open)}}">
<div class="accordion__content">
<content></content>
</div>
</div>
</div>
try with this.
toggleParentAcc: function (event) { // Toggle the classes of the accordions
//This is where I want to toggle the class
var header = event.target.parentElement;
Polymer.dom(header).classList.toggle('is-collapsed');
// rest of your code
}
While migrating to Polymer 1.0 from 0.5 I have come across an interesting thing. Thought it might help others having similar problem.
I have an element where I am using <template is="dom-repeat" items="{{customers}}">...</template>. The problem I am facing is I have to place every single property binding inside a HTML element. The code below what I intended to write:
<template is="dom-repeat" items="{{customers}}">
<div>
{{item.name}}<br />
{{item.addr}}, {{item.addr2}}<br />
{{item.phone}}
</div>
</template>
But it is only displaying the value for {{item.name}}. The reason is other property bindings are not wrapped within separate HTML tags, they are not displaying at all!
I tried the following but didn't work either:
<template is="dom-repeat" items="{{customers}}">
<div>
<p>{{item.name}}</p>
<span>{{item.addr}} {{item.addr2}}</span>
</div>
</template>
Means, I put {{item.name}} inside a <p>...</p> tag and placed {{item.addr}} and {{item.addr2}} inside a single <span>...</span> tag.
Then I went on and put every single property binding wrapped by their own HTML tags like the following:
<template is="dom-repeat" items="{{customers}}">
<div>
<p>{{item.name}}</p>
<span style="display:block">{{item.addr}}, <span>{{item.addr2}}</span></span>
<span style="display:block;">{{item.phone}}</span>
</div>
</template>
and it works!!
I truly have no idea whether it is a bug of 1.0 or there is something I am doing wrong! If anybody knows the answer please help.
Thanks in advance
You're not doing anything wrong. With the introduction of Polymer 0.9 (and later 1.0) data-binding to the content of text nodes only works if you wrap everything into its own element.
See the Polymer documentation:
The binding annotation must currently span the entire content of the tag
So you have to remove all whitespace and other characters for it to work.
Example from the documentation:
<!-- this works -->
<template>
First: <span>{{first}}</span><br>
Last: <span>{{last}}</span>
</template>
<!-- Not currently supported! -->
<div>First: {{first}}</div>
<div>Last: {{last}}</div>
<!-- Not currently supported! -->
<div>
{{title}}
</div>
Edit
As of Polymer 1.2, the issue described in the question is no longer problematic / erroneous. Compound bindings now work, see release notes on the Polymer blog.
Just a heads up, for element attributes though you can use something like a helper function for string concatenation. Here's an example.
<my-foo fullname="{{computeFullName(firstname, lastname)}}">
Hi, my name is <span>{{firstname}}</span>.
</my-foo>
...
computeFullName: function(first, last) {
return first + ' ' + last;
}
And here's the link: https://www.polymer-project.org/1.0/docs/migration.html#data-binding
EDIT:
For node content as well, string concatenation can be done using computed properties (I call them helper functions). Here's an example,
<dom-module id="x-custom">
<template>
My name is <span>{{fullName}}</span>
</template>
</dom-module>
<script>
Polymer({
is: 'x-custom',
properties: {
first: String,
last: String,
fullName: {
type: String,
// when `first` or `last` changes `computeFullName` is called once
// (asynchronously) and the value it returns is stored as `fullName`
computed: 'computeFullName(first, last)'
}
},
computeFullName: function(first, last) {
return first + ' ' + last;
}
...
});
</script>
With Polymer 1.2 you example code will actually work. Binding annotations no longer need to span the entire tag.
Example:
<div>first name: [[name.first]] last name: [[name.last]]</div>
https://blog.polymer-project.org/releases/2015/11/02/release-1.2.0/
You'll want to use a computed property to combine values. Search for them on this page https://www.polymer-project.org/1.0/docs/devguide/properties.html