Polymer 2.0: Notify and reflect to attribute - polymer

I'm new to this framework and would love to see some useful and simple examples of notify and reflect to attribute properties being put to use.
Please keep the examples simple or add explanation for whatever code you provide.

Notify:
can be set to True|False. Let's say you have parent-element and child-element. Working example
parent-element.html:
<dom-module id="parent-element">
<template>
<child-element foo="{{test}}"></child-element>
</template>
<script>
Polymer({
is: "parent-element",
properties: {
test: {
value: "bar"
}
}
})
</script>
</dom-module>
As you can see, we have 1 property called test which is propagated to child element, where we can manipulate with it.
child-element.html:
<dom-module id="child-element">
<template>
<paper-input value="{{test}}"></paper-input>
</template>
<script>
Polymer({
is: 'child-element',
properties: {
test: {
}
},
});
</script>
</dom-module>
What is hapenning? In child element we defined test property and this property is binded to paper-input, which means, whenever we write something in paper-input, the property updates itself automatically . YEE that's awesome, we don't need to take care of updating property inside child-element, but HOW can parent know that property test has changed? Well, he can't.
And here comes notify: true. If you set it up, you don't have to care about notifying parent-element that somewhere inside of the child-element, test property has been changed.
Shortly, property test in parent-element and child-element will updates simultaneously
Reflect-to-attribute:
As name already says, when you set this to some property, it's value will be visible in attribute of host element. Better to show this on some example.
In Polymer we know, that setting some binding to attribute of some element needs $ sign.
<custom-elem foo-attribute$="[[someProperty]]"></custom-elem>
Well, this can't be used everywhere. Let's say, that we need foo-attribute inside custom-elem.
Becuase foo-attribute was declared as attribute and not property, it's value won't be visible inside of element. So we need something where some attribute will represent attribute and also property.
So some working example, with some real situation would be like:
<dom-module id='parent-element'>
<template>
<style>
child-elemet[foo='bar'] {background-color: green}
child-element[foo='foo'] {background-color: red}
</style>
<child-element foo="{{test}}"></child-element>
</template>
<script>
Polymer({
is: "parent-element",
properties: {
test: {
value: "bar"
}
}
})
</script>
</dom-module>
In this case, CSS won't work, because foo is property (not an attribute) and CSS is applied on attributes only.
To make it work, we have to add reflectToAttribute on foo property inside child-element.
<dom-module id='child-element'>
<template>
<div>[[foo]]</div>
</template>
<script>
Polymer({
is: "child-element",
properties: {
foo: {
reflectToAttribute: true
}
}
})
</script>
</dom-module>
After this, foo will become attribute and also property. At this moment, CSS will be applied on child-element and we will be able to work with value of foo inside child-element

From the docs: https://www.polymer-project.org/1.0/docs/devguide/data-binding
To bind to an attribute, add a dollar sign ($) after the attribute
name:
<div style$="color: {{myColor}};">
Two-way bindings ... property must be set to notify: true.

Related

Polymer - can not click event outside of element to close itself

Polymer 1.*
I had to write my own dropdown menu. I need to close the menu when the user clicks outside of the element. However, I am not able to catch the event when a user clicks outside of the element so I can close the menu.
Any ideas what I am doing wrong?
EDIT: I've studying paper-menu-button which closes paper-listbox when I click outside the element.... but I don't see anywhere where it catches that event https://github.com/PolymerElements/paper-menu-button/blob/master/paper-menu-button.js#L311
<dom-module id="sp-referrals-reservations-dropdown">
<template>
<style include="grid-dropdown-styles">
</style>
<div id="dropdown" class="grid-dropdown">
<paper-listbox>
<div class="grid-dropdown-item">Convert to stay</div>
<div class="grid-dropdown-item">Cancel reservation</div>
<div class="grid-dropdown-item">Delete reservation</div>
</paper-listbox>
</div>
</template>
<script>
(function() {
'use strict';
Polymer({
is: 'sp-referrals-reservations-dropdown',
behaviors: [Polymer.IronControlState],
properties: {
},
listeners: {
'tap': '_close',
'click': '_close',
'blur': '_close',
'focusout': '_close',
'focusChanged': '_close',
'focus-changed': '_close',
'active-changed': '_close',
'activeChanged': '_close',
'iron-activate': '_close',
'ironActivate': '_close',
},
open: function(e) {
},
_close: function() {
console.log('aaa');
this.$.dropdown.style.display = "none";
},
});
})();
</script>
</dom-module>
I am not sure, will it be enough, but if you wrap the sp-referrals-reservations-dropdown element with a parent-element then you can listen to parent-element events same as its child.
<parent-element></parent-element>
<dom-module id="parent-element">
<template>
<style>
:host {
display:block;
background:green;
width:100%;
height:100vh; }
</style>
<sp-referrals-reservations-dropdown id="spref"></sp-referrals-reservations-dropdown>
At parent's script:
Polymer({is:'parent-element', properties:{},
listeners:{ 'tap': '_taped'},
_taped:function(t){
this.$.spref._close();
}
});
this _taped functions will call child's _close function. Hope its help.
Incase of needed more. We can develop this.
Demo
EDIT
Wrap your element into paper-dialog. And at ready:function() call
this.$.dialog.open()
Then when you click outside of the element. paper-dialog will close automatically.
Just FYI, you weren't able to get this to work because custom elements don't listen for events outside of their own encapsulation unless you explicitly wire them up to do so... and if you do so, you can't use Polymer's built-in event handling.
So something like this would work:
// Insert this somewhere it'll get run once attached to the DOM
// This line keeps clicks within your element from closing the dropdown
this.shadowroot.addEventListener('click', (event) => { event.stopPropagation(); });
// And this listens for any click events that made it up to body, and closes the element
document.querySelector('body').addEventListener('click', this._close);
Or, at least, that's what I think was going on. The polymer elements either did it by binding to a different event (blur?) or by having the parent element trigger an event on click that told the child element to close.

Access dom-repeat element's CSS class on tap

My dom repeat displays a list of icons which I can bookmark or unbookmark ,which generating dom-repeat I call a function to find if this icon is bookmarked or not,that will return CSS class
.book-marked {
color: red;
}
.not-book-marked {
color: green;
}
<template is="dom-repeat" items="{{membersList}}">
<iron-icon icon="bookmark" class$="[[_computeBookMark(item.userId)]]" on-tap="_toogleBookMark"></iron-icon>
</template>
Once I get all my list of icon now if user click that icon I need to toogle css class.so I wrote on-tap function
_toogleBookMark:function(e) {
var userData = e.model.item; //gets entire data object of that element
var index = e.model.index; //gets index of that element
},
I can't use ID since its dom-repeat ,Is there any other ways so that I can change CSS of that dom-repeat element in _toogleBookMark() function on clicking? or is it possible to change CSS with index??or using "e" reference in _toogleBookMark(e) function !!
Not sure if I understood correctly - you want to access the element you've tapped?
Just use the event.target property then. It will return the element on which the event happened, in this case, the icon you have tapped.
_toogleBookMark = function(e) {
e.target.classList.toggle("not-book-marked");
}
Check this example.
Mind you:
1) When using Shady DOM, assuming our element is a custom element, target can be a component from the element's template, not the element itself. To prevent that, use Polymer.dom(e).localTarget (read more here).
2) When using a custom element with light DOM children, the above may not be enough, your (local)target will be a light DOM child, not the element you wished for. In that case, use Element.closest(selector) to (optionally) go up the DOM to the element you want. Read more about the method here.
As you just want to swap your class on tap, do it like this:
Add an own attribute, like data-id="1" and the id attribute, but be sure they have the same value:
<template is="dom-repeat" items="{{membersList}}">
<iron-icon icon="bookmark" class$="[[_computeBookMark(item.userId)]]" on-tap="_toogleBookMark" data-id="{{item.userId}}" id="{{item.userId}}"></iron-icon>
</template>
Now, inside your _toggleBookMark function, you can access the current tapped element and swap CSS classes by:
_toogleBookMark:function(e) {
// this gives you your userId from the data-id attribute
var userId = e.path[0].dataId;
// we can access the element now with
var element = this.$$('#' + e.path[0].dataId);
if (element.classList.contains('book-marked')) {
element.classList.remove('book-marked');
element.classList.add('not-book-marked');
} else {
element.classList.add('book-marked');
element.classList.remove('not-book-marked');
}
},

How to set scroll-target on an iron-list within an app-header-layout with has-scrolling-region

I am trying to use an iron-list (and iron-scroll-threshold) within a app-header-layout with has-scrolling-region.
I generated the basic app layout with the polymer-CLI.
If I do not use has-scrolling-region on the app-header-layout and use "document" for scroll-target on the iron-list it kinda works. But with this solution the scrollbar belongs to the window and does not slide beneath the header and I obviously cannot get the nice "waterfall" behaviour that is usually associated with these kinds of layouts.
Therefore, I use has-scrolling-region on the app-header-layout, but what is the right way to pass the corresponding scoller to the scroll-target property of the iron-list?
<!-- Main content -->
<app-header-layout has-scrolling-region id="layout">
<app-header condenses reveals effects="waterfall">
<app-toolbar>
<paper-icon-button icon="menu" drawer-toggle></paper-icon-button>
<div title>Twiamo</div>
</app-toolbar>
</app-header>
<iron-pages role="main" selected="[[page]]" attr-for-selected="name" id="page">
<my-iron-list name="view1" scroll-target="[[_getScrollTarget()]]"></my-iron-list>
<my-view2 name="view2"></my-view2>
<my-view3 name="view3"></my-view3>
</iron-pages>
</app-header-layout>
I looked into the implementation of app-header-layout to find the right element. This expression actually yields me the right element and everything works fine.
_getScrollTarget: function() {
return this.$.layout.shadowRoot.querySelector("#contentContainer");
}
But there has to be a better, a right way? Grabbing into the shadow DOM of the app-header-layout is not exactly using "public interface"!
To complete the example, here my code for my-iron-list. My-iron-list wraps and iron-list, iron-scroll-theshold, and some dummy data provider stuff. The scroll-target on my-iron-list is just passed to the iron-list and iron-scroll-threshold within my-iron-list:
<dom-module id="my-iron-list">
<template>
<iron-list items="[]" as=item id="list" scroll-target="[[scrollTarget]]">
<template>
<div class="item">[[item]]</div>
</template>
</iron-list>
<iron-scroll-threshold
id="scrollTheshold"
lower-threshold="100"
on-lower-threshold="_loadMoreData"
scroll-target="[[scrollTarget]]">
</iron-scroll-threshold>
</template>
<script>
Polymer({
is: 'my-iron-list',
properties: {
page: {
type : Number,
value : 0
},
perPage: {
type : Number,
value : 100
},
scrollTarget: HTMLElement,
},
_pushPage: function() {
for (i = 0; i < this.perPage; i++) {
this.$.list.push('items', 'Entry number ' + (i+1+this.page*this.perPage));
}
},
_loadMoreData: function() {
this._pushPage();
this.page = this.page + 1;
this.$.scrollTheshold.clearTriggers();
}
});
</script>
</dom-module>
I have the same problem as you, for now the cleanest anwser I have was to use the app-header scrollTarget.
In your case move add an id to the app-header
<app-header condenses reveals effects="waterfall" id="header">
<app-toolbar>
<paper-icon-button icon="menu" drawer-toggle></paper-icon-button>
<div title>Twiamo</div>
</app-toolbar>
</app-header>
and then instead of
_getScrollTarget: function() {
return this.$.layout.shadowRoot.querySelector("#contentContainer");
}
just use the scrollTarget property
_getScrollTarget: function() {
return this.$.header.scrollTarget;
}
If you found out a better way let me know.
Cheers,
I struggled with the same issue. While I was using iron-scroll-target-behavior instead of iron-scroll-threshold, I still needed to pass a scroll-target reference to an element inside a app-layout-header.
If has-scrolling-region is true, app-header-layout sets the scroll-target to be an internal div with an ID of #contentContainer. You can target this div and pass it as the scroll-target to your iron-list.
You would just need to alter the _getScrollTarget function inside your original code.
_getScrollTarget: function() {
return this.$.layout.$.contentContainer;
}
Hope it helps!
If anyone is coming here for an answer in 2017, I'm just letting you know that the same issue persists in Polymer 2.0.
I was able to overcome the issue by having the following code in my app shell (eg. PSK's my-app.html):
First, put an id attribute of 'layout' on your app-header-layout element.
Next, add this to your Polymer class (in your my-app.html equivalent):
static get properties() {
return {
scrollTarget: HTMLElement,
}
}
ready() {
super.ready();
this.scrollTarget = this.$.layout.shadowRoot.querySelector("#contentContainer");
}
Then, pass in the property to a scroll-target attribute on your lazy-loaded pages:
<my-page scroll-target="[[scrollTarget]]"></my-page>
Finally, in your lazy-loaded pages (eg. my-page):
<iron-list scroll-target="[[scrollTarget]]"></iron-list>
...
static get properties() {
return {
scrollTarget: HTMLElement,
}
}
This isn't an ideal solution, but it works.

Struggling to understand the subtlety of relectToAttribute

I am struggling to understand the subtlety of reflectToAttribute on a Polymer elements property.
I have an pair of elements for transmitting values around the dom tree like iron-meta which I have called akc-meta and akc-meta-query. In my test fixture I am doing this
<test-fixture id="basic-test">
<template>
<template is="dom-bind" id=app>
<akc-meta key="[[key1]]" value="{{value1}}" id="meta1"></akc-meta>
<akc-meta-query key="[[key2]]" value="{{value2}}" id="meta2"></akc-meta-query>
<akc-meta-query key="[[key3]]" value="{{value3}}" id="meta3"></akc-meta-query>
<akc-meta key="[[key4]]" value="{{value4}}" id="meta4"></akc-meta>
</template>
</template>
</test-fixture>
and in my test suite I can set values like this
app.key1 = 'keya';
app.key2 = 'keya';
app.key3 = 'keya';
app.value1 = 'This is a multiple query test';
expect(app.value2).to.equal('This is a multiple query test');
expect(app.value3).to.equal('This is a multiple query test');
app.value1 = 'New Value';
expect(app.value2).to.equal('New Value');
expect(app.value3).to.equal('New Value');
where these elements transmit values under the hood between the elements when the keys are the same.
Neither of the elements use reflectToAttribute on any of the properties, although the value property of akc-meta-query does use notify:true
So what does reflectToAttribute actually do and why do you need it?
I have created a small example in this jsbin.
<style>
x-test {
display: block;
}
x-test[prop1="initialvalue1"] {
border: 5px solid yellow;
}
x-test[prop2="initialvalue2"] {
background: green;
}
x-test[prop1="newvalue1"] {
border: 5px solid black;
}
x-test[prop2="newvalue2"] {
background: red;
}
</style>
<dom-module id="x-test">
<template>
<div>{{prop1}}</div>
<div>{{prop2}}</div>
<button on-click="update1">Update Prop1</button>
<button on-click="update2">Update Prop2</button>
</template>
</dom-module>
<script>
HTMLImports.whenReady(function() {
Polymer({
is: 'x-test',
properties:{
prop1: {
type:String
},
prop2: {
type:String,
reflectToAttribute: true
},
},
update1: function(){
this.prop1 = "newvalue1";
},
update2: function(){
this.prop2 = "newvalue2";
}
});
});
</script>
<x-test prop1="initialvalue1" prop2="initialvalue2"></x-test>
The element here has two properties. prop1 is not reflected to the attribute, but prop2 is. Both are set to an initial value. If you open the developer tools, you will see something like this:
There is an update button to change either prop1 or prop2. If you click each button and update both properties, you will get this picture
As you can see, prop1 still has its old state. The state change has not been reflected back to the attribute. In contrast, prop2 has been reflected and has changed.
Now if you access either property through JavaScript, you will get the updated value. However, when you access the HTML attribute, in case of prop1 you will not.
A use case for this could be when you have CSS rules with attribute selectors as in my example. The selector x-test[prop1="newvalue1"] does not apply when prop1 is updated. On the other hand x-test[prop2="newvalue2"] does apply after the update of prop2, because it is configured to reflect to attribute.

Polymer - Creating a list of custom elements within a custom element

I am trying to get my head around Polymer. Right now I have successfully created a custom news element (my-newsbite) and can populate and display attribute data from the page calling the custom element.
The next step is to create a second custom element (my-newsbite-list), that takes multiple news items in the form of a json object from attributes in the calling page and displays a list of the my-newsbite custom elements.
For some reason I am unable to get it to work.
My test index.php page calls the my-newsbite-list like this...
<div style="width:200px;">
<my-newsbite-list news="[{title: 'title here',date:'12 July 2014',content:'content here',url:'http://www.frfrf.com'}]">
</my-newsbite-list>
</div>
The my-newsbite-list custom element looks like this...
<link rel="import" href="../components/bower_components/polymer/polymer.html">
<polymer-element name="my-newsbite-list" attributes="news" >
<template >
<template repeat="{{item in news}}">
<my-newsbite title="item.title" date="item.date" content="item.content" url="item.url">
</my-newsbite>
</template>
</template>
<script>
Polymer('my-newsbite-list', {
created: function() {
this.news = []; // Initialize and hint type to an object.
}
});
</script>
</polymer-element>
And finally my custom element my-newsbite looks like this...
<link rel="import" href="../components/bower_components/polymer/polymer.html">
<polymer-element name="my-newsbite" attributes="title date content url width">
<template>
<div class="my-newsbite-title">
{{title}}
<div class="my-newsbite-date">{{date}}</div>
</div>
<div class="my-newsbite-content">{{content}}...</div>
<div class="my-newsbite-url">more...</div>
<style>
:host {
/* Note: by default elements are always display:inline. */
display: block;
background-color:#FFFFFF;
font-fam
}
.my-newsbite-title {
/* Note: by default elements are always display:inline. */
display: block;
background-color:#06F;
color:white;
padding:3px;
}
.my-newsbite-content{
padding:3px;
}
.my-newsbite-date{
padding:3px;
font-size:8pt;
text-align:right;
float:right;
}
.my-newsbite-url{
padding:3px;
}
</style>
</template>
<script>
Polymer('my-newsbite', {
title: "****",
date: "****",
content: "****...",
url: "http://****.com",
});
</script>
</polymer-element>
The end result of all this is a blank rendered index page.
Can anyone see why the json object is not rendered with in the my-newsbite-list custom element ?
Thanks in advance.
I had already tried ebidel's suggestion but that didn't solve it.
Scott Miles is correct...
Polymer is very fussy about the JSON format passed in the attribute. (I don't know how to give you credit for the answer from a comment.)
So, in the index.php page the code looks like this (Note the double quotes around both the key names and the values)...
<my-newsbite-list news='[{"title":"sdfg","date":"12 July 2014","content":"sdfg sdfg sdfg sdfg sdfg sdfg sdf","url":"http://www.frfrf.com"}]'>
</my-newsbite-list>
The custom element Polymer javascript looks like this...
Polymer('my-newsbite-list', {
news: [],
ready: function(){
}
});
Thanks for all the suggestions.
The problem seems to lie in the way the json object in the calling attribute is received by the custom element. Adding the following code to the Polymer ready function fixes the issue.
Polymer('my-newsbite-list', {
ready: function(){
this.news = eval(this.news) || [];
}
});
This solution does work, but the correct method is shown in the answer above.