Sort array based on intermediate model's attribute - javascript

I have three models (I am using Vue/Vuex-ORM on the frontend). Category, CategoryItem and Item.
I'm able to fetch an array of categories, and within each category is an array of items. An intermediate join model defines the relationships of these two models, and I am able to access as such:
// array of categories, each category has array of items
const categories = Category.query().where('pack_id', this.selectedPack.id).with('items').get();
categories.map(category => {
category.items.forEach(item => {
console.log('item.pivot: ', item.pivot); // pivot refers to join model
// how to order items based on item.pivot?
})
})
Within the .forEach, I can access the join model with item.pivot. What I am looking to do however, is sort each category's items based on item.pivot.position.
I started going down a path where the first line inside of the .map I defined a new empty array, and would then theoretically push in a new value based on whether the position was higher or lower, but I couldn't quite wrap my head around how to accomplish this.
Thanks!

Well just my luck. A half hour after posting this question, I figure it out! Here was what I did, in case anyone is curious.
categories() {
const categories = Category.query().where('pack_id', this.selectedPack.id).with('items').get();
categories.forEach(category => category.items.sort(this.compare));
return cats;
}
compare(a, b) {
let comparison = 0;
if (a.pivot.position > b.pivot.position) comparison = 1;
else if (a.pivot.position < b.pivot.position) comparison = -1;
return comparison;
},

Related

Set state based on value of array item in previous state

Hello I am new to programming and I am trying to make a function in React that adds a team to an array of selected teams but only if there are less than 2 teams selected and if the team is available (not already chosen). It is working to limit the array to only 2 values but the array will accept the same team twice and I am not sure why. For example if the user clicks ATL twice then that team will be in the array twice. What did I do wrong here and what should I change in order to fix it? Sorry if the question is too simple for this forum, I am new.
Here is the code where I am changing the state and checking if gameState.selected_teams[0] != team:
function App() {
const [gameState, setGameState] = useState({
cards: Cards,
selected_teams: []
});
function setTeam(team){
if (gameState.selected_teams.length < 2 && gameState.selected_teams[0] != team) {
setGameState((prevState) => ({
cards: prevState.cards,
selected_teams: [
...prevState.selected_teams, {
team
}
]
}))
}
}
And for the component that calls the setTeam function:
function TeamSelect({cards, setTeam}) {
var teams = Object.keys(cards);
return (
<div className='flex-container'>
<div className='team-container'>
{
teams.map(team => (
<div
onClick={() => setTeam(team)}
className='team-label' key={team}>
{team}
</div>
))
}
</div>
</div>
)
}
You're adding an object {team: team} to your selected teams array each time you perform your click here:
selected_teams: [
...prevState.selected_teams, {
team
}
]
but your team key that you pass into your setTeam function is a string, so your comparison fails as you're trying to compare a string with an object. You can change your comparison to extract the team property from your object:
gameState.selected_teams[0]?.team != team
The ? ensures that a value exists at index 0 in your array before using .team on it (otherwise you would get an error if it's undefined).
You can adapt this code to handle more than one object by using .every() to check that all objects in selected_team's aren't equal to the one you're adding:
if(gameState.selected_teams.length < 2 && gameState.selected_teams.every(obj => obj.team != team)
If you don't need to pass an object {team: team} (as an object with one property doesn't add much value), then you can simply push your team string into your selected teams, and use .includes() to check if the team you're adding already exists in the array:
selected_teams: [
...prevState.selected_teams, team
]
you can then update your condition to use .includes():
if(gameState.selected_teams.length < 2 && !gameState.selected_teams.includes(team))

Updating components based on sorted array. Angular (4.3)

Working with an array of data that we want to be able to sort for display in a component, and it doesn't seem to be sorting or updating the DOM, however I have a working code sample that properly demonstrates the concept, and it should be sorting, but in the angular app, it's simply not getting sorted.
The parent component that houses the original data stores the data on an Input parameter object called Batch, and the array we're sorting is on Batch.Invoices.Results. The event from the child component is fine, and the appropriate data is confirmed to bubble to the parent component.
The function that's supposed to sort the array looks like this:
public OnInvoiceSortChange({orderValue, orderAscending}){
console.log(`Invoice Sorting has been called. Value: ${orderValue} . Ascending? ${orderAscending}`);
console.log(`Before:`);
console.log(this.BatchViewModel.Invoices.Results.map(x => x.VendorName));
const sortingArray = [...this.BatchViewModel.Invoices.Results];
if(orderAscending){
const sorted = sortingArray.sort((a, b) => a[orderValue] > b[orderValue] ? 1 : 0);
this.BatchViewModel.Invoices.Results = sorted;
console.log('Sorted');
console.log(sorted.map(x => x.VendorName));
} else {
const sorted = sortingArray.sort((a, b) => a[orderValue] < b[orderValue] ? 1 : 0);
this.BatchViewModel.Invoices.Results = sorted;
console.log(sorted.map(x => x.VendorName));
}
console.log(`After:`);
console.log(this.BatchViewModel.Invoices.Results.map(x => x.VendorName));
}
All the console logs are for debugger visibility, and the output is this:
Where in my testing file (non-angular) looks like this:(where data is a direct copy of the array from the Angular app.
const ascendingData = [...data];
const descendingData = [...data];
const sortedDescending = descendingData.sort((a, b) => a['VendorName'] < b['VendorName']? 0 : 1)
const sortedAscending = ascendingData.sort((a, b) => a['VendorName'] > b['VendorName']? 0 : 1);
const vendorListAscending = sortedAscending.map(x => x.VendorName);
const vendorListDescending = sortedDescending.map(x => x.VendorName);
console.log(vendorListDescending);
console.log(vendorListAscending);
and the output looks like this:
So I see that the sorting should work, but it's just not happening in Angular.
How can I get the array sorted, and update the DOM as well?
The function you pass to sort is wrong. It is supposed to return a negative value for "less", a positive value for "greater" or zero for "equal". If orderValue is numeric then it's easiest to just return a[orderValue] - b[orderValue], if not then just change your 0 to -1.
(By the way, name orderKey could be a bit clearer maybe?)
I don't think angular has anything to do here, but I cannot tell now why you get different results. Anyway, your sort function is invalid (it states that a equals b, but at the same time b is greater than a), I hope fixing this function helps.

Vue computed property overwriting global state without vuex

I have a list of people who have scores. In state I have them listed in an array, one of the items in the array is 'scoreHistory' which is an array of objects containing their scores at different points in time. I want to filter this set for different time periods i.e. -5 days, -30 days so instead of just seeing the overall score I can see the scores if everyone started at 0 say 30 days ago.
I have it (kind of) working. See my code below:
filteredScores () {
if(!this.people) {
return
}
// Here I was trying to ensure there was a copy of the array created in order to not change the original array. I thought that might have been the problem.
let allPeople = this.people.slice(0) // this.people comes from another computed property with a simple getter. Returns an array.
let timeWindow = 30 //days
const windowStart = moment().subtract(timeWindow,'days').toDate()
for (const p of allPeople ) {
let filteredScores = inf.scoreHistory.filter(score => moment(score.date.toDate()).isSameOrAfter(windowStart,'day'))
p.scoreHistory=filteredScores
//calculate new score
p.score = inf.scoreHistory.reduce(function(sum,item) {
return sum + item.voteScore
},0)
}
return allInf
}
I expected it to return to me a new array where each person's score is summed up over the designated time period. It seems to do that OK. The problem is that it is altering the state that this.people reads from which is the overall data set. So once it filters all that data is gone. I don't know how I am altering global state without using vuex??
Any help is greatly appreciated.
Your problem isn't that you're modifying the array, but that you're modifying the objects within the array. You change the scoreHistory and score property of each item in the array. What you want to do instead is create a new array (I recommend using map) where each item is a copy of the existing item plus a new score property.
filteredScores () {
if(!this.people) {
return
}
let timeWindow = 30 //days
const windowStart = moment().subtract(timeWindow,'days').toDate()
return this.people.map(p => {
let filteredScores = p.scoreHistory.filter(score => moment(score.date.toDate()).isSameOrAfter(windowStart,'day'))
//calculate new score
let score = filteredScores.reduce(function(sum, item) {
return sum + item.voteScore
}, 0)
// Create a new object containing all the properties of p and adding score
return {
...p,
score
}
}
})

Delete Session Storage - Shopping Cart Project

I hope this is a good question. I am working on a shopping cart project. I have been scouring the internet through different tutorials on shopping carts. I am attempting to write mine in Vanilla Javascript. I am having a problem with removing shopping cart items from session storage.
Below is what is currently in my session storage. As you can see it is an Array of objects.
[{"id":"8","name":"Candy
Skull","price":"20000","image":"../images/candyskull-
min.JPG","qty":"1"},{"id":"5","name":"Upsidedown
House","price":"20000","image":"../images/upsidedownhouse-
min.JPG","qty":"1"},{"id":"6","name":"Brooklyn
Window","price":"30000","image":"../images/brooklynwindow-
min.JPG","qty":"1"},{"id":"4","name":"Hand That
Feeds","price":"40000","image":"../images/handthatfeeds-
min.JPG","qty":"1"}]
I want to loop through the array and remove the matching object from the cart storage.
Below is the JS Code used to generate the .remove-from-cart buttons. As you can see it includes all the dataset information.
<td>
<span class="remove-from-cart">
<b data-id="${value.id}" data-name="${value.name}" data-
price="${value.price}" data-image="${value.image}" data-
qty="${value.qty}">X</b>
</span>
</td>
To test the functionality of what I have done so far you can visit www.dancruzstudio.com/shop
The function that I can't get to work properly is the removeFromStorage() function. For some reason when comparing an object to the objects in the array I'm never getting back a true boolean value, even when there are items in the cart that should match. Where am I going wrong? I hope someone can help. Below is a copy of my JS code.
The method I am using is having an identical dataset value in the remove item button generated by JS and then parsing that dataset into an object and comparing it to the objects in the session storage array which is called shopItems inside the removeFromStorage() function. I hope this information is enough for someone to see my problem. Thank you in advance.
// Remove item from DOM
document.querySelector('#cart-list').addEventListener('click',
removeFromCart)
function removeFromCart(e) {
if(e.target.parentElement.classList.contains('remove-from-cart')) {
//Remove from DOM
e.target.parentElement.parentElement.parentElement.remove();
//Remove from Session Storage
removeFromStorage(e.target.dataset);
}
}
// remove from Session storage
function removeFromStorage(removedItem){
let shopItems;
if(sessionStorage['sc'] == null){
shopItems = [];
} else {
shopItems = JSON.parse(sessionStorage['sc'].toString());
}
var compare = JSON.parse(JSON.stringify(removedItem))
shopItems.forEach(function(item, index){
if(compare === item){
console.log(compare);
console.log(item);
// shopItems.splice(index, 1);
}
});
sessionStorage['sc'] = JSON.stringify(shopItems);
}
You can not compare objects like this.
let a = {p:1};
let b = {p:1};
console.log(`a ===b ? ${a===b}`);
If your objects are fairly simple you can try comparing their stringify representation:
let a = {p:1};
let b = {p:1};
const compare = (x,y) => {
return JSON.stringify(x) === JSON.stringify(y);
}
console.log(`a === b ? ${compare(a,b)}`);
or write your custom compare function (that may be challenging):
Compare JavaScript objects
Since your objects are decorated with an id, the easisest way would be to idntify them by that:
let storage = [{"id":"8","name":"Candy Skull", "price":"20000","image":"../images/candyskull- min.JPG","qty":"1"},{"id":"5","name":"Upsidedown House","price":"20000","image":"../images/upsidedownhouse- min.JPG","qty":"1"},{"id":"6","name":"Brooklyn Window","price":"30000","image":"../images/brooklynwindow- min.JPG","qty":"1"},{"id":"4","name":"Hand That Feeds","price":"40000","image":"../images/handthatfeeds- min.JPG","qty":"1"}];
let items = [{"id":"6","name":"Brooklyn Window","price":"30000","image":"../images/brooklynwindow- min.JPG","qty":"1"}, {"id":"5","name":"Upsidedown House","price":"20000","image":"../images/upsidedownhouse- min.JPG","qty":"1"}];
const pluck = (acc, crt) => {
acc.push(crt.id);
return acc;
};
let storageIndexes = storage.reduce(pluck, []);
let itemsIndexes = items.reduce(pluck, []);
let removeIndexes = [];
itemsIndexes.forEach(id => removeIndexes.push(storageIndexes.indexOf(id)));
console.log('storage', storage);
console.log('removed items', items);
removeIndexes.sort().reverse().forEach(index => storage.splice(index,1));
console.log('remaining storage', storage);

Compare two elemens in Array and if certain properties match, update property of the first array

I have two arrays.
allInventoryItems.length //100,000 objects
`allInventoryItems[0] // {
name: 'Something',
price: 200
}
and
currentInventory.length //~250 objects
`currentInventory[0] // {
name: 'Something Else',
price: 300
}
You get the point.
What I want to do is compare these two arrays, and if their name property matches, update currentInventory's price property to the price of the allInventoryItems .
What is the best, and fastest way to loop through such a big array of objects, and how can I do it so that I don't have to loop 100,000 times through the allInventoryItems?
This is my current Model:
updatePrice() {
let allInventoryItems = [];
Price.find({})
.then(items => {
allInventoryItems.push(items[0].items);
})
return new Promise((resolve, reject) => {
Inventory.find({
}).then(items => {
// Update items[0].price according to allInventory items
price
});
});
}
Update:
Sorry for not being more specific.
Okay, so my Collections are like this.
Inventory:
Prices:
Now, the Prices Collection is ALL the items, ever.
And the Inventory Collection is the items that the user has in his Inventory.
The process is, that I want to update all the prices in the USER's Inventory, with the prices from the Prices Collection, where the Inventory[item].market_hash_name and the Prices[item].market_hash_name match.
Thanks!
As you stored all the items in the same document, I fear there is not much improvement you can bring, as you always have to update the whole document. Still you can use a Map to avoid a nested loop. You'll still have to perform both loops though, but consecutively.
return Promise.all(Inventory.findOne({ appid: RequestConfig.appid.DOTA2 }),
Price.findOne({ gameid: RequestConfig.appid.DOTA2 }))
.then( ([inv, ref]) => {
// Create a map, allowing to get an inventory item by name in constant time
const map = new Map(inv.items.map(item => [item.market_hash_name, item]));
for (const refItem of ref.items) {
if (map.has(refItem.market_hash_name) {
// Update a single price in the inventory document
map.get(refItem.market_hash_name).price = ref.price;
}
)
return inv.save(); // Update the whole inventory document, and return the promise
);
NB: Note that your code employs the promise construction anti-pattern: don't create a new Promise if you already have one.
Rather than allInventoryItems being an array, you could make it an object and use it as a hashtable. Then, lookups would be way faster than finding each item in an array.
updatePrice() {
let allInventoryItems = {};
Price.find({})
.then(items => {
// this might be wrong depending on structure of items
allInventoryItems[items[0].items.name] = items[0].items.price;
})
return new Promise((resolve, reject) => {
Inventory.find({
}).then(items => {
// loop through currentItems.
// If allInventoryItems[currentItem.name] exists, set the price of currentItem to its value. This lookup is O(1).
});
});
}
In general, since Inventory is likely to be a much smaller list that will be iterated over, then it would be a much smaller loop to do a lookup and update then to loop over the entire Price collection and try to find a possible match in Inventory. For instance given the numbers in your example: 100,000 documents for Price and only 250 documents for Inventory would be a savings of 99,750 no-op loops.
As an alternative if you are using node 8 then you can use async/await with cursors to limit the amount of in memory processing.
async function updatePrices() {
const inventoryCursor = Inventory.find({}).cursor();
let item;
while (item = await inventoryCursor.next()) {
const price = await Price.findOne({
name: item.name
}).exec();
console.log({
item,
price
});
item.price = price.price;
const updated = await item.save();
console.log({
updated
});
}
}

Categories

Resources