Find object by match property in nested array - lodash

I'm not seeing a way to find objects when my condition would involve a nested array.
var modules = [{
name: 'Module1',
submodules: [{
name: 'Submodule1',
id: 1
}, {
name: 'Submodule2',
id: 2
}
]
}, {
name: 'Module2',
submodules: [{
name: 'Submodule1',
id: 3
}, {
name: 'Submodule2',
id: 4
}
]
}
];
This won't work because submodules is an array, not an object. Is there any shorthand that would make this work? I'm trying to avoid iterating the array manually.
_.where(modules, {submodules:{id:3}});

Lodash allows you to filter in nested data (including arrays) like this:
_.filter(modules, { submodules: [ { id: 2 } ]});

Here's what I came up with:
_.find(modules, _.flow(
_.property('submodules'),
_.partialRight(_.some, { id: 2 })
));
// → { name: 'Module1', ... }
Using flow(), you can construct a callback function that does what you need. When call, the data flows through each function. The first thing you want is the submodules property, and you can get that using the property() function.
The the submodules array is then fed into some(), which returns true if it contains the submodule you're after, in this case, ID 2.
Replace find() with filter() if you're looking for multiple modules, and not just the first one found.

I think your best chance is using a function, for obtaining the module.
_.select(modules, function (module) {
return _.any(module.submodules, function (submodule) {
return _.where(submodule, {id:3});
});
});
try this for getting the submodule
.where(.pluck(modules, "submodules"), {submodules:{id:3}});

I looked into this and I think the best option is to use Deepdash. It's a collection of methods to do deeply filter, find etc.
Sure it would be possible with lodash alone but with Deepdash it's easier.
I tried to convert the previous answer to the latest Lodash version but that was not working. Every method was deprecated in v4 of Lodash. Possible replacements: select = map or filter, any = some, where = filter)
findDeep returns an object with some information to the found item (just some values, see the docs for more details):
value is the object found
key that's the index in the nested array
parent the parent of the value
So the code for the findDeep looks like:
const modules = [{
name: 'Module1',
submodules: [{
name: 'Submodule1',
id: 1
}, {
name: 'Submodule2',
id: 2
}]
}, {
name: 'Module2',
submodules: [{
name: 'Submodule1',
id: 3
}, {
name: 'Submodule2',
id: 4
}]
}];
const getModule = (modules, id) =>
_.findDeep(modules, module => module.id === id, {
childrenPath: "submodules"
});
const resultEl = document.getElementById("result");
const foundModule = getModule(modules, 3).value;
resultEl.innerText = JSON.stringify(foundModule, null, 2);
<script src="https://cdn.jsdelivr.net/npm/deepdash/browser/deepdash.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/lodash/lodash.min.js"></script>
<script>
deepdash(_);
</script>
<h2>Example to get module with id = 3</h2>
<pre id="result"></pre>

Related

All values getting upadted when tried to update single element in an associative array in Vue 3

I have the below code in Vue3:
data: function() {
return {
testData:[],
}
},
mounted() {
var testObj = {
name: 'aniket',
lastname: 'mahadik'
}
for (let index = 0; index < 3; index++) {
this.testData.push(testObj);
}
},
methods: {
updateLastName: function(key) {
this.testData[key].lastname = 'kirve';
}
}
When I call updateLastName(1) to update the lastname of only the second element, it's updating the lastname of all the elements.
I tried several ways but found no desired result.
Can someone point out to me what is going wrong here?
It is because you are pushing the reference to the same object in the array so when you update any item in the array you are instead updating every item since it reference the same object.
Either push by cloning the object :
testData.value.push({...testObj})
Or put the definition in the push
testData.value.push({ name: 'aniket', lastname: 'mahadik' })
Is JavaScript a pass-by-reference or pass-by-value language?

How to not trigger watch when data is modified on specific cases

I'm having a case where I do wish to trigger the watch event on a vue project I'm having, basically I pull all the data that I need then assign it to a variable called content
content: []
its a array that can have multiple records (each record indentifies a row in the db)
Example:
content: [
{ id: 0, name: "First", data: "{jsondata}" },
{ id: 1, name: "Second", data: "{jsondata}" },
{ id: 2, name: "Third", data: "{jsondata}" },
]
then I have a variable that I set to "select" any of these records:
selectedId
and I have a computed property that gives me the current object:
selectedItem: function () {
var component = this;
if(this.content != null && this.content.length > 0 && this.selectedId!= null){
let item = this.content.find(x => x.id === this.selectedPlotBoardId);
return item;
}
}
using this returned object I'm able to render what I want on the DOM depending on the id I select,then I watch this "content":
watch: {
content: {
handler(n, o) {
if(o.length != 0){
savetodbselectedobject();
}
},
deep: true
}
}
this work excellent when I modify the really deep JSON these records have individually, the problem I have is that I have a different upload methord to for example, update the name of any root record
Example: changing "First" to "1"
this sadly triggers a change on the watcher and I'm generating a extra request that isnt updating anything, is there a way to stop that?
This Page can help you.
you need to a method for disables the watchers within its callback.

Issue with passing the data in Vue / EventBus

I have an issue that I do not understand regarding passing data in Vue.
In store I have the following data (six categories - wine, beer, whiskey, gin, vodka and rum):
state: {
shopItems: [
{
id: 1,
category: "wine",
price: 20,
image:
"data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD//Z",
items: [
{
id: 1,
name: "Ruby",
price: 17,
image:
"https://images.freshop.com/1564405684707189696/e52a7445e17de485c0ae890de8762d57_medium.png"
},
{
id: 2,
name: "Sauvignon Blanc",
price: 23,
image:
"https://ipcdn.freshop.com/resize?url=https://images.freshop.com/00898322593368/2e0ad0dbe0ef46eded5590acdf43cae5_large.png&width=256&type=webp&quality=40"
},
{
id: 3,
name: "Dark Horse",
price: 25,
image:
"https://ipcdn.freshop.com/resize?url=https://images.freshop.com/00085000024218/680b13803f3203f3117eb69082f95cc2_large.png&width=256&type=webp&quality=40"
},
{
id: 4,
name: "Andree",
price: 35,
image:
"https://ipcdn.freshop.com/resize?url=https://images.freshop.com/00085000008287/17d49b2b92223defb638ca7b535dbab3_large.png&width=256&type=webp&quality=40"
}
]
},
{
id: 2,
category: "beer",
price: 3,
image:
"data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAkGBxIQEhAQEg8TFhEXExYSGRcVFRYSEhYVGBUYGBUVFRYcAAEAAgIDAQAAAAAAAAAAAAAABgcEBQEDCAL/",
items: [
{
id: 1,
name: "Banana",
price: 5,
image:
"data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAkGBxAQDxIPEhIPFRAPEhcPEBYSEBgSDw8QFRIS0tLS0uLi0tLS8tLS8tLi0tLS0tLi0tLS8tLS0wLS0tLS0tLS0tLSstLf/AABEIAOEA4QMBEQACEQEDEQH/xAAcAAEAAgIDAQAAAAAAAAAAAAAABQYEBwECCAP/"
},
{
id: 2,
name: "Franziskaner Weissbier",
price: 6,
image:
"data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAkGBxIQEhAQEg8TFhEXExYSGRcVFRYSEhYVGBUDEQH/xAAcAAEAAgIDAQAAAAAAAAAAAAAABgcEBQEDCAL/xABIEAACAgECAgcEBwQFCgcAAAAAAQIDEQQhBRIGBxMiMUFRYXGBwRQjMnKRobFCUpLCJDNigtE0Q1 "
},
{
id: 3,
name: "Birra Moretti",
price: 5,
image:
"data:image/jpeg; QQFEiExBgcTIkFRYXGBFCMycpGhsdEkM0JSYsE1Q1OCkhUWJTREVIOTotLh8PH/xAAaAQEAAwEBAQAAAAAAAAAAAAAAAgMEAQUG/8QANBEBAAICAAQDBQcEAgMAAAAAAAECAxEEEiExE0FRBSIyYXGBkaGxwdHwFEJS4SPxFTOC/9oADAMBAAIRAxEAPwDuMBAQEBAQEBAQEBAQNHau1adKoe59wE7q91mLN5BVBJ+ "
}
]
},
.
.
.
In my application I am passing the data related to the index of the chosen category from ToBeBought component to ShowDetails component – using EventBus.
showDetails(item) {
console.log("#item.category");
console.log(item.category);
const chosenCategoryIndex = this.shopItems.findIndex(
i => i.category === item.category
);
// const chosenCategoryItems = this.shopItems[chosenCategoryIndex]
// .items;
console.log("#chosenCategoryIndex");
console.log(chosenCategoryIndex);
EventBus.$emit("chosenCategory", chosenCategoryIndex);
this.$router.push("/details");
I pass the index of the chosen category - e.g. for wine index is 0.
I run chosenCategoryItems method on mounted
(I tried running it on created as well but it did not help)
to get the data in ShowDetails component. In console.log from this method I get an array with detailed data e.g. wines but after that method in mounted I have another console log to show the array (console.log(this.detailedItemsToBeBought);) - I get undefined from it.
It looks like the data was cleared in the meantime - I do not get what is going on here.
I tried to add watcher on detailedItemsToBeBought data but it is not being triggered.
data() {
return {
itemsToBeBought: [],
detailedItemsToBeBought: []
};
},
mounted() {
this.chosenCategoryItems();
console.log("#detailedItemsToBeBought mounted");
console.log(this.detailedItemsToBeBought);
console.log("#this.shopItems[0].items");
console.log(this.shopItems[0].items);
},
methods: {
chosenCategoryItems() {
EventBus.$on("chosenCategory", data => {
const chosenCategoryIndex = data;
console.log("#chosenCategoryIndex");
console.log(chosenCategoryIndex);
this.detailedItemsToBeBought = this.shopItems[
chosenCategoryIndex
].items;
console.log("#detailedItemsToBeBought event bus");
console.log(this.detailedItemsToBeBought);
});
return this.detailedItemsToBeBought;
}
When I try to display the data I do not see anything but when hardcode this.shopItems[0].items I get the data I need - e.g. for wine category the array with wines;
I know it is not an easy issue but is anyone able and willing to take the challenge to explain me whot might had gone wrong here. Why I get the data from this.shopItems[0].items but when I pass index to this.detailedItemsToBeBought = this.shopItems[chosenCategoryIndex].items; - it works fine in the method chosenCategoryItems but after that the data this.detailedItemsToBeBought are undefined on mounted?
I attach screenshot from console.
[![enter image description here][1]][1]
[1]: https://i.stack.imgur.com/30rk7.png
and the whole repository:
https://github.com/agnesliszka/alcohol_shop
It looks like your component needs to register for the listener BEFORE you emit the event.
You might want to simply store the chosen category in the store, instead of using an event.
If you really want to use an event (not recommended), you can delay the emit until after the component is ready, using nextTick or setTimer.
You will also want to register the listener in created or mounted.
this.$nextTick(()=>{
EventBus.$emit("chosenCategory", chosenCategoryIndex);
})
this.$router.push("/details");

adding an item to the array causes Error in nextTick "RangeError"

I am learning VUE and I am trying to create a dynamic component to build navigation with children.
I have tried to create a new object with Object.assign and new array with Array, but always the same result.
here is part of my app related to this problem:
https://jsfiddle.net/mL48st20/
problem occurs in
CmsMenuItem.createChild() (on line 155)
createChild: function () {
// force expand list
this.isExpanded = true;
console.log("Item add-child");
--> this.data.childrens.push($BLANK_ITEM);
},
Everything works just fine if I add a child to the existing item (loaded in Vue constructor) The problem occurs only when I try to add a child to a newly created item.
It pushes the new object to the item children array and also to its children array and so on.. infinite times, which explains the error.
Do you guys have any idea, how to solve this?
Thanks in advance for any bits of advice.
You're setting the same $BLANK_ITEM instance as a child of different items. You need to create new item instances each time.
Instead of:
var $BLANK_ITEM = {
name: $texts.newItem,
url: '',
languages: [],
siteId: '',
icon: '',
target: '_self',
title: '',
childrens: []
};
you should have a function which creates a new item instance:
function createBlankItem() {
return {
name: $texts.newItem,
url: '',
languages: [],
siteId: '',
icon: '',
target: '_self',
title: '',
childrens: []
};
}
then replace $BLANK_ITEM throughout your code with createBlankItem(), e.g.:
methods: {
addItem: function () {
var item = createBlankItem();
this.$emit('add-item', item);
this.data.push(item);
}
}
You also have some other errors related to the Sortable plugin, but I'm not sure what's going on there.

Vuelidate empty object on select validation issue

I would like to know how to validate empty object using vuelidate. I tried to give a demonstration on jsfiddle as links follows
Vue.use(window.vuelidate.default)
const { required, minLength } = window.validators
new Vue(
{
el: "#app",
data: {
companies: [
{
id: 1,
name: 'facebook'
},
{
id: 2,
name: 'apple'
}
],
text: {
id: null,
name: null
}
},
validations: {
text: {
required
}
}
}
)
jsfiddle
$v.text is valid because it is a non-empty object. That means it doesn't have 'falsy' value so it meets the requirement. One way to make it work:
validations: {
text: {
id: {required},
name: {required},
},
},
JSFiddle
If you don't want to repeat items object structure, you can write a custom validator.
There is missing information about how to use withParams in the documentation of vuelidate page.
So i have searched on its github page and found this link .
According to link i came up with that solution
import { withParams } from 'vuelidate'
export const checkIfNull = withParams(
{ type: 'required' },
value => (value.id === null ? false : true)
)
There is nothing special about validating an object, you just need to define the structure and add any validation rules you require.
Please see the example I created and take another look at the Collections docs.