Have computed binding fire when array sub property changes? - polymer

Polymer 1.*
I am trying to refine the behavior of my computed binding with a array. I had <div hidden$="[[getState(uploadState.*)]]">FOO</div> but it was firing off to often.
I refined it to uploadState.value:
<template is="dom-repeat"
initial-count="1"
index-as="index"
items="{{uploadState}}">
<div hidden$="[[getState(uploadState.value)]]">FOO</div>
With:
uploadState: {
type: Array,
value: function() {
var arr = Array.apply(null, Array(5));
var newArray = arr.map(()=> {
return {
value: false,
main: false,
edited: false,
loading: false
};
});
return newArray;
},
notify: true
},
attached: function() {
setTimeout(()=> this.set(`uploadState.0.value`, true), 1000)
}
but it does not fire off at all. How can I make it fire in the computed binding when the value property changes?
Also, how can I use this.get() to get the value when it changes? I tried var uploaded = this.get(['uploadState.value', 0]) in the computed binding getState but it just shows undefined(when it used to fire with the .*)

The problem with your usage of the binding uploadState.value is it doesn't exist. You are making an array of uploadState that have a member with the property value which looks more like uploadState.*.value but you don't really want to change on all the changes of value, just the one in question so you can take advantage of the item binding of dom-repeat so that your code would come out like so:
<template is="dom-repeat"
initial-count="1"
index-as="index"
items="{{uploadState}}">
<div hidden$="[[item.value]]">FOO</div>
</template>
I might suggest you change up your naming convention and use uploadStates being it's an array and all, so that you can do:
<template is="dom-repeat"
initial-count="1"
index-as="index"
items="{{uploadStates}}"
as="uploadState">
<div hidden$="[[uploadState.value]]">FOO</div>
</template>

Related

Computed Binding Not Detecting Change in Iron List

Polymer 1, I have in iron-list:
<iron-list
id="ironList"
scroll-target="[[ironListScrollTarget]]"
items="[[itemCollectionCopy]]">
...
<div class="text center-justified info-icon">
<iron-icon
hidden="[[!_isDirtyData(item.*, itemCollectionCopy)]]"
role="img"
aria-label="Check-out information has been modified"
title="Check-out information has been modified"
icon="icons:info-outline"></iron-icon>
<iron-icon
hidden="[[_isDirtyData(item.*, itemCollectionCopy)]]"
role="img"
class="pristine-data"
aria-label="Check-out information has been modified"
title="Check-out information has been modified"
icon="icons:info-outline"></iron-icon>
</div>
_isDirtyData: function(item) {
console.log(item);
return item.base.preferences;
},
setCustomPreference: function(e) {
const id = e.detail.data.clientId;
const preferences = e.detail.data.preferences;
const foo = this.itemCollectionCopy.map((item) => {
if (item.client_id === Number(id)) {
item.preferences = preferences;
}
return item;
});
this.itemCollectionCopy = [];
this.itemCollectionCopy = [...foo];
},
When adding a preferences object to item.preferences, I could not get the computed binding hidden="[[_isDirtyData(item.*)]]" to detect a change. Instead, I had to add the whole array itemCollectionCopy in hidden="[[_isDirtyData(item.*, itemCollectionCopy)]]" which seems excessive.
Why wouldn't the computed binding detect a change with just hidden="[[_isDirtyData(item.*)]]"?
The way polymer is observing changes in property is not perfect, and they added some array mutations methods to change array and notify templates of this changes.
You can use them if they work for your particular case.
https://polymer-library.polymer-project.org/1.0/docs/devguide/model-data#work-with-arrays
Sometime you want to perform operation in a JS way, or just there is no array mutation method adapted for your case.
There is a notifyPath method which can help also.
this.notifyPath('myarray.*');
for example will trigger all corresponding observers.

Property change not reflecting in UI when its set from dom-repeat's function - Polymer

I have array of objects and a property, my dom-repeat structure is like as below
<template is="dom-repeat" items="{{arrayOfObj}}"> //first dom repeat
<span>[[myProperty]]<span> //here also its not updating
<template is="dom-if" if="[[_checkSomeCondition()]]"> //calling method from dom-if
<span>[[myProperty]]<span> //here its not getting updated value
</template>
</template>
I have a property
properties:{
myProperty:{
type:Boolean
}
}
my function is called each time when dom-repeat iterates
_checkSomeCondition:function() { //I'll check and set property
if(some condition){
this.myProperty = true;
return true;
}
else{
this.myProperty = false;
return true;
}
console.log(this.myProperty); //I'll get the updated value on console
}
but its not changing in screen!! It will display whatever data it set first time inside _checkSomeCondition !! but in console its updating
For testing I inserted a button and after all dom-repeat rendered on tapping that button I called some function ,there when I changed value it get reflected everywhere
this.myProperty = true;
but why its not working when value is changed inside a function which is called by dom-repeat?? I tried all 3 ways of updating a object
Plunker:https://plnkr.co/edit/iAStve97dTTD9cv6iygX?p=preview
Setting a variable via this.myValue = 'somevalue'; won't update binding.
Its best to set variables via this.set('variablename', 'variablevalue');
You could also, after setting a property via this.variablename = 'variablevalue'; this.notifyPath('variablename');
Try
<template is="dom-if" if="[[_checkSomeCondition(myProperty)]]"> //calling method from dom-if
<span>[[myProperty]]<span> //here its not getting updated value
</template>
And then
_checkSomeCondition: function(property) {
I have modified your code like below. Im not sure if it helps for your senario. Please have a look may help to bring up ideas
<template is="dom-repeat" items="{{sensor.Sensor.Contaminants}}" index-as="index">
<span>{{myProperty}}<span> //here also its not updating
<div>{{_checkSomeCondition(index)}}<div> //here its not getting updated value
</template>
And your _checkSomeCondition method will be:
_checkSomeCondition: function() { //I'll check and set property
console.log(index);
if(<your condition>){
this.myProperty = 'true';
} else{
this.myProperty = 'false';
}
// Return whatever you want to show in UI
return this.myProperty;
console.log(this.myProperty); //I'll get the updated value on console
},
and your myProperty should notify the changes. So the property should have notify: true.
With this code i could see the changes in the UI as expected. Let me know if it helps

Why use function wrap for Polymer property value of type object?

When initializing a property to an object or array value, use a function to ensure that each element gets its own copy of the value, rather than having an object or array shared across all instances of the element.
this is from the official polymer document my question here is why to not to share this default value across multiple instance as this default value will only be called once during initialization ??
<dom-module id="title-element">
<template>
<h1 style$="background-color: {{backgroundColor}}">{{config.title}}</h1>
</template>
<script>
Polymer({
is: 'title-element',
properties: {
config: {
type: Object,
notify: true,
value: {
title: 'This is my element',
}
},
backgroundColor: String,
},
ready: function () {
this.addEventListener('config-changed', function () {
console.log('config had changed');
this.querySelector('h1').style.backgroundColor = 'blue';
})
}
})
</script>
</dom-module>
<title-element background-color="yellow"></title-element>
<title-element background-color="green"></title-element>
in the above example i tried to change the value of config.title by selecting that element in chrome console and change it once using $0.config = {"title":"any other"} and also using notifyPath and as expected it changed only in the selected element not all instances
what is the purpose of using function wrap then ?
So that every element gets it own copy instead of sharing it.
If you provide a function, Polymer calls the function once per element instance.
When initializing a property to an object or array value, use a
function to ensure that each element gets its own copy of the value,
rather than having an object or array shared across all instances of
the element.
Here's the link to documentation
Here's a simple test case to depict the same.
<link rel="import" href="http://polygit.org/components/polymer/polymer.html">
<dom-module id="shared-object">
<template>
<style></style>
<div on-tap='changeValue'>Shared Object: {{shared.key}}</div>
<div on-tap='changeValue'>Not Shared Object: {{notShared.key}}</div>
</template>
</dom-module>
<script>
Polymer({
is: 'shared-object',
properties: {
shared: {
type: Object,
value: {
key: 'value'
}
},
notShared: {
type: Object,
value: function() {
return {
key: 'value'
}
}
}
},
changeValue: function() {
this.set('shared.key', 'value1');
this.set('notShared.key', 'value1');
}
})
</script>
Instance one
<br>
<shared-object id='obj1'></shared-object>
<br>
<br>Instance two
<br>
<shared-object id='obj2'></shared-object>
<script>
document.querySelector('shared-object').addEventListener('tap', function() {
console.log('First instance:\nshared value:', document.querySelector('#obj1').shared, '\nnot shared value:', document.querySelector('#obj1').notShared);
console.log('second instance:\nshared value:', document.querySelector('#obj2').shared, '\nnot shared value:', document.querySelector('#obj2').notShared);
})
</script>
After you tap on any of the value you'll notice that even though the display values are correct for all the cases but in console shared object has same value for both the instances, whereas notSharedObject has different value even in console.

Polymer - Updating Element

I'm learning Polymer. One item that is challenging me is updating the item of an array. I wish there was a CDN for Polymer so I could put together a fiddle. For now though, I have an element defined like this:
my-element.html
<dom-module id="my-element">
<button on-click="onLoadData">Load Data</button>
<button on-click="onTest1Click">Test 1</button>
<button on-click="onTest2Click">Test 2</button>
<template is="dom-repeat" items="[[ data ]]" as="element">
<div><span>[[ element.id ]]</span> - <span>[[ element.status ]]</span></div>
<template is="dom-repeat" items="[[ element.children ]]" as="child">
<div> <span>[[ child.id ]]</span> - <span>[[ child.status ]]</span></div>
</template>
</template>
</template>
<script>
Polymer({
is: 'my-element',
properties: {
data: {
type: Array,
value: function() {
return [];
}
}
},
onLoadData: function() {
// Generate some dummy data for the sake of illustration.
for (var i=1; i<=3; i++) {
var element = {
id: i,
state: 'Initialized',
description: ''
};
element.children = [];
for (var j=1; j<=5; j++) {
var child = {
id: i + '-' + j,
state: 'Initialized',
description: ''
}
element.children.push(child);
}
data.push(element);
}
},
// Setting an individual property value works
onTest1Click: function() {
this.set('data.0.children.0.state', 'data set');
},
// Setting an entire object value does NOT work.
onTest2Click: function() {
var c = this.data[0].children[0];
c.state = 'data set';
this.set('data.0.children.0', c);
}
});
</script>
</dom-module>
For some reason, if I update the property value of an array element (as shown in onTest1Click), the UI is updated properly. However, if I update an entire element (as shown in onTest2Click), the UI does NOT get updated. In my real problem, I'm updating multiple properties on an element. For that reason, I'm trying to update an array element and not just a property. Am I doing something wrong or misunderstanding something? Or, am I going to have to update each property value individually?
If you want to mutate an array, rather than just an object in an array (such as swapping out an entire element in an array), there are array mutation methods similar to this.set.
For example, this.splice('data.0.children', 0, 1, c) will remove the current item at the 0 index of the child array and replace it with a new one, which is what it appears you're trying to do. There's also this.shift, this.unshift, this.push and this.pop. These are all similar to their Array prototype counterparts.
One thing to note is that in your example, you're also not actually swapping out the entire object. When you grab the element from the array, mutate a field, and try and replace it with itself, you're not actually replacing it, so that doesn't actually trigger an update. And since the mutation of the field was done outside of Polymer's notification system, that also doesn't trigger an update. If you replace the item with an actual different object, it will work using splice.
https://jsbin.com/rapomiyaga/1/edit?html,output (This is a modified snapshot of Günter Zöchbauer's jsbin)
If you're not making a copy of the object/a completely new object, you'll want to update each field individually through this.set.
Yes, you are going to need to update each property value individually. When you call set, Polymer will go to the given path and check if the value has changed. If the value is an object it will compare the references (and not the subproperties). Since the object reference has not changed, it will not update the UI.

polymer template repeat field names

How can I dynamically get all the fields of this rows array objects using a polymer repeat template?
rows = [{
"field1":"test1",
"field2":"test2",
"field3":"test3",
"field4":"test4"
}]
<template repeat="{{data in rows}}">
{{data.field1}}
{{data.field2}}
{{data.field3}}
{{data...}}
</template>
This plunker shows two examples:
How to refresh a repeat using a filter parameter. Less overhead and
simple to implement. It's recommended to use event actions over
observers where possible,
How to observe an Object as an Array (based from this issue comment
Polymer/polymer-expressions#11 (comment))
<!-- parameters passed to filters are observed, so changing refresh updates the repeat -->
<template repeat="{{key in objectData | toKeys(refresh)}}">
{{ objectData[key] }}
</template>
Polymer({
refresh: 0, // update this value to refresh the repeat
toKeys: function(input) {
if (!input) return;
return Object.keys(input);
}
});
Dodgy request...
I tried and my best luck was something like this:
<polymer-element name="x-for-in">
<template>
<template repeat="{{field in keys}}">
{{obj[field]}}
</template>
</template>
<script>
Polymer({
obj : null,
keys : [],
created : function () {
this.obj = {
field1 : 1,
field2 : 2,
field3 : 3
};
this.keys = Object.keys(this.obj);
}
})
</script>
</polymer-element>
OUTPUT : 1,2,3
BTW: I'm not 100% sure what you meant in description, because your code says rows - is array of objects, while you're saying rows is an object....
Anyway so far so good, example above should give an idea.
BTW2: {{data is rows}} where rows is an object - will not work, because it's essentially a javascript for in. polymer restricts such things as I understand