I got an object from API resource and put it inside the property, then children component can't access the props's object inside created method to assign it values inside my data properties as arrays and strings
when i try to console the props from child component i found my items object inside it
"This is my parent component"
<template>
<v-container grid-list-xl fill-height>
<v-layout row wrap>
<v-flex xs6 offset-xs3>
<message-box :items="source" v-if="source.length > 0"></message-box>
</v-flex>
</v-layout>
</v-container>
</template>
<script>
<script>
import MessageBox from './MessageBox'
export default {
components:{MessageBox},
data() {
return {
room_id: 1,
source: {},
};
},
created(){
var app = this;
axios.get(`/api/room/${app.room_id}/message`)
.then(res => app.source = res.data.data);
}
};
</script>
</script>
"This is my child component"
<template>
<div>
<beautiful-chat
:participants="participants"
:titleImageUrl="titleImageUrl"
:onMessageWasSent="onMessageWasSent"
:messageList="messageList.messageList"
:newMessagesCount="newMessagesCount"
:isOpen="isChatOpen"
:close="closeChat"
:icons="icons"
:open="openChat"
:showEmoji="true"
:showFile="true"
:showTypingIndicator="showTypingIndicator"
:colors="colors"
:alwaysScrollToBottom="alwaysScrollToBottom"
:messageStyling="messageStyling"
#onType="handleOnType"
#edit="editMessage"
/>
</div>
</template>
<script>
import CloseIcon from "vue-beautiful-chat/src/assets/close-icon.png";
import OpenIcon from "vue-beautiful-chat/src/assets/logo-no-bg.svg";
import FileIcon from "vue-beautiful-chat/src/assets/file.svg";
import CloseIconSvg from "vue-beautiful-chat/src/assets/close.svg";
export default {
props: ['items'],
data() {
return {
room_id: 1,
participants:[],
messageList: [],
limit: 7,
busy: false,
auth_uid: User.id(),
titleImageUrl:
"https://a.slack-edge.com/66f9/img/avatars-teams/ava_0001-34.png",
newMessagesCount: 0,
isChatOpen: false,
alwaysScrollToBottom: false, // when set to true always scrolls the chat to the bottom when new events are in (new message, user starts typing...)
messageStyling: true,
showTypingIndicator: "",
icons: {
open: {
img: OpenIcon,
name: "default"
},
close: {
img: CloseIcon,
name: "default"
},
file: {
img: FileIcon,
name: "default"
},
closeSvg: {
img: CloseIconSvg,
name: "default"
}
},
colors: {
header: {
bg: "#4e8cff",
text: "#ffffff"
},
launcher: {
bg: "#4e8cff"
},
messageList: {
bg: "#ffffff"
},
sentMessage: {
bg: "#4e8cff",
text: "#ffffff"
},
receivedMessage: {
bg: "#eaeaea",
text: "#222222"
},
userInput: {
bg: "#f4f7f9",
text: "#565867"
}
}
};
},
methods: {
sendMessage(text) {
if (text.length > 0) {
this.newMessagesCount = this.isChatOpen
? this.newMessagesCount
: this.newMessagesCount + 1;
this.onMessageWasSent({
author: "support",
type: "text",
data: { text }
});
axios
.post(`/api/room/${this.room_id}/message`, { body: text })
.then(res => console.log("message sent"));
}
},
onMessageWasSent(message) {
// called when the user sends a message
this.messageList = [...this.messageList, message];
},
openChat() {
// called when the user clicks on the fab button to open the chat
this.isChatOpen = true;
this.newMessagesCount = 0;
},
closeChat() {
// called when the user clicks on the botton to close the chat
this.isChatOpen = false;
},
handleScrollToTop() {
// called when the user scrolls message list to top
// leverage pagination for loading another page of messages
},
handleOnType() {
console.log("Emit typing event");
},
editMessage(message) {
const m = this.messageList.find(m => m.id === message.id);
m.isEdited = true;
m.data.text = message.data.text;
},
},
created(){
// console.log(this.$props.items)
Array.prototype.forEach.call(this.$props.items, child => {
this.participants = child.participants;
// console.log(this.participants)
this.messageList = child.body;
// console.log(this.messageList)
});
},
computed:{
itemarr(){
return this.$props.items
}
}
};
</script>
"The console error is TypeError: Array.prototype.forEach called on null or undefined"
"The output of my items object is {__ob__: Observer}"
You can use v-if to solve your problem. You need to wait for ajax response to render child component
<template>
<v-container grid-list-xl fill-height>
<v-layout row wrap>
<v-flex xs6 offset-xs3>
<message-box v-if="sourceLength > 0" :items="source"></message-box>
</v-flex>
</v-layout>
</v-container>
</template>
<script>
<script>
import MessageBox from './MessageBox'
export default {
components:{MessageBox},
data() {
return {
room_id: 1,
source: {},
};
},
created(){
var app = this;
axios.get(`/api/room/${app.room_id}/message`)
.then(res => app.source = res.data.data);
},
get sourceLength() {
return Object.keys(this.source).length;
}
};
</script>
</script>
Related
User inputs a company's ID number and it will return them a list of addresses that belong to that company split in 3 objects (street, zip code, city).
When they click on one of those addresses, I want the 3 elements of the selected address to be stored in the postData() function:
<template>
<div>
<v-card-text>
<v-row v-if="ask_id">
<v-text-field label="Enter the id_number" v-model="id_number"></v-text-field>
<v-btn
#click="
send_id_number();
show_results()">
</v-btn>
</v-row>
<v-row v-else>
<v-select v-model="res_data"
:items="filteredData" label="Places" :item-text="
(item) => item.street + item.zip_code + item.city">
</v-select>
</v-row>
</v-card-text>
</div>
</template>
<script>
import axios from 'axios';
export default {
props: ["id"],
data() {
return {
ask_id: true,
id_number: null,
res_object: [],
res_data: [],
places: []
};
},
computed: {
filteredData() {
return this.res_object.places
? this.res_object.places.filter((g) => {
return g.places_closed == false;
})
: [];
},
postData() {
let data = {
selected_street: this.?,
selected_zip: this.?,
selected_city: this.?,
id_number: this.?
}
return data
}
},
methods: {
show_results() {
this.mode = false
},
send_id_number() {
axios.get(`https://api.places_url.com/${this.id_number}`)
.then((response) => {
this.res_object = response.data
})
.catch((err) => {
console.log(err)
})
}
},
};
</script>
However whatever I try to do tells me TypeError: undefined is not an object. I'm missing a step but I'm not sure what it is.
structure of the api response:
{
"company_id": 123456789,
"places":[
{"street":"8 main street",
"zip_code":"303",
"city":"RandomCity",
"places_closed":false
},
{"street":"10 main street",
"zip_code":"404",
"city":"RandomCity",
"places_closed":false
},
{"street":"11 main street",
"zip_code":"505",
"city":"RandomCity",
"places_closed":false
}
]
}
you can try something like the below add return-object props in v-select and then you get res_data v-model as an object.
<template>
<div>
<v-card-text>
<v-row v-if="ask_id">
<v-text-field label="Enter the id_number" v-model="id_number"></v-text-field>
<v-btn
#click="
send_id_number();
show_results()">
</v-btn>
</v-row>
<v-row v-else>
<v-select #change="postData()" v-model="res_data"
:items="filteredData" label="Places" :item-text="
(item) => item.street +' '+ item.zip_code +' '+ item.city" return-object>
</v-select>
</v-row>
</v-card-text>
</div>
</template>
<script>
import axios from 'axios';
export default {
props: ["id"],
data() {
return {
ask_id: false,
id_number: null,
res_object:
{
"company_id": 123456789,
"places":[
{"street":"8 main street",
"zip_code":"303",
"city":"RandomCity",
"places_closed":false
},
{"street":"10 main street",
"zip_code":"404",
"city":"RandomCity",
"places_closed":false
},
{"street":"11 main street",
"zip_code":"505",
"city":"RandomCity",
"places_closed":false
}
]
},
res_data: [],
places: []
};
},
computed: {
filteredData() {
return this.res_object.places
? this.res_object.places.filter((g) => {
return g.places_closed == false;
})
: [];
},
},
methods: {
postData() {
console.log(this.res_data);
return this.res_data;
},
show_results() {
this.mode = false
},
send_id_number() {
console.log(this.id_number);
axios.get(`https://api.places_url.com/${this.id_number}`)
.then((response) => {
this.res_object = response.data
})
.catch((err) => {
console.log(err)
})
}
},
};
</script>
Let me know if you want something else
I'm trying to implement a component where, if a field changes, it may trigger a code path that necessitates user feedback. I'm using another, fairly widely used component (ViewDialogue), to render and return that feedback.
The problem I am experiencing is, if multiple changes occur, I only receive the dialogue for the final one. For example: In the AChange function, I will only receive the dialogue for HandleA() even if UpdateB and UpdateC are triggered.
PARENT COMPONENT:
<template>
<v-card>
<v-text-field
v-model="valueC"
#change="CChange"
></v-text-field>
<v-text-field
v-model="valueB"
#change="BChange"
></v-text-field>
<v-text-field
v-model="valueA"
#change="AChange"
></v-text-field>
<v-dialog v-model="qdialog" width="500">
<ViewDialogue :question="question" #HandleAnswer="HandleAnswer" />
</v-dialog>
</v-card>
</template>
<script>
export default {
data: () => ({
qdialog: false,
question: '',
valueA: 0,
valueB: 0,
valueC: 0,
answerA: false,
answerB: false,
answerC: false,
BChanged: false,
CChanged: false,
}),
methods: {
HandleAnswer(x) {
if (x === true) {
if (this.answerA) {
this.DoA()
} else if (this.answerB) {
this.DoB()
} else if (this.answerC) {
this.DoC()
}
} else {
this.answerA = false
this.answerB = false
this.answerC = false
this.question = ''
this.qdialog = false
}
},
BChange() {
this.BChanged = true
},
CChange() {
this.CChanged = true
},
DoA() {
this.valueA = this.valueB
this.answerB = false
this.qdialog = false
},
DoB() {
this.valueB = this.valueA
this.answerB = false
this.qdialog = false
},
DoC() {
this.valueC = this.valueA
this.answerC = false
this.qdialog = false
},
UpdateB() {
if (this.valueB !== this.valueA) {
this.question = 'Update B to A?'
this.answerB = true
this.qdialog = true
}
},
UpdateC() {
if (this.valueC !== this.valueA) {
this.question = 'Update C to A?'
this.answerC = true
this.qdialog = true
}
},
HandleC() {
if (this.BChanged) {
this.UpdateB()
}
if (this.CChanged) {
this.UpdateC()
}
},
HandleA() {
if (this.valueA < this.valueB + this.valueC) {
this.question = 'Update A to B?'
this.answerA = true
this.qdialog = true
}
},
AChange() {
this.HandleC()
this.HandleA()
},
},
}
</script>
CHILD COMPONENT: ViewDialogue
<template>
<v-card>
<v-card-title class="text-h5 grey lighten-2">
{{ question }}
</v-card-title>
<v-divider></v-divider>
<v-card-actions>
<v-btn color="primary" text #click="HandleAnswer(false)"> No </v-btn>
<v-spacer></v-spacer>
<v-btn color="primary" text #click="HandleAnswer(true)"> Yes </v-btn>
</v-card-actions>
</v-card>
</template>
<script>
export default {
props: {
question: { type: String, default: '' },
},
emits: ['HandleAnswer'],
data: () => ({}),
methods: {
HandleAnswer(a) {
this.$emit('HandleAnswer', a)
},
},
}
</script>
I'm not thrilled about this answer, as it seems to hook into the nuxt instance, but here we are. Used [this][1] Dialog component.
[1]: https://gist.github.com/eolant/ba0f8a5c9135d1a146e1db575276177d
In my Plugins/core-components.js:
import ConfirmDialog from '#/components/Test/ConfirmDialog.vue'
Vue.component('ConfirmDialog', ConfirmDialog)
In my layouts/default.vue:
<template>
<v-main>
<v-app id="app">
<ConfirmDialog ref="confirm"></ConfirmDialog>
<nuxt />
</v-app>
</v-main>
</template>
<script>
export default {
async mounted() {
this.$nuxt.$root.$confirm = this.$refs.confirm
},}
</script>
Finally, my Parent Component (no child involved now)
<script>
export default {
data: () => ({
qdialog: false,
question: '',
valueA: 0,
valueB: 0,
valueC: 0,
answerA: false,
answerB: false,
answerC: false,
BChanged: false,
CChanged: false,
}),
methods: {
HandleAnswer(x) {
return new Promise((resolve, reject) => {
if (x === true) {
if (this.answerA) {
this.DoA()
} else if (this.answerB) {
this.DoB()
} else if (this.answerC) {
this.DoC()
}
resolve(true)
} else {
this.answerA = false
this.answerB = false
this.answerC = false
this.question = ''
this.qdialog = false
}
resolve(false)
}}),
BChange() {
this.BChanged = true
},
CChange() {
this.CChanged = true
},
DoA() {
this.valueA = this.valueB
this.answerB = false
},
DoB() {
this.valueB = this.valueA
this.answerB = false
},
DoC() {
this.valueC = this.valueA
this.answerC = false
},
async UpdateB() {
if (this.valueB !== this.valueA) {
if (
await this.$nuxt.$root.$confirm.open(
'Update B to A?',
'Are you sure?',
{ color: 'red',}
)
) {
this.answerB= true
await this.HandleAnswer(true)
} else {
await this.HandleAnswer(false)
}
}
},
async UpdateC() {
if (this.valueC !== this.valueA) {
if (
await this.$nuxt.$root.$confirm.open(
'Update C to A?',
'Are you sure?',
{ color: 'red',}
)
) {
this.answerC = true
await this.HandleAnswer(true)
} else {
await this.HandleAnswer(false)
}
}
},
async HandleC() {
if (this.BChanged) {
await this.UpdateB()
}
if (this.CChanged) {
await this.UpdateC()
}
},
async HandleA() {
if (this.valueA < this.valueB + this.valueC) {
if (
await this.$nuxt.$root.$confirm.open(
'Update A to B?',
'Are you sure?',
{ color: 'red',}
)
) {
this.answerA = true
await this.HandleAnswer(true)
} else {
await this.HandleAnswer(false)
}
}
},
async AChange() {
await this.HandleC()
await this.HandleA()
},
},
}
</script>
so I have a json file which contains a bunch of visual test results and I would like to have a column in the data table for each row which have a dynamic path for a vue page that showcases the details for each test.I've got most of it figured out except the dynamic routing and passing parameters to the dynamic page.Here's the datatable component that I created (I'm using vuetify and nuxt btw)
<template>
<div>
<v-text-field
v-model="search"
label="Search"
class="mx-4"
></v-text-field>
<v-data-table
:headers="headers"
:items="tests"
:items-per-page="15"
:search="search"
class="elevation-1"
>
<template v-slot:[`item.status`]="{ item }">
<v-chip
:color="getColor(item.status)"
dark
>
{{ item.status }}
</v-chip>
</template>
<template #[`item.details`]="{ item }">
<nuxt-link :to="`tests/${item.name}`">show details</nuxt-link>
</template>
</v-data-table>
</div>
</template>
<script>
import testtable from '../../VisualTests/test_07_03_2022_10_13_48/Tests_results.json';
export default {
data() {
return {
search: '',
tests: testtable,
headers: [
{
text: 'Test tag',
align: 'start',
sortable: true,
value: 'tag',
},
{ text: 'testname', value: 'name' },
{ text: 'status', value: 'status' },
{ text: 'details', value: 'details' },
]
}
},
methods: {
getColor (status) {
if (status=="failed") return 'red'
else if (status=="skipped/pending") return 'blue'
else return 'green'
}
}
}
</script>
<style lang="scss" scoped>
</style>
this is my nuxt router file :
import Vue from 'vue'
import Router from 'vue-router'
import { normalizeURL, decode } from 'ufo'
import { interopDefault } from './utils'
import scrollBehavior from './router.scrollBehavior.js'
const _05a6b87a = () => interopDefault(import('..\\pages\\tests\\_name\\index.vue' /* webpackChunkName: "pages/tests/_name/index" */))
const _f1328bfa = () => interopDefault(import('..\\pages\\index.vue' /* webpackChunkName: "pages/index" */))
const emptyFn = () => {}
Vue.use(Router)
export const routerOptions = {
mode: 'history',
base: '/',
linkActiveClass: 'nuxt-link-active',
linkExactActiveClass: 'nuxt-link-exact-active',
scrollBehavior,
routes: [{
path: "/tests/:name",
component: _05a6b87a,
name: "tests-name"
}, {
path: "/",
component: _f1328bfa,
name: "index"
}],
fallback: false
}
export function createRouter (ssrContext, config) {
const base = (config._app && config._app.basePath) || routerOptions.base
const router = new Router({ ...routerOptions, base })
// TODO: remove in Nuxt 3
const originalPush = router.push
router.push = function push (location, onComplete = emptyFn, onAbort) {
return originalPush.call(this, location, onComplete, onAbort)
}
const resolve = router.resolve.bind(router)
router.resolve = (to, current, append) => {
if (typeof to === 'string') {
to = normalizeURL(to)
}
return resolve(to, current, append)
}
return router
}
I want to pass the testname,tag and status to the dynamic page.
So far, I have only been able to pass 1 parameter(name).
my dynamic page is in a folder named tests inside pages with a nested '_name' folder that contains index.vue.
How can I pass all the parameters ?
For example, I have two lists: income and outcome. And I have two storages (one for income and one for outcome). I am adding these storages in modules into index.js.
I can make one repository for these income and outcome, display it in the list and calculate it. But I want to make a separate store for each.
Now the question is: How can I implement this correctly? I roughly did. But here I show and calculate only INCOME and that's it.
How to do it better? import via ...mapGetters two storages in one component to be calculate and show in the list? Or take data from two storages, and calculate everything in the index.js. Then take this data from the index.js? How do I use multiple modules in one component? I want to show the balance of income and outcome in one component and show in the list.
index.js
import Vue from "vue";
import Vuex from "vuex";
import income from "./modules/income";
import outcome from "./modules/outcome";
Vue.use(Vuex);
export default new Vuex.Store({
state: {},
mutations: {},
actions: {},
modules: {
income,
outcome,
},
});
income.js
import Vue from "vue";
const income = {
namespaced: true,
state: {
list: {
1: {
type: "INCOME",
value: 100,
comment: "Some comment",
id: 1,
},
},
},
getters: {
incomeList: ({ list }) => list,
},
mutations: {
},
actions: {
},
},
};
export default income;
outcome.js
// import Vue from "vue";
const outcome = {
namespaced: true,
state: {
list: {
1: {
type: "OUTCOME",
value: -50,
comment: "Some outcome comment",
id: 2,
},
},
},
getters: {
outcomeList: ({ list }) => list,
},
mutations: {
},
actions: {
},
};
export default outcome;
this is my component where i calculate balance
<template>
<div class="total-value" :style="balanceColor">
Balance: {{ totalBalance }}
</div>
</template>
<script>
import {mapGetters} from 'vuex';
export default {
name: 'TBalance',
computed: {
balanceColor: function() {
return {
color: this.totalBalance === 0 ? 'black' : this.totalBalance > 0 ? 'green' : 'red'
}
},
totalBalance() {
return Object.values(this.incomeList).reduce((acc, item) => acc + item.value, 0)
},
...mapGetters("income", ["incomeList"]),
},
methods: {
}
}
</script>
Here is an option for a more correct use of the store with modules.
I also put the calculation in the getter, which makes your component clean.
Try to bring the logic to the store so you can use the balance amount anywhere.
index.js
import Vue from "vue";
import Vuex from "vuex";
import income from "./modules/income";
import outcome from "./modules/outcome";
Vue.use(Vuex);
export default new Vuex.Store({
state: {},
mutations: {},
actions: {},
modules: {
income,
outcome,
},
});
income.js
const income = {
namespaced: true,
state: {
list: {
1: {
type: "INCOME",
value: 100,
comment: "Some comment",
id: 1,
},
},
},
getters: {
incomeBalance: state => {
// also, you can move this function into a separate file, and reuse
return Object.values(state.list).reduce((acc, item) => acc + item.value, 0);
},
},
};
export default income;
outcome.js
const outcome = {
namespaced: true,
state: {
list: {
1: {
type: "OUTCOME",
value: -50,
comment: "Some outcome comment",
id: 2,
},
},
},
getters: {
outcomeBalance: state => {
return Object.values(state.list).reduce((acc, item) => acc + item.value, 0);
},
},
};
export default outcome;
It is your component
<template>
<div class="total-value" :style="balanceColor">Balance: {{ incomeBalance }}</div>
</template>
<script>
import { mapGetters, mapState } from 'vuex';
export default {
name: 'TBalance',
computed: {
...mapState('outcome', ['list']), // if you want a list here i added for example
...mapState('income', ['list']), // if you want a list here i added for example
...mapGetters('outcome', ['outcomeBalance']), // also added it for example
...mapGetters('income', ['incomeBalance']),
balanceColor() {
return {
color: this.incomeBalance === 0 ? 'black' : this.incomeBalance > 0 ? 'green' : 'red',
};
},
},
methods: {},
};
</script>
I have created a simple to do app in VUE.
In order to delete a card (each card is an object with an id, title and description located in state in an App.vue component), I am passing and id from App as a prop to TaskList and to the button (delete) in the Task component. Then in order trigger a deleteTask function, again I am emmiting an id from Task to TaskList and then to App.
This approach works. However, is that kind of long chain of emmiting is considered as good practice? Is there a better way to do it?
App.vue
<template>
<div>
<TaskList :tasks="tasks" #id="deleteTask"/>
<Form :length="tasks.length" #addTask="addNewTask" />
</div>
</template>
<script>
import TaskList from './components/TaskList';
import Form from './components/Form';
export default {
name: 'App',
components: { TaskList, Form },
data() {
return {
tasks: [
{
id: 1,
title: 'Hello World',
description: 'this is the world'
},
{
id: 2,
title: 'Hello Mars',
description: 'this is the mars'
},
{
id: 3,
title: 'Hello Jupiter',
description: 'this is the jupiter'
}
]
}
},
methods: {
addNewTask(taskObject) {
const listOfTasks = this.tasks.concat(taskObject);
this.tasks = listOfTasks;
},
deleteTask(id) {
const filteredList = this.tasks.filter(element => {
return element.id != id;
})
this.tasks = filteredList;
}
}
}
</script>
TaskList.vue
<template>
<div class="taskList" v-bind:key="task" v-for="task in tasks">
<Task :title="task.title" :description="task.description" :id="task.id" #id="sendId"/>
</div>
</template>
<script>
import Task from './Task';
export default {
props: ['tasks'],
components: { Task },
methods: {
sendId(id) {
this.$emit('id', id);
console.log(id)
}
}
}
</script>
Task.vue
<template>
<div class="task">
<h1>{{ title }}</h1>
<p>{{ description }}</p>
<button #click="passId">Delete</button>
</div>
</template>
<script>
export default {
props: ['title', 'description', 'id'],
methods: {
passId() {
this.$emit('id', this.id);
}
}
}
</script>
One sure way of reducing this chain of data transfer is by using Vuex, But if you don't want to use that you can also use an "EventBus"
NOTE - Still you will have to pass the id from parent to child
Creating event bus
// src > eventBus.js
import Vue from 'vue'
export default new Vue()
Emit the event when the user clicks on the delete button
// Task.vue
<template>
<div class="task">
<h1>{{ title }}</h1>
<p>{{ description }}</p>
<button #click="passId">Delete</button>
</div>
</template>
<script>
import EventBus from 'path/to/eventBus'
export default {
props: ['title', 'description', 'id'],
methods: {
passId() {
EventBus.$emit('delete-task', this.id);
}
}
}
</script>
Listen to the event on the topmost parent
<template>
<div>
<TaskList :tasks="tasks" #id="deleteTask"/>
<Form :length="tasks.length" #addTask="addNewTask" />
</div>
</template>
<script>
import TaskList from './components/TaskList';
import Form from './components/Form';
import EventBus from 'path/to/eventBus.js'
export default {
name: 'App',
components: { TaskList, Form },
data() {
return {
tasks: [
{
id: 1,
title: 'Hello World',
description: 'this is the world'
},
{
id: 2,
title: 'Hello Mars',
description: 'this is the mars'
},
{
id: 3,
title: 'Hello Jupiter',
description: 'this is the jupiter'
}
]
}
},
mounted(){
// Listening to the delete-task event
EventBus.$on('delete-task', (id) => {
this.deleteTask(id)
})
},
methods: {
addNewTask(taskObject) {
const listOfTasks = this.tasks.concat(taskObject);
this.tasks = listOfTasks;
},
deleteTask(id) {
const filteredList = this.tasks.filter(element => {
return element.id != id;
})
this.tasks = filteredList;
}
}
}
</script>