Unable to access sub-array in Vue 3 prop object item - javascript

In a Vue3/InertiaJS project, I am attempting to do a simple dynamic select list population, but I'm having issues reading the props data to populate the dropdown.
<script setup>
import {Head, Link, useForm} from '#inertiajs/inertia-vue3'
import SelectInput from '#/Components/ping/SelectInput.vue'
import {ref} from 'vue';
const props = defineProps({
areas: [
{
id:1,
name:"Club",
approvers: [
{
id:12,
first_name: "Joe",
last_name: "Blow"
}
]
},
{
id:2,
name:"Adult Small Bore",
approvers: [
{
id:34,
first_name: "Sam",
last_name: "Snead"
}
]
},
{
id:3,
name:"Appleseed",
approvers: [
{
id:56,
first_name: "Johhny",
last_name: "Appleseed"
}
]
},
{
id:4,
name:"Archery",
approvers: [
{
id:56,
first_name: "Jim",
last_name: "Beam"
},
{
id:56,
first_name: "Jack",
last_name: "Daniels"
}
]
},
{
id:5,
name:"Armed Women of America",
approvers: [
{
id: 99,
first_name: "Amelia",
last_name: "Earhart"
}
]
}
]
})
const form = useForm({
date: '',
hours: '',
area: '',
reason: '',
comments: '',
approver: ''
});
let selectApprovers = ref([]);
function updateApprovers(event) {
const area = props.areas.filter(area => area.id === parseInt(event.target.value));
console.log({area});
selectApprovers.value = area.approvers.map(function (item) {
return {
id: item.id,
name: item.first_name + ' ' + item.last_name
}
});
selectApprovers.value = approvers;
}
function store() {
form.post(`/admin/work-hours`)
}
</script>
<template>
<div>
<form #submit.prevent="store">
<select-input v-model="form.area" :error="form.errors.area"
class="pb-8 pr-6 w-full lg:w-1/2" label="Area" #change="updateApprovers">
<option :value="null" />
<option v-for="area in props.areas" :key="area.id" :value="area.id">{{ area.name }}</option>
</select-input>
<select-input v-model="form.approver" :error="form.errors.approver"
class="pb-8 pr-6 w-full lg:w-1/2" label="Approver">
<option :value="null" />
<option v-for="approver in selectApprovers" :key="approver.id" :value="approver.id">{{ approver.name }}</option>
</select-input>
</form>
</div>
</template>
where the <select-list> input is just a wrapper around a standard <select> element.
The props in the real app are coming from Laravel via InertiaJS. The issue I am having is in updateApprovers(). This line works fine:
const area = props.areas.filter(area => area.id === parseInt(event.target.value));
as seen in the console.log({area}). However, it errors out at the next line when it tries to read area.approvers, and I get the error
Uncaught TypeError: can't access property "map", area.approvers is undefined
I've tried using value
selectApprovers.value = area.value.approvers.map(function (item) {
but I get the same error. Visual inspection of the props in Vue dev tools shows that the approvers array is there; what do I need to change to be able to access it?
I've tried setting up an sfc playground, but for some reason it's having issues reading the v-for loop.

Array.filter always returns another array, so this line:
const area = props.areas.filter(area => area.id === parseInt(event.target.value));
results in area being an array of objects. Those individual objects may each have an inner approvers array, but the area array itself has no approvers property, which is why area.approvers.map() throws an error.
You could do area[0].approvers.map(), but I think a more appropriate Array function to use in the first place would be Array.find to return the first matching object which you could then map.
function updateApprovers(event) {
const area = props.value.areas.find(
area => area.id === parseInt(event.target.value)
);
selectApprovers.value = area.approvers.map(function (item) {
return {
id: item.id,
name: item.first_name + ' ' + item.last_name
};
});
}

Related

Node Js how to fetch data from database in an hierarchical way

I'm writing a back code using NodeJs to fetch some data from backend, I want dataBase data to be like this
like this:
data = [{
name: "Admin",
id: '1',
children: [
{ name: "Admin", id: "1" },
{ name: "groupe1", id: "2" },
{
name: "groupe2", id: "1455", children: [
{ name: "groupe2", id: "1455" },
{ name: "gro", id: "5444" },
{ name: "hhrr", id: "45" }
]
}
]
}]
the idea is simple we have a list of group each group has a parent I want to display all the groups list in an hierarchical way the top one of the tree is done
Some groups are parents and groups in the same time and some others are only groups if the group is not parent we add an object with its name and ID in the array of children of his parent
if this groups is a parent that's mean it has children we add an object with its ID and name in the array of children of his parents, and we add property children for the object which is array named children with for the first time an object with the name and the id of the group etc...
i tryed to do this but it did not work
const getParentsByType = async ({ name, _id }) => {
let parentResult = [
{
id: _id,
name: name,
children: [
{
id: _id,
name: name,
},
],
},
];
parentResult= await findParent(_id, parentResult[0].children, 0);
return parentResult;
};
const findParent = async (parentId, parentResult, itemPos) => {
let children = await Models.GroupModel.find({ parent: parentId, status: true }).select('name _id');
for (let i = 0; i < children.length; i++) {
let childrenList = await Models.GroupModel.find({ parent: children[i]._id, status: true }).select('name _id');
if (childrenList.length != 0) {
parentResult.push(buildParentWithChild(children[i]._id, children[i].name));
findParent(children[i]._id,parentResult.children[i],itemPos++)
} else {
parentResult.push(buildParent(children[i]._id, children[i].name));
}
}
return parentResult
};
and this the model of the data base
const Group = mongoose.Schema({
name: {
type: String,
required: true,
},
status: {
type: Boolean,
required: true,
},
parent: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Group',
},
});
i had two days trying to resolve tis but with no result
i need some helps and Thank you
Try parsing your returned data. It validates your data as objects i dont see any problem with your function regardless i still have no idea what format your a trying to build.
let children = JSON.parse(JSON.stringify(await Models.GroupModel.find({ parent: parentId, status: true }).select('name _id')));
let childrenList = JSON.parse(JSON.stringify(await Models.GroupModel.find({ parent: children[i]._id, status: true }).select('name _id')));
If I understand you right, you want to convert the array returned by Models.GroupModel.find, and which looks like
var dbresult = [
{_id: "1", parent: null, name: "one"},
{_id: "2", parent: "1", name: "two"}
];
into a hierarchical structure. This can be done with a function that adds all children of a given parent p, including, recursively, their children. Like the following:
function children(p) {
var result = [];
for (r of dbresult) if (r.parent === p) {
var row = {_id: r._id, name: r.name};
var chld = children(r._id);
if (chld.length > 0) row.children = chld;
result.push(row);
}
return result;
}
console.log(JSON.stringify(children(null)));
Note that this approach requires only one database access (to fill the dbresult) and is therefore probably faster than your findParent function.

How to input and create two (or more than two) fields using React-Admin?

The following code provided in React-Admin docs lets me pick a record and enter only ONE field into the database:
const choices = [
{ id: 123, first_name: 'Leo', last_name: 'Tolstoi' },
{ id: 456, first_name: 'Jane', last_name: 'Austen' },
];
const optionRenderer = choice => `${choice.first_name} ${choice.last_name}`;
<AutocompleteInput source="author_id" choices={choices} optionText={optionRenderer} optionValue = "first_name" />
So in this case, a field called "first_name" would be inserted into my database table.
Is there a way to enter all three fields as an input? For example, I would want 3 separate fields
id: 456,
first_name: 'Jane',
last_name: 'Austen'
to be inserted into the database(not as a dictionary of 3 fields but 3 independent fields).
<AutocompleteInput> allows to select an existing record related to the current one (e.g. choosing the author for a post).
I understand that you want to create a new record instead. You can do so via the onCreate prop, as explained in the doc:
import { AutocompleteInput, Create, SimpleForm, TextInput } from 'react-admin';
const PostCreate = () => {
const categories = [
{ name: 'Tech', id: 'tech' },
{ name: 'Lifestyle', id: 'lifestyle' },
];
return (
<Create>
<SimpleForm>
<TextInput source="title" />
<AutocompleteInput
onCreate={(filter) => {
const newCategoryName = window.prompt('Enter a new category', filter);
const newCategory = { id: categories.length + 1, name: newCategoryName };
categories.push(newCategory);
return newCategory;
}}
source="category"
choices={categories}
/>
</SimpleForm>
</Create>
);
}

unable to destructuring two objects simultaneously

How can I map movies by using columns as a property reference.
Like
{movies.map(item => {columns.map(column => item.column.path)})}
but using this i'm getting result as undefined
Movies contains all details about movies
const movies = [
{
_id: "5b21ca3eeb7f6fbccd471815",
title: "Terminator",
genre: { _id: "5b21ca3eeb7f6fbccd471818", name: "Action" },
numberInStock: 6,
dailyRentalRate: 2.5,
publishDate: "2018-01-03T19:04:28.809Z",
liked: true,
},
{
_id: "5b21ca3eeb7f6fbccd471816",
title: "Die Hard",
genre: { _id: "5b21ca3eeb7f6fbccd471818", name: "Action" },
numberInStock: 5,
dailyRentalRate: 2.5,
},
{
_id: "5b21ca3eeb7f6fbccd471817",
title: "Get Out",
genre: { _id: "5b21ca3eeb7f6fbccd471820", name: "Thriller" },
numberInStock: 8,
dailyRentalRate: 3.5,
},
{
_id: "5b21ca3eeb7f6fbccd471819",
title: "Trip to Italy",
genre: { _id: "5b21ca3eeb7f6fbccd471814", name: "Comedy" },
numberInStock: 7,
dailyRentalRate: 3.5,
},
{
_id: "5b21ca3eeb7f6fbccd47181a",
title: "Airplane",
genre: { _id: "5b21ca3eeb7f6fbccd471814", name: "Comedy" },
numberInStock: 7,
dailyRentalRate: 3.5,
}];
Columns contains all properties that are needed to access the movies
const columns = [
{ path: "title", label: "Title" },
{ path: "genre", label: "Genre" },
{ path: "numberInStock", label: "Stock" },
{ path: "dailyRentalRate", label: "Rate" }];
I know this problem can be solved using 2 loops.
1 outer loop for movies after getting each movie, use a 2nd internal loop to access the properties
Update: After getting #rory-mccrossan answer
Further, how can I map this data in a table such that
This is the code part regarding the same that I used.
import React, { Component } from "react";
import _ from "lodash";
class TableBody extends Component {
renderCell = (item, column) => {
if (column.content) return column.content(item);
return _.get(item, column.path);
};
createKey = (item, column) => {
return item._id + (column.path || column.key);
};
render() {
const { data, columns } = this.props;
return (
<tbody>
{data.map((item) => (
<tr key={item._id}>
{columns.map((column) => (
<td key={this.createKey(item, column)}>
{this.renderCell(item, column)}
</td>
))}
</tr>
))}
</tbody>
);
}
}
export default TableBody;
But I'm getting the error: Objects are not valid as a React child (found: object with keys {_id, name}). If you meant to render a collection of children, use an array instead.)
The main issue is that how can I map data in a table
There's two issues here. Firstly the braces around the second map() call will be interpreted as a code block, yet it does not return any value so the resulting array will be empty. Remove those braces.
Secondly, item.column.path needs to be item[column.path] as you're using column.path as the property accessor of item.
Here's a working example:
const movies = [{_id:"5b21ca3eeb7f6fbccd471815",title:"Terminator",genre:{_id:"5b21ca3eeb7f6fbccd471818",name:"Action"},numberInStock:6,dailyRentalRate:2.5,publishDate:"2018-01-03T19:04:28.809Z",liked:!0},{_id:"5b21ca3eeb7f6fbccd471816",title:"Die Hard",genre:{_id:"5b21ca3eeb7f6fbccd471818",name:"Action"},numberInStock:5,dailyRentalRate:2.5},{_id:"5b21ca3eeb7f6fbccd471817",title:"Get Out",genre:{_id:"5b21ca3eeb7f6fbccd471820",name:"Thriller"},numberInStock:8,dailyRentalRate:3.5},{_id:"5b21ca3eeb7f6fbccd471819",title:"Trip to Italy",genre:{_id:"5b21ca3eeb7f6fbccd471814",name:"Comedy"},numberInStock:7,dailyRentalRate:3.5},{_id:"5b21ca3eeb7f6fbccd47181a",title:"Airplane",genre:{_id:"5b21ca3eeb7f6fbccd471814",name:"Comedy"},numberInStock:7,dailyRentalRate:3.5}];
const columns = [{path:"title",label:"Title"},{path:"genre",label:"Genre"},{path:"numberInStock",label:"Stock"},{path:"dailyRentalRate",label:"Rate"}];
let output = movies.map(item => columns.map(column => item[column.path]));
console.log(output);

js how to check two objects values in an array are together

If I have the array:
let messages = [
{
username: 'john',
message: 'hi'
},
{
username: 'john',
message: 'there'
},
{
username: 'bob',
message: 'hello'
},
{
username: 'john',
message: 'whats up'
}
]
If i have messages like:
In vuejs rendered out how would I combine messages with the same username and render the text under each other?
Don't to it in the view, use a computed to get the data you want. You can then use <template> tags to control which elements are shown, that way you don't need to wrap the elements into a single DOM element.
Below is an example that shows a straight forward way of generating an array for the computed that can then be iterated over.
Vue.createApp({
data() {
return {
messages: [{
username: 'john',
message: 'hi'
},
{
username: 'john',
message: 'there'
},
{
username: 'bob',
message: 'hello'
},
{
username: 'john',
message: 'whats up'
}
]
}
},
computed: {
byUser() {
const arr = [];
let tempName = null;
let tempGroup = {}
this.messages.forEach(m => {
if (tempName !== m.username) {
tempGroup = {
username: m.username,
messages: []
}
arr.push(tempGroup);
}
tempGroup.messages.push(m.message);
tempName = m.username;
})
return arr;
}
}
}).mount("#app")
<script src="https://unpkg.com/vue#next/dist/vue.global.prod.js"></script>
<div id="app" class="container">
<template v-for="(m, i) in byUser">
<h2>
{{ m.username }}
</h2>
<p v-for="message in m.messages">
{{ message }}
</p>
<hr>
</template>
</div>

Multiple select Vue.js and computed property

I'm using Vue.js 2.0 and the Element UI library.
I want to use a multiple select to attribute some roles to my users.
The list of all roles available is received and assigned to availableRoles. Since it is an array of object and the v-model accepts only an array with value, I need to extract the id of the roles trough the computed property computedRoles.
The current roles of my user are received and assigned to userRoles: [{'id':1, 'name':'Admin'}, {'id':3, 'name':'User'}].
computedRoles is then equals to [1,3]
The preselection of the select is fine but I can't change anything (add or remove option from the select)
What is wrong and how to fix it?
http://jsfiddle.net/3ra1jscx/3/
<div id="app">
<template>
<el-select v-model="computedRoles" multiple placeholder="Select">
<el-option v-for="item in availableRoles" :label="item.name" :value="item.id">
</el-option>
</el-select>
</template>
</div>
var Main = {
data() {
return {
availableRoles: [{
id: 1,
name: 'Admin'
}, {
id: 2,
name: 'Power User'
}, {
id: 3,
name: 'User'
}],
userRoles: [{'id':1, 'name':'Admin'}, {'id':3, 'name':'User'}]
}
},
computed : {
computedRoles () {
return this.userRoles.map(role => role.id)
}
}
}
I agree mostly with #wostex answer, but he doesn't give you the userRoles property back. Essentially you should swap computedRoles and userRoles. userRoles becomes a computed property and computedRoles is a data property. In my update, I changed the name of computedRoles to selectedRoles.
var Main = {
data() {
return {
availableRoles: [{
id: 1,
name: 'Admin'
}, {
id: 2,
name: 'Power User'
}, {
id: 3,
name: 'User'
}],
selectedRoles:[1,2]
}
},
computed : {
userRoles(){
return this.availableRoles.reduce((selected, role) => {
if (this.selectedRoles.includes(role.id))
selected.push(role);
return selected;
}, [])
}
}
}
var Ctor = Vue.extend(Main)
new Ctor().$mount('#app')
And here is the fiddle.
Check the solution: jsfiddle
The caveat here is that computed properties are getters mainly. You can define setter for computed property, but my approach is more vue-like in my opinion.
In short, instead of v-model on computed set v-model for data property.
Full code:
<script src="//unpkg.com/vue/dist/vue.js"></script>
<script src="//unpkg.com/element-ui/lib/index.js"></script>
<div id="app">
<template>
<el-select v-model="ids" multiple placeholder="Select" #change="logit()">
<el-option v-for="item in availableRoles" :label="item.name" :value="item.id">
</el-option>
</el-select>
</template>
</div>
var Main = {
data() {
return {
availableRoles: [{
id: 1,
name: 'Admin'
}, {
id: 2,
name: 'Power User'
}, {
id: 3,
name: 'User'
}],
userRoles: [{'id':1, 'name':'Admin'}, {'id':3, 'name':'User'}],
ids: []
}
},
mounted() {
this.ids = this.userRoles.map(role => role.id);
},
methods: {
logit: function() {
console.log(this.ids);
}
}
}
var Ctor = Vue.extend(Main)
new Ctor().$mount('#app')

Categories

Resources