I have a collection of objects which have a name property.
For editing these objects I have a paper-listbox that lists the names of the objects and lets the user select one of the objects by name. Next to the listbox I have a form with paper-inputs etc to edit properties of the selected object. One of the paper-inputs binds to the name property of the selected object so the user can change the name.
My problem is that the changes to the name property are not reflected to the listbox. How do I update the listbox after the user has changed the name?
I confirmed that the name change actually happens because when I change to another object and back to the previous one the changed name is still there.
The problem is just that the listbox does not update.
I tried something like:
this.notifyPath("myObjects")
but that doesn't do anything.
The paper-listbox is created like this
<paper-listbox selected="{{selectedObjectIndex}}">
<template is="dom-repeat" items="[[myObjects]]">
<paper-item>[[item.name]]</paper-item>
</template>
</paper-listbox>
selectedObjectIndex has an observer that sets the selected object
selectedPageIndexChanged(newValue, oldValue) {
...
this.selectedObject = this.myObjects[this.selectedObjectIndex];
}
Here below some working and not working examples. (I tried to illustrate the code at codepen as show DEMO, here also part of the code in order to quick inspection.
<paper-listbox selected="{{selectedObjectIndex}}">
<template is="dom-repeat" items="{{myObjects}}" >
<paper-item>[[index]].[[item.name]]</paper-item> <br/>
</template>
</paper-listbox>
<paper-input value="{{selectedObject.name}}" ></paper-input>
....
selectedObjectIndex:{
type:Number,
observer:'selectedPageIndexChanged'
}
}}
static get observers() {return ['nameValueChanged(selectedObject.name)']}
constructor() {
super();
}
ready() {
super.ready();
}
selectedPageIndexChanged(newValue, oldValue) {
this.selectedObject = this.myObjects[newValue];
}
nameValueChanged(name){
//this.myObjects[this.selectedObjectIndex].name = name;
//this.set('myObjects.' + this.selectedObjectIndex + ".name", name ) // --> Does not work
this.set('myObjects.' + this.selectedObjectIndex, { name: name} ) // --> Works
// this.splice('myObjects', this.selectedObjectIndex,1, this.selectedObject) -- Does not work
//this.notifyPath('myObjects.' + this.selectedObjectIndex + ".name") // -- works (with one of above)
}
}
The above signed Does not work lines changes the object but not observable at dom-repeat
Related
I am working on a dashboard, in which I have a search panel at the top (let's call it component A), where users can enter a query. The value of this input will change a lot of other components in the dashboard (not only components that are its direct descendants or siblings). I want to send the search value from component A to component B, which should then respond by performing some action with the input value.
I have tried a few things:
Directly calling the function in component B. Haven't been able to get that to work at all.
Manually setting B's local property value and using an observer to trigger a function call. I manager to set the value, but the observer does not trigger.
Using a global variable, which I can easily access across components, but I still can't trigger functions in specific components.
How can I best do this?
I'm relatively new to Polymer, so forgive me if my ideas aren't completely 'Polymerised' :)
Approach 1
<dom-module id="component-B">
<template>
</template>
<script>
Polymer({
is: 'component-B',
properties: {
id: '',
observer: '_idUpdate'
},
_idUpdate: function(){
console.log("HELLO");
}
});
</script>
</dom-module>
<dom-module id="component-A">
<template>
</template>
<script>
Polymer({
is: 'component-A',
idSearch: function() {
var id = this.$.search.value;
document.querySelector('component-B').properties.id = id;
},
});
</script>
</dom-module>
As you want to send data to multiple elements (which might not be siblings of the firing element) you can use any of these two methods
Use iron-signal to fire the signal and then in all the elements where you want the data use iron-signal tag to listen to the signal
<iron-signals on-iron-signal-<signal-name>="<function>"></iron-signals>
You can also use standard HTML method dispatchEvent to fire a signal and then add eventListeners in all the element where you want data.
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
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.
I have yet another question:
Why is that if I repeat over a list of objects from a template the element in the template cannot see the object when used declaratively?
artists = [ {name: 'whatever',..},...];
Example:
<template repeat="{{a in artists}}">
<artist-record model="{{a}}"></artist-record>
</template>
If I do the same imperatively it works
Example
artists.forEach(function(a) {
var node = document.createElement('artist-record');
node.model = a;
this.$.container.appendChild(node);
}, this);
Is there a way to do this declaratively?
This works for me: http://jsbin.com/zemawugu/1/edit
Check that your array assignment is to this.artists and not artists. The binding system needs that array to be on the element prototype for the template to see it.
I am building a custom element to read files in a given directory (e.g. ini files from a /config directory) and display them as a list wrapped in a core-selector. The user can then select a file from the list.
All works fine except reading the selected data seems clumsy. The relevant code is:
<div>
<core-selector on-core-activate={{getFileSelected}} selected="">
<template repeat="{{file in files}}">
<rnc-commandfilelist>
<span class="rnc-fileindex">{{file.index}}</span>
<span class="rnc-filename">{{file.commandFileName}}</span>
</rnc-commandfilelist>
</template>
</core-selector>
</div>
</template>
<script>
Polymer('rnc-getscaffoldini', {
matchstring: ".ini",
configurationdirectory: "configuration",
getFileSelected: function (e, detail, sender) {
var fileSelected = detail.item.children[1].innerText;
console.log(fileSelected);
var fullPath = this.configurationdirectory + "/" + fileSelected;
this.setAttribute('selectedfilename', fileSelected);
this.setAttribute('selectedfullpathname', fullPath);
}
});
</script>
The code line:
var fileSelected = detail.item.children[1].innerText;
gets the selected file name okay. Is there a better way of getting the selected data fields back?
The selectedModel property published by core-selector refers to the selected data model. The repeat syntax here repeat="{{file in files}}"> means that each item has a data model that contains the scope data plus a property called file. That means for each item, the selected file is stored in the selector as selectedModel.file. You can use binding to access the data.
So, if you do:
<core-selector selectedModel="{{selectedModel}}">
then you can have:
selectedModelChanged: function() {
// this.selectedModel.file refers to the particular file that is selected
}
--
Fwiw, you could also structure it this way:
<template repeat="{{files}}">
<rnc-commandfilelist>
<span class="rnc-fileindex">{{index}}</span>
Because of the different repeat syntax, now the data model is simply the file record, so:
<core-selector selectedModel="{{selectedFile}}">
then you can have:
selectedFileChanged: function() {
// this.selectedFile refers to the particular file that is selected
}
http://jsbin.com/putecu/1/edit