How to count elements in an array depending on value - arrays

I have an array of Answer Entities that has an structure that includes an array of Vthumbs.
Entities that can either have a boolean vthumbdown/vthumbup property.
And the problem is this: How can I count the number of elements in the Vthumbs array that have a true "vthumbdown" (and the number of elements that have false "vthumbup")?
{
"accepted": true,
"creationDate": "2018-12-21T15:42:34.497Z",
"id": 0,
"urlvanser": "string",
"userId": 0,
"vquestionId": 0,
"vthumbs": [
{
"creationDate": "2018-12-21T15:42:34.497Z",
"id": 0,
"userId": 0,
"vanswerId": 0,
"vquestionId": 0,
"vthumbdown": true,
"vthumbup": true
}
]
}
]
I can see the total number of items with {{vanswer.vthumbs.length}} but I can not think how to get the number of Ups and Downs

Here is a great reference on javascript arrays and their available methods on MDN. In this instance you would most likely want to utilize the filter method as follows.
vanswer.vthumbs.filter(vthumb => vthumb.vthumbdown).length;
vanswer.vthumbs.filter(vthumb => vthumb.vthumbup).length;

You can get this simply by filtering the array:
const ups = vanswer.vthumbs.filter(v => v.vthumbup).length;
const downs= vanswer.vthumbs.filter(v => v.vthumbdown).length;
Then use it in your template:
Ups: {{ ups }}
Downs: {{ downs }}
Edit: according to comments, you wanna count these things in the template. For that, you can build a simple pipe:
import { Pipe, PipeTransform } from '#angular/core';
#Pipe({
name: 'countThumbs'
})
export class CounterPipe implements PipeTransform {
transform(obj: any, upsOrDowns = 'ups'): any {
if (!obj.vthumbs || !Array.isArray(obj.vthumbs)) {
return null;
}
if (upsOrDowns === 'ups') {
return obj.vthumbs.filter(item => item.vthumbup).length;
} else {
return obj.vthumbs.filter(item => item.vthumbdown).length
}
}
}
You then use it in the template like this:
Thumbups: {{ vanswer | countThumbs:'ups' }}
Thumbdowns: {{ vanswer | countThumbs:'downs' }}
Here's a more generic approach at Stackblitz.

Related

I can't access the props of my objects (Angular)?

I have this variable in my environment.ts:
featureToggle: {"feature1": true, "feature2: false}
In a service of mine, I want to give these values to a component with a getAll-method, just like:
getAll() {
return environment.featureToggle;
}
In a component I'm having an array and call the servicemethod in my ngOnInit, where I assign the values to my array. Through *ngFor im iterating through the array.
Then I get an ERROR NG0901 IterableDiffers.find.
Yes, it might be, because it is an Object Array, so I would have to convert it in my service first to a normal Array, or assign the values to an interface to work with it?
like
interface FeatureInterface {
feature: string,
isActive: boolean;
}
But I can't even .map through my environments variable nor does forEach work. I also tried Object.keys(environment.featureToggle). Is there any way to access and iterate my properties in my environment.ts and work with them in any component?
Component:
features: FeatureInterface[] = [];
ngOnInit(): void {
this.features = this.featureToggleService.getAllFeatures()
Html:
<div *ngFor="let item of features">
{{item.feature}}
...
Check this out!
let featureToggle = { "feature1": true, "feature2": false, "feature3": true};
const result = Object.entries(featureToggle).filter((e) => e[1]).map((i) => i[0]);
console.log(result);
UPDATE 1:
Based on the requirement(as mentioned in the comments), try this:
let featureToggle = { "feature1": true, "feature2": false, "feature3": true };
const result = Object.entries(featureToggle).map((i) => {
return {
feature: i[0],
isAvailable: i[1]
}
});
console.log(result);
[ { feature: 'feature1', isAvailable: true },
{ feature: 'feature2', isAvailable: false },
{ feature: 'feature3', isAvailable: true } ]

order array in order based by order in model and numerically

I want to order this list by the order of what the selection has in the model. For instance, since VHS_ is the designated Name..all VHS_ items should appear first, but in numeral order. Then LEE_, JE_ and CHS_ (everything should flow underneath but in its own numerical order. JE_50 will appear above JE_59
new Vue({
el: "#app",
data: {
selection:[{Name:"VHS_",Dept:"Truck",subjects:["_VHS_","LEE_","JE_","CHS_"]}],
todos: [
{
"Name": "CHS_200_TL_L62_TRUCK"
},
{
"Name": "VHS_600_TL_L62_TRUCK"
},
{
"Name": "VHS_116_TL_L62_TRUCK"
},
{
"Name": "VHS_613_TL_L62_TRUCK"
},
{
"Name":"JE_50_OL_T62_TRUCK"
},
{
"Name": "VHS_T10_OL_L62_TRUCK"
},
{
"Name":"JE_59_OL_T62_TRUCK"
},
{
"Name": "LEE_100_TL_L62_TRUCK"
},
]
},
mounted:function(){
this.arranges();
},
methods: {
arranges: function() {
// Sort data
todos.sort(b.Name,a.Name)
},
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app">
<ul v-for="t in todos"><li>{{t.Name}}</li></ul>
</div>
If the todos array should be resorted whenever selection changes you should use a computed property that will rerun automatically whenever any of it's dependencies (selection) changes.
computed: {
sortedTodos() {
// sort using a shallow copy
return [...this.todos].sort((a, b) => {
a = a.Name;
b = b.Name;
// get the index of a and b's subject code found in the selection array
let aSubj = this.selection[0].subjects.findIndex((s) =>
s.includes(a.split("_")[0])
);
let bSubj = this.selection[0].subjects.findIndex((s) =>
s.includes(b.split("_")[0])
);
// if a and b start with same three character subject, sort by standard string comparison
if (aSubj === bSubj) {
return a.localeCompare(b);
// else sort according to their index in the selection array
} else {
return aSubj - bSubj;
}
});
},
},
then use the computed property in your template
<ul v-for="t in sortedTodos"><li>{{t.Name}}</li></ul>

How to generate an array of objects in Alpine.data with Livewire Eloquent collection and Adding properties in those js objects

and thanks for your attention and help,
I have a Collection in my livewire controller. This collection contains a list of players, with some properties : here we will just focus on id and name.
So we can imagine that we have 3 players in the collection :
Players[0] : 'id'=>1, 'name'=>'Yann';
Players[1] : 'id'=>2, 'name'=>'Robert';
Players[2] : 'id'=>3, 'name'=>'Jessica';
I need to get these players in my alpine data.
I can easily get these players in Alpine with the #js method :
window.addEventListener('alpine:init', () => {
Alpine.data('data', () => ({
players: #js($players),
}))
})
So, now I have in my alpine.data :
players: [
{ id: 1, name: 'Yann' },
{ id: 2, name: 'Robert' },
{ id: 3, name: 'Jessica' },
]
Now I can easily display the players in my html with a template x-for :
<template x-for="player in players">
<p x-text="player.name"></p>
</template>
But I want to add some additionnal properties in each player object. Those properties will be updated in front depending user's actions.
I would like to get something like this :
players: [
{ id: 1, name: 'Yann', touched20: 0, touched15: 0 },
{ id: 2, name: 'Robert', touched20: 0, touched15: 0 },
{ id: 3, name: 'Jessica', touched20: 0, touched15: 0 },
]
All additionnal properties are the same for each object in the array, so I imagine i could use a foreach loop to put them in the objects.
But I can't see and don't understand how i can include a loop in my alpine.data script to do this.
Anyone could help me ?
I edit my question because I found a solution :
I just make a loopPlayers function outside of my alpine data and call this function in the alpine data :
function loopPlayers() {
let players = [];
const livewirePlayers = #js($players);
livewirePlayers.forEach(element => {
players.push(element);
});
players.forEach(function(element) {
element.touched15 = 0;
})
return players;
}
And in alpine.data :
players: loopPlayers(),
Now I have my collection of players from my livewire controller & I have new properties for each element of the collection in the js data
That was easy, as usual I guess :)

How to return only top 5 records in Vue js nested loop

I need to iterate over shoppingItem (items) inside shoppingOrder (orders). There are three orders. The first order has one item (itemid:1), second order has six items (itemid:2,3,4,5,6,7), third order has one item (itemid:8). I need to show only the top five items. i.e., 1,2,3,4,5 but the current code only limits the item in the second order, showing five items inside. The final output comes like 1,2,3,4,5,6,8
If first order has five items the loop should exit, if first order has one item and second order has six items, it has to show 1 in order 1 and 2,3,4,5 in order2 and exit before order3. But in my example, using order.shoppingItem.slice(0,5) only limits the items in the second order. It is not limiting the total items. How do I resolve this issue? I am using Vue JS version 2
NestedLoop.vue
<template>
<div>
<div v-for="order in shoppingOrder" :key="order.orderId">
<div v-for="item in order.shoppingItem.slice(0,5)" :key="item.itemId">
{{item.itemId}}
</div>
</div>
</div>
</template>
<script>
export default {
name: "NestedLoop",
data() {
return {
shoppingOrder: [
{
orderId: 1,
orderDate: "7/30/2020",
orderStatus: "Dispatched",
shoppingItem: [
{
itemId: 1,
itemName: "Pen",
itemQuantity: "1",
itemPrice: "10.00"
}
]
},
{
orderId: 2,
orderDate: "7/25/2020",
orderStatus: "Ordered",
shoppingItem: [
{
itemId: 2,
itemName: "Notebook",
itemQuantity: "2",
itemPrice: "40.00"
},
{
itemId: 3,
itemName: "Scale",
itemQuantity: "3",
itemPrice: "100.00"
},
{
itemId: 4,
itemName: "Sharpener",
itemQuantity: "1",
itemPrice: "10.00"
},
{
itemId: 5,
itemName: "DocumentFolder",
itemQuantity: "1",
itemPrice: "10.00"
},
{
itemId: 6,
itemName: "PencilBox",
itemQuantity: "5",
itemPrice: "140.00"
},
{
itemId: 7,
itemName: "SketchBox",
itemQuantity: "5",
itemPrice: "10.00"
}
]
},
{
orderId: 3,
orderDate: "7/34/2020",
orderStatus: "Dispatched",
shoppingItem: [
{
itemId: 8,
itemName: "Sketch",
itemQuantity: "1",
itemPrice: "10.00"
}
]
}
]
};
},
methods: {}
};
</script>
<style scoped>
</style>
Result
1
2
3
4
5
6
8
Expected only to return top 5 items like this (1 in order1 and then 2,3,4,5 in order2),
1
2
3
4
5
I love how succinct #Gaetan C's answer is, but .flat is a relatively new method that will require poly-filling for browsers if you are using Vue-cli 3. See this Github issue for more details. If you run into any challenges, you may need to manually edit the poly-fill option of the babel preset.
For more compatibility among other browsers or if you are using vue cli 2 or below, I present this solution.
computed: {
firstFiveShoppingItems: {
let initialShippingItem = [];
for (const order of this.shoppingOrder) {
if (initialShippingItem.length + order.shoppingItem.length >=5) {
return [...initialShippingItem, ...order.shoppingItem].slice(0, 5);
}
initialShippingItem = [...initialShippingItem, ...order.shoppingItem]
}
return initialShippingItem;
}
}
Then in your template, you add
<div v-for="item in firstFiveShoppingItems" :key="item.itemId">
{{item.itemId}}
</div>
As I said, it's not as elegant or succinct as #Gaetan C's answer but it will be more compatible among other browsers and you won't need to go through headaches with poly-fill configurations.
Another thing I like about my solution is that it doesn't iterate over all shopping item arrays like #Gaetan C's answer. It breaks when the result is obtained.
Just use a computed property instead to get the 5 first shopping items:
computed: {
firstShoppingItems: {
return this.shoppingOrder.map(x => x.shoppingItem).flat().slice(0, 5);
}
}
Then you just need one v-for loop:
<div v-for="item in firstShoppingItems" :key="item.itemId">
{{item.itemId}}
</div>
I'd recommend taking advantage of Vue's computed properties to make a list limited to five items. Currently your code is only slicing the inner list to five items.
computed: {
topFive() {
const limit = 5;
let count = 0;
let res = []
for (let i = 0; i < this.shoppingOrder.length && count < limit; i += 1) {
let order = this.shoppingOrder[i];
for (let j = 0; j < order.shoppingItem.length && count < limit; j += 1) {
let item = order.shoppingItem[j];
res.push(item);
count++;
}
}
return res;
}
Once you have a computed property, you can call it from the template like so:
<div v-for="item in topFive" :key="item.itemId">{{item.itemId}}</div>

Is there any way to update state by find Item and replace on a nested state?

I am building an order functionality of my modules in the component state on react
so the state object looks like that
"activity": {
"name": "rewwerwer",
"description": "werwerwerwerwer",
"modules": [
{
"name": "Text",
"order": 1,
"module_id": 1612,
},
{
"name": "Text2",
"order" 2,
"module_id": 1592,
}
]
}
handleSortUp = (moduleid ,newOrder) => {
const { modules } = this.state.activity;
const module = modules.find(element => element.module_id === moduleid);//Thios returns the correct object
this.setState({ activity: { ...this.state.activity.modules.find(element => element.module_id === moduleid), order: newOrder } });
}
I tried this but it updates the order field and object
but also removes all other objects from modules array :<
I like just to replace only the order field on each module by module id
and leave rest data there
the required response from the state that i need when the handleSortUp(1612,14); is fired
handleSortUp(1612,2);
{
"name": "rewwerwer",
"description": "werwerwerwerwer",
"modules": [
{
"name": "Text",
"order": 2,
"module_id": 1612,
},
{
"name": "Text2",
"order": 1,
"module_id": 1592,
}
]
}
I can do this on a simple array the question is how to update the State on react
Also the one way to change the order is answered fine but how also to change the field that had that order registered
So when we fire Change Item 1 order to 2 the Item 2 needs to take the Order 1
Thank you
Sure! This would be a great place to use the built-in .map method available for arrays. Consider the following example.
let array = [{order: 1, type: "food"}, {order: 2, type: "notfood"} ]
const newArray = array.map((item) => {
//CHECK TO SEE IF THIS IS THE ITEM WE WANT TO UPDATE
if(item.order == 1){
return {
...item,
newField: "newData"
}
} else {
return item
}
})
Output is:
[{order: 1, type: "food", newField: "newData"}
{order: 2, type: "notfood"}]
So yes you could totally update the module you're looking for without mutating the rest of your array. Then use your findings to update the component state using some good ol spread.
this.setState({
activity: {
...this.state.activity,
modules: newArray}
})
Of course they get all eliminated. Pay attention to what you wrote here:
this.setState({ activity: { ...this.state.activity.modules.find(element => element.module_id === moduleid), order: newOrder } });
What are you doing with that find? Let's see what Array.prototype.find() returns: https://developer.mozilla.org/it/docs/Web/JavaScript/Reference/Global_Objects/Array/find
It returns an index, why would you insert an index into the state?
The answer partially came from yourfavoritedev.
As he said you can use the built-in Array.prototype.map() and do it like this:
handleSortUp = (moduleid ,newOrder) => {
const { modules } = this.state.activity;
const newModules = modules.map(module => module.module_id === moduleid ? { ...module, order: newOrder } : module)
this.setState({ activity: { ...this.state.activity, modules: newModules } });
}
This should work, let me know but I strongly advice to ask me or search on the web if you don't understand what is happening there (syntactically or semantically speaking).

Resources