Unable to observe an array of objects in Ember.js? - javascript

I have an array of objects within a component like this:
checkboxes: [
{
label: 'A',
state: false
},
{
label: 'B',
state: false
},
{
label: 'C',
state: false
}
]
In my Handlebars template, I have a loop to show the list of checkboxes and each checkbox state maps back to the array item like this:
{{#each checkboxes as |item|}}
<li><input type="checkbox" checked={{item.state}}> {{item.label}}</li>
{{/each}}
I wrote an observer to observe changes to the array but it doesn't seem to work:
observeCheckboxes: function() {
console.log(this.get('checkboxes').toArray());
}.observes('checkboxes.[]')
How do I observe changes to the checkbox states so I can get a list of checked items?

Change your observer to observe checkboxes.#each.state.
Have a look through the documentation here for a complete overview.
If possible, try to avoid using an observer, and rather use a computed property, and then also to rather define computed properties and observers as Ember.computed & Ember.observer.
Here is a twiddle that demonstrates why your observer is not firing.

Related

Is there a way to add properties or component data to looped objects with Alpine.js?

Essentially I'm receiving JSON from the server and would like to add properties to the objects I'm looping or the x-data of the component, is this possible with alpine.js?
The data is something that will be requested on an interval and looks something like:
let teams = [
{ id: 1, name: 'Person 1'},
{ id: 2, name: 'Person 2'},
...
];
What I'd like to do is add a new property favourite to the looped team object, with default set to false or add it as component data. I plan to use this property to filter the list later on. At the moment I have:
<template x-for="team in teams" :key="team.id">
<tr>
<td x-text="team.name"></td>
<td>
<input type="checkbox" x-model="favourite">
</td>
</tr>
</template>
I have tried a few things to get this working, such as using:
x-data="{ team: team, favourite: false }"
On both the tr and template tag as well as without passing the team object to the x-data directive. It seems like there is a clash between the x-for and x-data directives as this throws an error: Uncaught ReferenceError: team is not defined
I have also tried using x-init alongside the x-data directive to no avail.
At this stage I'm wondering if I'll have to switch to another JS library to get this working (recommendations welcome if necessary) or if my approach is fundamentally wrong.
Any help/pointers would be much appreciated.
In principle what you should be doing is adding the "favourite" field before it gets to the x-for.
For example if you're loading teams using fetch you could do:
<div
x-data="{ teams: [] }"
x-init="setInterval(() => {
fetch('/teams-url').then(res => res.json()).then(teamsData => {
teams = teamsData.map(team => ({ favourite: false, ...team }));
});
}, 1000);"
>
I also noticed that your example is using v-model and v-text, which should be x-model and x-text respectively.

Vue reactivity issue, need some explanations

I've removed most useless parts of my code, so don't worry if this code doesn't really make sense, it's just to show you what's not working.
First, I create an array from a base array called objects:
objects: [
{
text: "I dont trigger stuff",
},
{
"text": "I dont trigger stuff",
},
{
text:"I trigger stuff",
activated: undefined,
},
],
And the create function
created() {
const newArray = [];
this.objects.forEach(anObj => {
anObj.activated = false;
newArray.push(anObj);
});
this.filteredObjects = newArray;
},
I initialize a property activated to false. In my real code I'm not using a forEach, but a find but the result is the same.
Then, I display a few buttons to trigger an "activation"
<button
v-for="(myObj, index) in filteredObjects"
:key="index"
#click="activateObject(myObj, index)">
{{ myObj.text }}
</button>
And the function being triggered is this one:
activateObject(anObj, anObjIndex) {
this.$set(this.filteredObjects[anObjIndex], 'activated', !anObj.activated)
},
My goal here is just to update the activated property.
To check if reactivity is working, I've got a watcher:
watch: {
filteredObjects: {
handler() {
alert('called')
},
deep: true,
}
},
I've got two questions:
1/ Since all activated properties are set to false for all objects, why is there only one working, the one with the property initially set to undefined?
2/ If I update my activation function to:
activateObject(anObj, anObjIndex) {
anObj.activated = !anObj.activated;
this.$set(this.filteredObjects, anObjIndex, anObj);
},
It works well. Can someone explain me why, and what is the difference?
In both cases, VueJS Devtools shows the updated values when clicking on refresh. It's a reactivity issue.
You can find a fiddle here:
https://jsfiddle.net/dv1jgneb/
From the Docs:
Since Vue performs the getter/setter conversion process during instance initialization, a property must be present in the data object in order for Vue to convert it and make it reactive.
This explains why only the third button "trigger stuff".
So, you may either add that attribute in the data(), or as said in the docs, use this.$set:
this.objects.forEach(anObj => {
this.$set(anObj, 'activated', false);
newArray.push(anObj);
});
JS Fiddle
Hope this helps!

Vue object properties not changing with v-bind:class

I have a object which kind of looks like this:
{
data: [
{
id: 28,
type: "blabla",
name: "myname",
language: "en",
active: true
},
{
id: 5,
type: "blabla",
name: "myname2",
language: "fr",
active: false
},
// etc
]
}
I have split this object up into groups which I am display like this:
<li class="suggestion" v-for="suggestion in group">
</li>
I want to highlight my results so that is why I have a position property on my Vue object. which can be changed.
I have a watcher set up for the position attribute:
position() {
this.suggestions.forEach((suggestion, index) => {
suggestion.active = index === this.position;
});
}
It will make the current position(so the currently item active). This works fine when I am checking the array of objects and their properties. The items change their .active property perfectly. But the class binding does not seem to re-evaluate.
What is going wrong here? The initial active state does get taken into account and my first item is highlighted perfectly fine.
Nothing happens on a change to the actual classes though yet with the Vue devtools I can see perfectly fine that the objects properties are changing.
So the problem was that if object gets created like this:
object[suggestion.type].push(suggestion);
Vue doesn't detect changes to that object. It has to do with how Vue watches objects for changes.
In order for Vue to play nicely with objects you create and want to be reactive you need to utilize Vue.set
Vue.set(object, suggestion.type, suggestion)
Or
this.$set(object, suggestion.type, suggestion)

How can I observe multiple key paths with ractive?

I'd like to observe changes to a key path per docs on ractive observe. However, I'd like to be able to observe multiple paths at the same time, eg, given:
var binding = new Ractive({
el: '.here',
data: {
items: [
{
finished: false
},
{
finished: false
}
]
},
template: someTemplate
})
I'd like to be able to do something like:
binding.observe('items.*.finished')
Or similar to be able to watch the finished property of any item in the array.
What's the best way to do this?
Exactly as you described! http://jsfiddle.net/rich_harris/c3yc848z/
Note that the values of any * placeholders are passed to the callback as additional arguments, beyond the (newValue, oldValue, keypath) that you normally get – so in this case there'd be a fourth index argument.

Filter rallygrid by tag selection

I have a rallygrid object and a rallytagpicker object created. What I would like to do is filter the rallygrid to only include items which include any of the selected tags. I have tried a couple of different ways of accomplishing this but I can't seem to get it to work. Here is my code for the tagpicker:
this.tagPicker = this.add({
xtype: 'rallytagpicker',
autoExpand: true,
listeners: {
select: this._onTagSelect,
scope: this
}
});
So, the first problem I'm having is that select doesn't actually seem to fire when I select an tag from the drop down, and secondly, once it does fire how can I refresh the filter on the grid to include items with this new tag?
As it is right now I'm filtering using filter objects like the following:
{ property: 'Tags.Name', operator: 'contains', value: 'My Tag' }
It would be nice if I could store all the tags to filter by in an array and pass the whole array at once rather than having an individual filter object for each tag. Is that possible?
What if you use the change event instead?
To build the filters you'll want to just or them together for each tag:
Rally.data.QueryFilter.or([
{
property: 'Tags.Name',
operator: '='
value: 'My Tag'
},
{
property: 'Tags.Name',
operator: '='
value: 'My Tag2'
},
]);
Then to refresh the grid you can use its refresh method, passing the new filters:
grid.refresh({
filters: [
//filters here
]
});

Categories