Struggling to understand the subtlety of relectToAttribute - polymer

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.

Related

Polymer 2.0: Notify and reflect to attribute

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.

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');
}
},

Constructing dynamic lists of lists with Polymer

I am trying to use Polymer and am struggling to figure out how to create my system in a Polymer-y way.
I'd like a left nav bar (a column on the left side), as shown in the example app docs. For the purposes of the explanation, the left nav bar shows a list of people and the main content window shows a list of books they own. When a person is selected, the main content window should be updated to show only that person's books.
Both lists should be dynamically populated via a network query and be able to be re-populated via a refresh button. It seems like this might complicate how I bind data, as the functions can't just live in a the shadowdom, as the nav bar and content lists need to be able to communicate.
From the day I've spent looking into Polymer, the nav bar (list of people) seems a natural candidate for <iron-selector>. The list of books sounds like <iron-list>. I'll need to have several instances of the book list, however--one for each person.
Should I create a <book-list> custom element for this, perhaps with <iron-list> in the template? Is it straightforward to create these <book-list> elements in response to selections made in <iron-selector>, as I won't know how to create them before querying for book owners? Any pointers on how to do this?
This sounds like a very basic use case, but I still don't know enough about Polymer to have any intuitions about the best way to do it. Any help would be much appreciated.
I eventually got this working. As a starting point, I leaned heavily on the example given in the docs that uses the app-drawer-template.
As suggested by a commenter above, using a router ended up being part of the answer. What was confusing me was that I wan't sure how to dynamically update both the <iron-selector> used in that example as well as the <iron-pages>. I wanted both to represent the same list of users. How would I keep these in sync, etc?
I ended up with three custom elements. Two were thin wrappers around <iron-selector> and <iron-pages>. The third was a <user-provider> element. The role of <user-provider> is to perform the request for the list of users and expose the result as a variable that the other two elements can interact with.
<user-provider> has only a refresh button. It has a property called users of type Array. In the ready() lifecycle callback and in response to clicking the refresh button, it updates the list of users. Note that reflectToAttribute and notify are both set to true, ensuring that it can indeed provide the list to the other elements. A stripped down version is shown below:
<dom-module id="user-provider">
<template>
<style>
:host {
display: block;
box-sizing: border-box;
</style>
<paper-fab icon="refresh" on-tap="refresh"></paper-fab>
</template>
<script>
Polymer({
is: 'user-provider',
properties: {
users: {
type: Array,
notify: true,
reflectToAttribute: true
}
},
refresh: function() {
this.refreshUsers();
},
refreshUsers: function() {
var _this = this;
// in reality I require a browserify module here
window.getUsersViaPromise()
.then(retrievedUsers => {
_this.users = retrievedUsers;
});
},
// Element Lifecycle
ready: function() {
this.users = [];
this.refreshUsers();
}
});
</script>
</dom-module>
My <user-selector> element takes an array as an argument. The thing that was stumping me here was that I didn't realize how simple it was to use a dom-repeat template inside of an <iron-selector>. I thought I'd have to wrap the repeats in a <div> or something, at which point the <iron-selector> would stop working. With dom-repeat I'm able to even use my special settings element and keep it always at the bottom of the list. The incoming variable corresponds to the element that is selected.
<dom-module id="user-selector">
<template>
<style>
:host {
display: block;
--app-primary-color: #4285f4;
--app-secondary-color: black;
}
</style>
<iron-selector
selected="{{incoming}}"
attr-for-selected="name"
class="drawer-list"
role="navigation">
<template is="dom-repeat" items="{{users}}">
<a name="view{{index}}">{{item.instanceName}}</a>
</template>
<a name="settings">Settings</a>
</iron-selector>
</template>
<script>
Polymer({
is: 'user-selector',
properties: {
incoming: {
type: String,
reflectToAttribute: true,
notify: true
},
users: {
type: Array,
reflectToAttribute: true
},
}
});
</script>
</dom-module>
<user-pages> is a thin wrapper around <iron-pages>. It works together with <user-selector> to update the content displayed in the main portion of the app.
<dom-module id="user-pages">
<template>
<style>
:host {
display: block;
box-sizing: border-box;
}
</style>
<iron-pages role="main" selected="{{incoming}}" attr-for-selected="name">
<template is="dom-repeat" items="{{users}}">
<user-summary
name="view{{index}}"
username="{{item.userName}}">
</user-summary>
</template>
<settings-view name="settings"></settings-view>
</iron-pages>
</template>
<script>
Polymer({
is: 'user-pages',
properties: {
users: Array,
incoming: {
type: String,
reflectToAttribute: true,
notify: true
},
}
});
</script>
</dom-module>
And, finally, my <my-app> element plumbs it all together.
<dom-module id="my-app">
<template>
<style>
:host {
display: block;
--app-primary-color: #4285f4;
--app-secondary-color: black;
}
app-header {
background-color: var(--app-primary-color);
color: #fff;
}
app-header paper-icon-button {
--paper-icon-button-ink-color: white;
}
.drawer-list {
margin: 0 20px;
}
.drawer-list a {
display: block;
padding: 0 16px;
line-height: 40px;
text-decoration: none;
color: var(--app-secondary-color);
}
.drawer-list a.iron-selected {
color: black;
font-weight: bold;
}
.drawer-list a.subroute {
padding-left: 32px;
}
</style>
<app-route
route="{{page}}"
data="{{routeData}}"
tail="{{subroute}}"></app-route>
<app-drawer-layout fullbleed>
<!-- Drawer content -->
<app-drawer>
<app-toolbar>My App</app-toolbar>
<start-app-button></start-app-button>
<user-provider users="{{retrievedUsers}}"></user-provider>
<user-selector incoming="{{page}}" users="{{retrievedUsers}}">
</user-selector>
</app-drawer>
<!-- Main content -->
<app-header-layout has-scrolling-region>
<app-header condenses reveals effects="waterfall">
<app-toolbar>
<paper-icon-button icon="menu" drawer-toggle></paper-icon-button>
<div title>My App</div>
</app-toolbar>
</app-header>
<user-pages
incoming="{{page}}"
users="{{retrievedUsers}}">
</user-pages>
</app-header-layout>
</app-drawer-layout>
</template>
<script>
Polymer({
is: 'my-app',
properties: {
page: {
type: String,
reflectToAttribute: true
},
},
observers: [
'_routePageChanged(routeData.page)'
],
_routePageChanged: function(page) {
this.page = page || 'view1';
}
});
</script>
</dom-module>
Note in particular the way that <user-provider> is responsible for obtaining the list of users and exposing it just via a variable, allowing the other two elements to make obtain the data via basic databinding. That, along with the dom-repeat elements as immediate children of <iron-selector> and <iron-pages>, allowed this to be pretty straight forward once I started thinking of it in the Polymer way.

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.

knockoutjs css binding value when boolean computed property is false/true

I am binding my currently selected item like this:
The selected item gets visible, but how can I give the unselected items an unselected/default background color? When I set a background color to the template class I do not see anymore the background color set from the selection via mouse click.
Is the problem maybe that I have 2 backgrounds set but none is removed?
<div data-bind="foreach: items">
<div class="cellContainer" >
<div class="template" data-bind="click: $parent.selectItem, css: { selected: isSelected }">
<div data-bind="text: number"></div>
</div>
</div>
</div>
.selected {
background-color: #0094ff;
}
Sounds like a cascade issue. Make sure your ".template" style is defined before your ".selected" style in your css file.
Make sure your selectItem method resets isSelected on all elements before setting it to true on the argument. A naive implementation could be:
var ViewModel = function() {
// Skipped the rest for brevity...
self.selectItem = function(item) {
// Deselect all items first
for (var i = 0; i < self.items().length; i++) {
self.items()[i].isSelected(false);
}
// Select the argument
item.isSelected(true);
};
};
See this jsfiddle for a demo.
However, it is often easier to keep a reference on the parent view model to the currently selected item, and change the css binding to something like:
css: { selected: $parent.TheOneSelectedItem().number() == number() }
See this fiddle for the alternate demo.
Something like this might work.
.template {
background-color: #fff;
}
.template.selected {
background-color: #0094ff;
}
It does not look like a knockout issue.