v-for inside v-for displaying data in onclick event - javascript

hello everyone i have array of objects, and inside each object i have also array of objects..
i did the v-for inside the v-for to display data
at first i wanted for each row to show the data of the first element of the each child of the parent array and onClick event, i wanted to change the data only in the specifique row.
infos: [{
name: 'name1',
infosName: [{
place: 'place.1.1',
surface: '100'
},
{
place: 'place.1.2',
surface: '200'
}
]
},
{
name: 'name2',
infosName: [{
place: 'place.2.1',
surface: '300'
},
{
place: 'place.2.2',
surface: '400'
}
]
}
]
i created a method to display the data and got on parametres Two indexs
this a jsfiddle to understand more the problem
Thank you
https://jsfiddle.net/f0ehwacm/2

There are several issues needing fixing, but you are on the right lines.
Most importantly, you need to store not just one "myIndex" but a separate "myIndex" for each row
That is the root cause of your problem.
Let me rephrase your question?
I believe you are hoping for four buttons. The top two buttons choose between two options.
Completely separately, the bottom two buttons choose between two options.
It would be easier for readers to understand your intention if you called the two top buttons "Question 1, Option A", and "Question 1, Option B". And then the bottom two "Question 2...". Then they would understand why when you click on one of the buttons, you want to affect the output of the table for that row only.
Avoid using generic terms like "index" and "i"
These make it unnecessarily difficult for people to understand what you intend. Better to use a concrete noun, in this case "question" or "answer", and prefix it with "i" when you mean the index, such as "iQuestion" for the index of the question and "question" for the question itself.
You seem to have a single function "getInfos" which does BOTH getting and setting of information
This is a major problem. You should separate the two functions.
When you click, you want to run a "set" function, that updates your index.
When you are simply displaying, you can access a "get" function, which does not change anything.
You need to store an index for each row
In my terminology, you need to store the index of your answer to each question.
So instead of this.myIndex starting at 0, you have it starting at [0,0]. Each of the two values can be updated separately, allowing the program to update the answer to one row (i.e. one question), while leaving the other row unchanged.
I have renamed this variable to this.myAnswer to make it easier to understand.
this.$set when writing to an array that you want Vue to react to
I initially wrote the "setAnswer" function as follows:
this.myAnswer[iQuestion]=iAnswer
However, I found that the on-screen display was not updating. This is a common problem in Vue, when you update not the main property listed in data(), but an array element of that property.
This is because Vue is not tracking the updates of the array elements, only the array itself. So if you were to reassign the entire array, Vue would notice.
The workaround is to tell Vue explicitly that you are updating something that needs to be reactive. Vue will then update it on screen.
To do this, change your assignment from this format:
this.array[index] = value
To this
this.$set(this.array, index, value)
Vue provides this function this.$set, which executes your normal this.array[index] = value and tells Vue to do the screen update.
How to cope with missing "infosName"
In response to your question in the comments. You have a convenient place to solve this: your getAnswer() function.
Change from this:
getAnswer(iQuestion,iAnswer){
return {
'name' : this.infos[iQuestion].infosName[iAnswer].place,
'surface' : this.infos[iQuestion].infosName[iAnswer].surface
}
to this:
getAnswer(iQuestion,iAnswer){
if (this.infos.length>iQuestion &&
this.infos[iQuestion].infosName &&
this.infos[iQuestion].infosName.length>iAnswer
){
return {
'name' : this.infos[iQuestion].infosName[iAnswer].place,
'surface' : this.infos[iQuestion].infosName[iAnswer].surface
}
else return {
name : "",
surface: ""
}
}
Solution
html:
<div id="app">
<div v-for="(question,iQuestion) in infos">
<div class="row d-flex">
<span style="margin-right:10px" v-for="(answer,iAnswer) in question.infosName" class="badge badge-primary" #click="setAnswer(iQuestion,iAnswer)"><i class="fa fa-eye" style="margin-right:10px;cursor: pointer"></i>{{ answer.place }}</span> </div>
<div class="row">
<p>Name : {{ getAnswer(iQuestion,myAnswer[iQuestion]).name }} </p>
<p>Surface : {{ getAnswer(iQuestion,myAnswer[iQuestion]).surface }}</p>
</div>
</div>
</div>
JS:
new Vue({
el :'#app',
data : function(){
return {
myAnswer : [0,0],
infos : [
{
name : 'name1',
infosName : [
{
place : 'Question 1, Option A',
surface : '100'
},
{
place : 'Question 2, Option B',
surface : '200'
}
]
},
{
name : 'name2',
infosName : [
{
place : 'Question 2, Option A',
surface : '300'
},
{
place : 'Question 2, Option B',
surface : '400'
}
]
}
]
}
},
methods:{
setAnswer(iQuestion,iAnswer){
this.$set(this.myAnswer,iQuestion,iAnswer)
},
getAnswer(iQuestion,iAnswer){
return {
'name' : this.infos[iQuestion].infosName[iAnswer].place,
'surface' : this.infos[iQuestion].infosName[iAnswer].surface
}
}
}
})

Related

Vuejs2: how to judge props update when using object as a prop?

Suppose I have an array feedsArray, the example value may look like this:
this.feedsArray = [
{
id: 1,
type: 'Comment',
value: 'How are you today ?'
},
{
id: 2,
type: 'Meet',
name: 'Daily sync up'
}
]
Suppose I have registered two components: Comment and Meet, Each component has a prop setting as the following:
props: {
feed: Object
}
and the main component has the following definition:
<component v-for="feed in feedsArray" :feed="feed" :key="feed.id" :is="feed.type"></component>
As you can see, it uses is property to select different component. My question is, how to detect feed object change in the child component ? Like when I set
this.feedsArray[0] = {
id: 1,
type: 'Comment',
value: 'I am not ok'
}
How can the Comment component detect the changes ? I tried to add a watcher definition in the child component like the following:
watch: {
feed: {
handler (val) {
console.log('this feed is changed')
},
deep: true
}
},
But it doesn't work here. Anyone know how to solve this ?
Do not assign directly to an array using index - use splice() instead, otherwise JavaScript can not detect that you have changed the array.
If you want to change only the value of an already existing key of an object - then simply update it e.g. this.feeds[0].value = 'I am not okay any more';
This works for existing keys only - otherwise you have to use this.$set(this.feeds[0], 'value', 'I am not okay any more');

What is the addressing issue with my complex document update() in MongoDB?

I have been unable to reach into my MongoDB collection and change a value in a complex document. I have tried more variations than the one example shown below, all sorts of variations, but they fail.
I want to change the Value of the Key "air" from "rain" to "clear". In real life, I will not know that the current Value of the Key "air" is "rain".
Note, I am not using the MongoDB _id Object and would like to accomplish this without using it.
3 documents in the weatherSys collection:
{
"_id" : ObjectId("58a638fb1831c61917f921c5"),
"SanFrancisco" : [
{ "sky" : "grey" },
{ "air" : "rain" },
{ "ground" : "wet" }
]
}
{
"_id" : ObjectId("58a638fb1831c61917f921c6"),
"LosAngeles" : [
{ "sky" : "grey" },
{ "air" : "rain" },
{ "ground" : "wet" }
]
}
{
"_id" : ObjectId("58a638fb1831c61917f921c7"),
"SanDiego" : [
{ "sky" : "grey" },
{ "air" : "rain" },
{ "ground" : "wet" }
]
}
var docKey = "LosAngeles";
var subKey = "air";
var newValue = "clear";
var query = {};
//var queryKey = docKey + ".$";
query[query] = subKey; // query = { }
var set = {};
var setKey = docKey + ".0." + subKey;
set[setKey] = newValue; // set = { "weather.0.air" : "clear" }
db.collection('weatherSys').update(query, { $set: set }, function(err, result) {
if (err) throw err;
});
UPDATE-1:
Ok, so I was hoping I could find a layout a bit simpler than you had suggested but I failed. Everything I tried was not addressable at the "air" Key level. So I copy and pasted your exact JSON into my collection and ran it. I'm using MongoChef to manipulate and test the collection.
Here is my new layout drived from pasting your JSON in 3 times to create 3 documents:
When I then attempted to update the "San Francisco" document's "air" key I got an unexpected result. Rather than updating "air":"dry" it created a new "air" key in the "San Francisco" Object:
So I thought ok, lets try the update again and see what happens:
As you can see it updated the "air" key that it had previously created. I could fight this out and try to make it work "my" way but I just want it to work so I reconfigure my collection layout again, along the lines of what is "working":
And run the update again:
Then I verify it by running the update again:
It works, I am updating properly in a multi-document environment. So this is my current working collection layout:
I have a couple of questions about this-
I am using the top level Key "weather" in every document. It adds nothing to the information within the document. Is there a layout design change that would not necessitate that Key and the overhead it brings along?
Lets say I have to use the "weather" key. Its value is an array, but that array only has one element, the Object which contains the Keys: city, sky, air, and ground. Does addressing necessitate the use of an array with only one element? Or could I get rid of it. Instead of "weather":[{}] could the design be "weather":{} or would I get into non addressability issues again?
It appears I can now update() any of the Values for the Keys: air, sky, and ground, but what is the find() structure to say READ the Value of the Key "ground" in one of the documents?
----> OK, I think I've got this question #3-
db.weatherSys.find({ "weather.city" : "San Francisco" }, { "weather.ground": 1 })
In the original collection layout that you had suggested, could you explain to me why it did not update as you and I had expected but instead created a new the "city" object?
A lot here. I appreciate your sticking with it.
You can't use positional operator for querying the array by its key.
You can access the weather array by index, but that means you know the array index.
For example if you want to update air element value in weather array.
db.collection('weatherSys').update( {}, { $set: { "weather.1.air" : "clear"} } );
Update:
Unfortunately, I can't see any way to update the values without knowing the array index for key.
You don't need query object as your keys are unique .
db.collection('weatherSys').update( {}, { $set: { "SanFrancisco.1.air" : "clear"} } );
or
Other variant if you want to make sure the key exists.
db.collection('weatherSys').update( { "SanFrancisco": { $exists: true } }, { $set: { "SanFrancisco.1.air" : "clear"} } );
Not sure if you can but if you can update your structure to below.
{
"_id" : ObjectId("58a638fb1831c61917f921c5"),
"weather" : [
{
"city": "LosAngeles",
"sky" : "grey" ,
"air" : "rain" ,
"ground" : "wet"
}
]
}
You can now use $positional operator for update.
db.collection('weatherSys').update( {"weather.city":"LosAngeles"}, { $set: { "weather.$.air" : "clear"} } );
I am using the top level Key "weather" in every document. It adds
nothing to the information within the document. Is there a layout
design change that would not necessitate that Key and the overhead it
brings along?
The only layout that I can think of is promoting all the embedded properties to the top level. Sorry, not sure why I didn't think of this the first time around. Sometimes you just need a right question to get the right answer.
{
"_id" : ObjectId("58a638fb1831c61917f921c5"),
"city": "LosAngeles",
"sky" : "grey",
"air" : "rain",
"ground" : "wet"
}
All the updates will be simply top level updates.
db.collection('weatherSys').update( {"city":"LosAngeles"}, { $set: { "air" : "clear"} } );
Lets say I have to use the "weather" key. Its value is an array, but
that array only has one element, the Object which contains the Keys:
city, sky, air, and ground. Does addressing necessitate the use of an
array with only one element? Or could I get rid of it. Instead of
"weather":[{}] could the design be "weather":{} or would I get into
non addressability issues again?
N/A if you are okay with first suggestion.
It appears I can now update() any of the Values for the Keys: air,
sky, and ground, but what is the find() structure to say READ the
Value of the Key "ground" in one of the documents?
db.weatherSys.find({ "city" : "San Francisco" }, { "ground": 1 })
In the original collection layout that you had suggested, could you
explain to me why it did not update as you and I had expected but
instead created a new the "city" object?
That is a copy paste error. I meant to suggest the working layout you have right now. Updated my previous layout.

String Interpolation not getting updated after change to object in Viewmodel in Aurelia

I have an element in my View in Aurelia that is not getting updated when an object from its Viewmodel is getting updated. I've seen the documentation about Pub/Sub and Event Aggregators, however this seems a little heavy-handed for what I want to do, since I am not trying to communicate between two different resources, but rather just within a View and its Viewmodel.
When a change occurs to the object in the Viewmodel, I don't know how to correctly update (or trigger an update to) the string interpolation in the View.
My code is as follows
myview.html
<h1>My List</h1>
<ul>
<li repeat.for="group of modelObject.groups">
<span>${group.id}</span>
<span repeat.for="state of group.region">${state}</span>
</li>
<ul>
<button click.delegate(editModelObject())>Edit</button>
myviewmodel.js
constructor()
{
this.modelObject = {
<other stuff>,
"groups": [
{
"id": "default",
"regions" : ["NY", "CT", "NJ"]
},
{
"id": "west",
"regions" : ["CA", "OR"]
}
],
<more stuff>
}
}
editModelObject() {
<something that updates both the id and regions of a group in this.modelObject>
}
For some reason, the states are correctly changing in the view, but the id's are not. Do I need to use something like Pub/Sub to get the two-way binding to work correctly? Or is there a simple thing that I am missing or doing wrong?
This works if you change a property of one of the array's objects. But this doesn't work if you assign one of the array's index because this would require dirty-checking. See https://github.com/aurelia/binding/issues/64
To solve your problem you should use splice() instead of indexed assignment. For instance:
const newItem = { id: 77, name: 'Test 77', obj: { name: 'Sub Name 77' } };
//instead of this.model.items[0] = newItem; use below
this.model.items.splice(0, 1, newItem);
Running example https://gist.run/?id=087bc928de6532784eaf834eb918cffa

Fitting multiple dependent datasets into AngualrJs ngRepeat

I am looking for a little bit of help with logically fitting two objects with common reference into AngularJs ngRepeat.
Example objects (these get called from a service):
$scope.objArr1 = [
{ id: 1, Name: 'Name 1', Value: 'Value 1', },
{ id: 2, Name: 'Name 3', Value: 'Value 2', },
{ id: 3, Name: 'Name 3', Value: 'Value 3', },
];
$scope.objArr2 = [
{ id: 1, Name: 'Name 1', Value: 'Value 1', ObjArr1: { id: 1, Name: 'Name 1', Value: 'Value 1', }, },
{ id: 2, Name: 'Name 1', Value: 'Value 1', ObjArr1: { id: 1, Name: 'Name 1', Value: 'Value 1', }, },
{ id: 3, Name: 'Name 1', Value: 'Value 1', ObjArr1: { id: 3, Name: 'Name 3', Value: 'Value 3', }, },
];
Something along those lines. Basically if you can think of it this way; first array objects form buckets while second array objects form items that fit into corresponding bucket.
First approach
HTML:
<ul>
<li data-ng-repeat="item in objArr1 | filter : someFilter">{{item.Name}}
<ul>
<!-- how to filter objArr2 items based on objArr1 property ? -->
<li data-ng-repeat="item2 in objArr2 | filter : someOtherFilter">{{item2.Name}}</li>
</ul>
</li>
</ul>
In simple terms I was trying to filter $scope.objArr2 items that correspond to the current repeater item in the inner repeater. I tried various things with someOtherFilter but I was unable to reference the item from outer repeater.
Problem
I couldn't figure out how get this filtering bit to work.
Second approach
When all else failed I decided to combine the data structures into one like so:
// deep copy to avoid dependency
angular.copy($scope.objArr1, $scope.objArr3);
// loop over objArr3 and add empty array objArr2
// which we will populate a bit later
angular.forEach($scope.objArr3, function (val, key) {
$scope.objArr3[key]["objArr2"] = [];
});
Then I setup a $watch-er`to monitor both objArr1 and objArr2 because I don't know when these will return.
$scope.$watchGroup(['objArr1', 'objArr2'], function (newVals, oldVals) {
// check to make sure there is stuff to loop over
// i am wrongly assuming there will be items in both objArr1 and objArr2
// i'll worry about what to do when there is no data a bit later
if(newVals[0].length > 0 && newVals[1].length > 0) {
angular.forEach($scope.objArr1, function (val1, key1) {
angular.forEach($scope.objArr2, function (val2, key2) {
if (val1.Id === val2.objArr1.Id) {
$scope.objArr3[key1].objArr2.push(val2);
}
});
});
}
});
HTML:
<ul>
<li data-ng-repeat="item in objArr1 | filter : someFilter">{{item.Name}}
<ul>
<li data-ng-repeat="item2 in item.objArr2">{{item2.Name}}</li>
</ul>
</li>
</ul>
Problem
While this has worked just fine on the surface I get a lovely Error: [$rootScope:infdig] 10 $digest() iterations reached. Aborting! in the console.
I am a bit puzzled what would cause for $digest to fire so many times.
However, by commenting update line $scope.objArr3[key1].objArr2.push(val2); of my watcher the error goes away. But then I don't understand how this would result in extra digest iterations.
Halp
In the end either of the approach that I came up with has some problem. While second approach actually does its job and populates my repeater correctly but there is that nasty error in the console.
Anyone with a bit more experience in this field please help.
Update
Some of the silly things I tried with someOtheFilter are:
data-ng-repeat="item2 in objArr2 | filter : someOtherFilter"
$scope.someOtherFilter = function(item){
// item is always the current inner repeaters item2 object
// that just the way angular filter works
return item.objArr2 === $scope.objArr1.Id; // this is silly but idea is there
};
data-ng-repeat="item2 in objArr2 | filter : someOtherFilter(item)"
$scope.someOtherFilter = function(item){
// if memory serves me right
// in this case item is always repeaters current item2 object
// with no ability to reference outer repeaters current item object
}
data-ng-repeat="item2 in objArr2 | filter : someOtherFilter(item, item2)"
$scope.someOtherFilter = function(item, item2) {
// if memory serves me right
// in this case item was always inner repeaters current item2 object
// and item2 is always undefined
// again with no ability to reference outer repeaters current item
}
At this point I gave up on first approach. But thinking about it now I might have been able to utilise $index (if inner repeater somehow or other didn't overwrite outer repeaters $index reference) to get index value of the outer repeater and try to get at $scope.objArr1[index].
No matter which scenario would have worked for someOtherFilter inner working only need to compare inner object objArr1.Id to outer objects Id.
UPDATE (learn from my mistakes)
OK, after confirming the answer as working I still had the same issue in my production example Error: [$rootScope:infdig] 10 $digest() iterations reached. Aborting!.
After cooling down for a few days I decided to revisit this problem and this is what I found.
<div class="block" data-ng-repeat="team in Teams | filter : validateAgeCategory">
<div data-ng-style="getHeaderStyle()">
<span>{{team.Name}}</span>
<!-- bunch of things removed for brevity -->
</div>
<ul data-ng-style="getListStyle()">
<li data-ng-repeat="players in Players | filter : { Team: { Id: team.Id, }, }">
<a data-ng-style="getListItemStyle()" data-ng-href="#/players/{{player.Id}}">{{player.Name}}</a>
</li>
</ul>
</div>
I adapted my Team/Player example for easier understanding. Regardless notice that in production I use a few ng-style calls to retrieve CSS.
Reason why I am doing so is because IE has a tendency to remove {{}} definitions from inline style="color: {{color}};" definition during document load. It's IE bug so to speak.
Moving on, what I found is that the innermost ng-style was causing the error with $digest. By removing data-ng-style="getListItemStyle()" everything is happy. Everything bu me of course.
Looking at this as an overhead it would be better to create CSS classes and instead apply classes based on some indexing to style my HTML.
There you have it.
OK, I'll try my best to help.
I think the problem with your second approach is somehow related to this question. Read the comments there. It might be related to the list being changed by the filter.
As for your first approach, I'm still not sure what you were trying to do, but I've created this example to show you that you can filter inside nested ngRepeats.
BTW, If you need to access outer $index inside an inner ngRepeat, you can use ngInit.

Angular - applying filter in real time

I have an array of objects:
[{
name: "test",
type: 0,
speed: 50
}, {
name: "test2",
type: 4,
speed: 10
}, {
name: "test3",
type: 4,
speed: 67
}, {
name: "test4",
type: 2,
speed: 40
}]
I want to show the array with ng-repeat..
Until here everything is simple...
But now I want to add few buttons, each button will filter the array with other parameters...
For example:
Button one: Show only the objects where the speed is more than X,
Button two: Show only the objects where the type is Y..
etc...
I now how to use angular filters, but I couldn't understand how to use it for my needs...
Thank you very much!
The most suitable solution for your purpose it is using filter named filter. It can accept custom filtering function in argument.
HTML
<div ng-repeat="item in items|filter:filterItem"></div>
Then you should define function filterItem in your controller:
JS
// assume that first button sets `$scope.hideSlow` flag and the second `$scope.showType` respectively
$scope.filterItem = function(item) {
return (!$scope.hideSlow || item.speed > X) &&
(!$scope.showType || item.type === Y);
}
You can add extra filtering logic into this function, but do not forget write tests on it to be sure that your function filtering properly
I might suggest creating a custom filter that inspects the model to determine the result set out of the filter. Bind the buttons to methods on the scope to check from within the filter.
https://docs.angularjs.org/tutorial/step_09

Categories