I'm starting to learn Polymer 1.0 and I couldn't figure out how to programatically search for insertion points. I realize I could wrap a <div> around the <content> tag and check if that <div> has children or not, but that requires rendering a <div> for every element, which seems wasteful. Is there a way, with JavaScript, to check if any insertion points have been loaded? Ideally, I'd have a function thereAreInsertionPoints which would determine whether or not the <p> tag would render. My Polymer code looks like this:
<template>
<h1>{{title}}</h1>
<p>{{body}}</p>
<content id="content"></content>
<p if="{{thereAreInsertionPoints()}}">There are insertion points!</p>
</template>
<script>
Polymer({
is: "post-content",
properties: {
title: String,
body: String
},
thereAreInsertionPoints: function(){
//determine whether or not we have insertion points
}
});
</script>
There are various Polymer APIs for working with the DOM including Content APIs.
Content APIs:
Polymer.dom(contentElement).getDistributedNodes()
Polymer.dom(node).getDestinationInsertionPoints()
These APIs can be used in various ways to check for distributed nodes and insertion points. I have created a working implementation that shows the post-content element with additional methods to check for distributed nodes and destination insertion points.
<script src="http://www.polymer-project.org/1.0/samples/components/webcomponentsjs/webcomponents-lite.js"></script>
<link rel="import"
href="http://www.polymer-project.org/1.0/samples/components/polymer/polymer.html">
<dom-module id="post-content">
<template>
<h1>{{title}}</h1>
<p>{{body}}</p>
<content></content>
<template is="dom-if" if="{{destinationInsertionPointsExist()}}">
<p>Destination insertion point(s) exist.</p>
</template>
<template is="dom-if" if="{{distributedNodesExist()}}">
<p>Distributed node(s) exist.</p>
</template>
</template>
<script>
Polymer({
is: "post-content",
properties: {
title: String,
body: String
},
destinationInsertionPointsExist: function () {
var distributedNodes = Polymer.dom(this).childNodes;
var countDestinationInsertionPoints = 0;
distributedNodes.forEach(function (distributedNode) {
var distributedNodeHasDestinationInsertionPoints = Polymer.dom(distributedNode).getDestinationInsertionPoints().length > 0 ? true : false;
if (distributedNodeHasDestinationInsertionPoints) {
countDestinationInsertionPoints++;
}
});
return countDestinationInsertionPoints > 0 ? true : false;
},
distributedNodesExist: function () {
var contentNodes = Polymer.dom(this.root).querySelectorAll("content");
var countDistributedNodes = 0;
contentNodes.forEach(function(contentNode) {
var contentNodehasDistributedNodes = Polymer.dom(contentNode).getDistributedNodes().length > 0 ? true : false;
if (contentNodehasDistributedNodes) {
countDistributedNodes++;
}
});
return countDistributedNodes > 0 ? true : false;
}
});
</script>
</dom-module>
<post-content title="This is the title" body="This is the body">
<p>This is distributed content</p>
</post-content>
A few notes about the code:
I made a lot of the variable names and ternary checks very verbose for clarity in this answer. Changes could be made to simplify the code.
For example:
var distributedNodeHasDestinationInsertionPoints = Polymer.dom(distributedNode).getDestinationInsertionPoints().length > 0 ? true : false;
could become something like
var hasInsertionPoints = Polymer.dom(distributedNode).getDestinationInsertionPoints().length
Use the new (Polymer 1.0) dom-if conditional template.
<p if="{{thereAreInsertionPoints()}}">There are insertion points!</p>
becomes
<template is="dom-if" if="{{destinationInsertionPointsExist()}}">
<p>Destination insertion point(s) exist.</p>
</template>
I would recommend stepping through the destinationInsertionPointsExist and distributedNodesExist methods to insure that you fully understand what is actually being checked. You may need to modify these methods to suit your particular needs and requirements.
For example, even if you have a single space between the post-content element start and end tag both of these methods will return true.
<post-content title="This is the title" body="This is the body"> </post-content>
Related
I have this template dom-if using Polymer 1.X, and it is not working.
It is supposed to display 'Loading' when requirement.allLoaded is false and display the real content when requirement.allLoaded is true.
I switch the state of this variable in my test function. But it doesn't take effects.
//Properties
requirement: {
type:Object,
value: {
allLoaded: false,
tagsLoaded: false
}
}
//Test function
_testButton: function(){
console.log(this.requirement);
this.requirement.allLoaded = !this.requirement.allLoaded;
console.log(this.requirement);
},
<div id="modal-content">
<template is="dom-if" if={{!requirement.allLoaded}}>
<p>Loading</p>
</template>
<template is="dom-if" if={{requirement.allLoaded}}>
<iron-pages selected="[[selectedTab]]" attr-for-selected="name" role="main">
<details-tab name="details"></details-tab>
<bar-chart-tab name="barchart"></bar-chart-tab>
<live-data-tab name="livedata" shared-info='{{sharedInfo}}'></live-data-tab>
</iron-pages>
</template>
</div>
Note: I already used this structure to display/not display things in other project (with Polymer 2) and it was working.
Is it only the change that does not work? I.e. it shows correctly on load?
Try notifying Polymer of the change:
this.requirement.allLoaded = !this.requirement.allLoaded;
this.notifyPath('requirement.allLoaded');
You could also use this.set('property.subProperty', value) for Observable Changes.
In your case, that's this.set('requirement.allLoaded', !this.requirement.allLoaded);
I am playing with Polymer 2.0, and I don't understand how to pass an object as an element attribute.
Here's my code:
<dom-module id="notes-app">
<template>
<style>
:host {
display: block;
}
</style>
<button on-click="loadNotes">Get the notes</button>
<template is="dom-repeat" items="[[notes]]" as="note">
<note recipe='JSON.stringify(note)'></note>
</template>
</template>
<script>
class NotesApp extends Polymer.Element {
static get is() { return 'notes-app'; }
static get properties() {
return {
notes: {
type: Array,
value: []
}
};
}
loadNotes() {
this.notes = [
{"desc":"desc1", "author":"auth1", "type":"type1"},
{"desc":"desc2", "author":"auth2", "type":"type2"},
{"desc":"desc3", "author":"auth3", "type":"type3"}
];
}
}
window.customElements.define(NotesApp.is, NotesApp);
</script>
</dom-module>
simple-note is the element who has a property of type Object:
<dom-module id="note">
<template>
<style>
:host {
display: block;
}
</style>
<div>
<fieldset>
<label>description</label>{{note.desc}}<br>
<label>author</label>{{note.author}}<br>
<label>type</label>{{note.type}}
</fieldset>
</div>
</template>
<script>
class SimpleNote extends Polymer.Element {
static get is() { return 'simple-note' }
static get properties() {
return {
note: {
type: Object,
value: {},
notify: true
}
};
}
}
customElements.define(SimpleNote.is, SimpleNote);
</script>
</dom-module>
As you can see I want note-app to display all the objects in its notes property by passing an object representing a note to every simple-note elements (don't known if it is the right way to make elements interact each other). I want it to happen when I press the notes-app button. How can I pass an object to an element attribute in this case?
Since you're trying to pass the variable as an object, you should use property bindings instead of attribute bindings (which only supports strings).
Polymer data bindings require curly or square brackets ({{twoWayBinding}} or [[oneWayBinding]]). For example, to set the foo property of the <x-child> element to the value of note, the template would look something like this:
<template is="dom-repeat" items="[[notes]]" as="note">
<x-child foo="[[note]]">
</template>
Given that SimpleNote.is equals "simple-note", I assume your usage of <note> and <dom-module id="note"> were only typos in your question. They should be set to simple-note, as the element name must start with a lowercase ASCII letter and must contain a dash.
It looks like you're binding a recipe property, but <simple-note> declares a note property (and no recipe) and binds to note sub-properties in its template. I assume recipe is another typo.
working demo
I am having huge difficulty to implement simple dropdown list with Polymer 0.5.
I am also parallel migrating from Polymer .5 to 1.0. But that is separate discussion (
Migrating Polymer project .5 to 1.0 error).
Here is the code I am using to define polymer element inside body:
<polymer-element name="x-trigger" extends="paper-icon-button" relative="" on-tap="{{toggle}}" noink="">
<template>
<shadow></shadow>
<content></content>
</template>
</polymer-element>
I am using the element further down the body like this:
<x-trigger icon="menu" relative="" noink="" role="button" tabindex="0" aria-label="menu">
<paper-dropdown tabindex="-1" class="core-transition" style="outline: none; display: none;">
halign = left
<br>
valign = top
</paper-dropdown>
</x-trigger>
I defined following script section in the head section of the page:
<script>
Polymer('x-trigger', {
toggle: function () {
if (!this.dropdown) {
this.dropdown = this.querySelector('paper-dropdown');
}
this.dropdown && this.dropdown.toggle();
}
});
</script>
The problem is, I do see the icon button in the page but when ever I click on that button, nothing happens.
Further debugging revealed,
If I open the console debugger in chrome and
Place break point on Polymer or inside toggle method in the script section
Do page refresh
Break point gets hit and drop-down works
I don’t know what is causing the issue
Update: While debugging i got the following error in the line:
Polymer('x-trigger', {
/deep/ combinator is deprecated
Does this mean that i have to upgrade to polymer v1 to resolve this issue or is their any workaround for polymer 0.5?
The difference between Polymer 0.5 and 1.0 is really quite large. The /deep/ selector you reference was one of the big issues I faced migrating.
I recently migrated a project from 0.5 to 1.0 and in order to do so I had to change all instances of /deep/ to the new notation.
My advice would be to migrate from 0.5 to 1.0 first, then use the new Polymer documentation to come up with a solution.
In that project I implemented a simple drop-down. Here's my approach:
<dom-module id="profile-edit-page">
<style>
// Styling
</style>
<template>
<div class="container-app">
<div class="container-inner">
<!-- Other form elements -->
<input is="iron-input" id="filterInput" type="text" required placeholder="Automotive assistant" label="Occupation" bind-value="{{order.occupation}}" on-focus="startPickingOccupation" on-keydown="updateFilter" on-blur="stopPickingOccupation" class="block field input-reg mb2"></input>
<div class$="[[pickingOccupationClass(pickingOccupation)]]">
<paper-menu >
<template id="occupationRepeat" is="dom-repeat" items="[[occupations]]" filter="isShown">
<paper-item class="option" on-click="pickOccupation">[[item]]</paper-item>
</template>
</paper-menu>
</div>
<button class$="inputClass" class="btn btn-primary btn-block" on-click="forward" value="{{order.registration}}">Continue</button>
</div>
</div>
</template>
</dom-module>
<script>
Polymer({
properties: {
order: Object,
pickingOccupation: {
type: Boolean,
value: false
},
occupationFilter: {
type: String,
value: ""
},
occupations: {
type: Array,
value: ["Abattoir Worker",
"Accommodation Officer",
"Accountant",
// Etc.
"Zoology Consultant"]
}
},
is: "profile-edit-page",
pickOccupation: function(e) {
this.set('order.occupation', e.model.item);
this.set('pickingOccupation', false);
},
startPickingOccupation: function() {
this.pickingOccupation = true;
},
stopPickingOccupation: function() {
this.async(function() {
this.pickingOccupation = false;
},500);
},
updateFilter: function() {
if(typeof(this.$.occupationRepeat) === "undefined") {
return;
}
this.set('occupationFilter', this.$.filterInput.value.toLowerCase());
this.async(function() {
this.$.occupationRepeat.render();
},50);
},
isShown: function(item) {
if(this.order.occupation == '') {
return false;
}
return (item.toLowerCase().search(this.occupationFilter) !== -1);
},
pickingOccupationClass: function(picking) {
if(this.pickingOccupation) {
return "picking";
} else {
return "hidden";
}
}
});
</script>
Move the script into the actual polymer-element:
<polymer-element name="x-trigger" extends="paper-icon-button" relative="" on-tap="{{toggle}}" noink="">
<template>
<shadow></shadow>
<content></content>
</template>
<script>
Polymer('x-trigger', {
toggle: function () {
if (!this.dropdown) {
this.dropdown = this. querySelector('paper-dropdown');
}
this.dropdown && this.dropdown.toggle();
}
});
</script>
</polymer-element>
I'm trying to make custom element within a custom element and have the inner custom element able to change its value from within and the outer able sync its binding on that change. What can I do to get this working?
I've thoroughly scoured the documentation, but it's very lackluster in this department. I believe Node.bind() may be something of interest, but not sure how it would apply in this case.
Here's a simplified test case and plunker demo:
<polymer-element name='test-app'>
<template>
First:
<test-input id='one' value='{{value}}'></test-input>
<br/>
<br/>
Second:
<test-input id='two' value='{{value}}'></test-input>
<br/>
<br/>
Value:
{{value}}
</template>
<script>
Polymer({
value: 5
})
</script>
</polymer-element>
<polymer-element name='test-input'>
<template>
<style>
#val {
font-size: 50px;
}
</style>
<div id='val'>{{value}}</div>
<button on-tap='{{increment}}'>+</button>
<button on-tap='{{decrement}}'>-</button>
</template>
<script>
Polymer({
publish: {
value: 4
},
increment: function() {
this.value = this.value + 1;
},
decrement: function() {
this.value = this.value - 1;
}
})
</script>
</polymer-element>
<test-app></test-app>
http://plnkr.co/edit/KjQ9DusaFg2jp1BTFUde?p=preview
If this was working, the value property of the test-app parent element should be in sync with both of the test-inputs value property.
Notice this warning in the console:
Attributes on test-input were data bound prior to Polymer upgrading the element. This may result in incorrect binding types.
test-app uses test-input before Polymer knows about test-input. All of an element's dependencies must be declared before that element is declared. Move test-input above test-app and it works as expected.
http://plnkr.co/edit/ZaIj60S3lAHT18k5T3sn?p=preview
I'm trying to bind a method to an on-tap attribute of a paper-button. After much testing, I've found that I can only bind a (for lack of a better word) top-level function, and not a method of an object in the template.
For example, I have a template, to which I have bound a number of objects, one of which is a user object. Object user has a bunch of methods and variables, like 'isNew' or 'reputation'. The user object also has a method 'addReputation'
I can use the object variables like this :
<template if = '{{user.new}}'><h1>{{user.name}}</h1></template>
And I can bind button taps like this:
<paper-button on-tap='{{addReputation}}'>Add Rep</paper-button>
But not like this:
<paper-button on-tap='{{user.addReputation}}'>Add Rep</paper-button>
Does anyone know why this may be?
if you set the method to a handler on your element's prototype it works. That way you can still keep things dynamic:
<script src="http://www.polymer-project.org/webcomponents.min.js"></script>
<script src="http://www.polymer-project.org/polymer.js"></script>
<polymer-element name="my-element" on-tap="{{tapHandler}}">
<template>
<style>
:host {
display: block;
}
</style>
click me
<content></content>
</template>
<script>
Polymer({
created: function() {
this.user = {
method: function() {
alert('hi');
}
};
this.tapHandler = this.user.method;
}
});
</script>
</polymer-element>
<my-element></my-element>
i'm sharing my plunk to resolve above problem. plunk link
In the template
<button on-tap="{{fncall}}" data-fnname="b">b call</button>
In the script
x.fncall = function(e) {
var target = e.target;
var fnName = target.getAttribute("data-fnname");
return x.datamodel[fnName]();
}
Polymer(x);