I disunderstood the documentation of passing parent component to child component.The parent component as the follow code:
<template>
<div className="fixed-table">
<basic-container>
<el-table
:data="dataSource"
border
style="width: 100%">
<el-table-column label="操作" align="center">
<template slot-scope="scope">
<el-button type="primary" size="mini" round #click="handleEdit(scope.$index, scope.row)">编辑
</el-button>
<edit-dialog ref="editDialog" :row="scope.row" :index="scope.$index" :allBaseRules="allBaseRules" :allActions="allActions"></edit-dialog>
</template>
</el-table-column>
</el-table>
</basic-container>
</div>
</template>
<script>
import BasicContainer from '#vue-materials/basic-container';
import EditDialog from "./components/EditDialog";
import HttpUtils from '../../../../components/HttpUtils.js'
export default {
components: {
BasicContainer,
EditDialog
},
name: 'FixedTable',
data() {
return {
dataSource: [],
allActions: [],
};
},
methods: {
handleEdit(index, row) {
this.$refs.editDialog.$emit("open",row);
}
},
mounted() {
let self = this;
HttpUtils.get('/api/rule/action/queryAll')
.then(function (response) {
self.allActions = response.data.data;
})
.catch(function (error) {
});
}
}
</script>
I want to parse "allActions" to child components.The child components is a dialog,I open it by control the 'dialogFormVisible'. The child components like this:
<template>
<el-dialog title="编辑业务规则" :visible.sync="dialogFormVisible" :append-to-body="true">
<el-form :model="formRow" :label-position="labelPosition" label-width="20%">
<el-form-item label="前置行为" prop="preHandlers">
<el-select v-model="preHandler" clearable placeholder="请选择">
<el-option v-for="item in allActions" :key="item.actionID" :label="item.actionName"
:value="item.actionID"></el-option>
</el-select>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button #click="dialogFormVisible = false">取 消</el-button>
<el-button type="primary" #click="handleSubmit('ruleForm')">确 定</el-button>
</div>
</el-dialog>
</template>
<script>
export default {
data() {
return {
dialogFormVisible: false
preHandler: ""
};
},
computed: {
formRow() {
return Object.assign({}, this.row);
}
},
props: {
row: {
type: Object,
default: {}
},
index: {
type: Number,
default: 0
}
allActions: {
type: Array,
default: function () {
return []
}
}
},
created: function () {
this.$on('open', function (row) {
this.dialogFormVisible = true
});
}
};
</script>
However,I can't get 'allActions' from parent component. How can i get it?
When defining the attributes on an HTML element keep in mind that HTML is case insensitive. So a prop defined as allBaseRules really is seen as allbaserules. So you need to use "Kabab-case" when defining the attribute name as below.
<edit-dialog
ref="editDialog"
:row="scope.row"
:index="scope.$index"
:all-base-rules="allBaseRules"
:all-actions="allActions"
>
Vue automatically recognizes the kabab-case props and converts them to camelCase for you. So you are fine to still receive the props as you are currently.
https://v2.vuejs.org/v2/guide/components-props.html#Prop-Casing-camelCase-vs-kebab-case
See the the example here https://jsfiddle.net/skribe/1dbfj8h6/
Related
I would like to pass data from my parent to child component and bind data to a input field through v-model to display data from my api call in parent component. But it seems to be problem when binding to input field i get this error message:
Unexpected mutation of "data" prop.eslintvue/no-mutating-props
Partent Component
<script lang="ts">
import { defineComponent,ref } from 'vue';
import axios from 'axios'
import ChildComponent from '../components/ChildComponent.vue';
export default defineComponent({
Component: { ChildComponent },
name: 'IndexPage',
setup() {
return {
fixed: ref(false),
data: []
};
},
mounted() {
this.getData();
},
methods: {
getData() {
axios.get('/api/Hotel/' + 2).then((response) => {
this.data = response.data;
this.fixed = true,
console.log(this.data);
});
}
},
components: { ChildComponent }
});
</script>
Child Component
<template>
<main>
<q-card class="height: 500px; width: 500px">
<q-card-section>
<div class="text-h6">Terms of Agreement</div>
<div class="q-pa-md">
<div class="q-gutter-md" style="max-width: 300px">
<div>
<q-input filled v-model="data.message" label="Filled" />
</div>
</div>
</div>
</q-card-section>
<q-separator />
<q-separator />
<q-card-actions align="right">
<q-btn flat label="Decline" color="primary" v-close-popup />
<q-btn flat label="Accept" color="primary" v-close-popup />
</q-card-actions>
</q-card>
</main>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue';
export default defineComponent({
props:['data'],
name: 'ChildComponent',
setup() {
return {
text: ref(''),
};
},
});
</script>
I have tried this to make mounted methods in my child components like this:
<div>
<q-input filled v-model="dataIn.message" label="Filled" />
</div>
export default defineComponent({
props:['data'],
name: 'ChildComponent',
setup() {
return {
text: ref(''),
dataIn:{}
};
},
mounted(){
this.dataIn = this.data
},
});
It seems to work but not optimal, i lost my data when i refresh my page. Anyone have a soulution ?
Props should be read readonly.
Your dataIn approach needs a watcher that will update your dataIn whenever your data props change
optionsApi:
export default defineComponent({
props:['data'],
name: 'ChildComponent',
data() {
text: '',
data: this.dataIn,
}
watcher: {
dataIn: (newValue,oldValue){
this.data = newValue
}
}
});
It seems that you want to make change to your data on your child component, you have to make it two-way binding. You should change your child code like this ( you are using custom q-input in your component and the attributes may differ a little but it is the same concept) :
<q-input
:value="value"
v-bind="$attrs"
v-on="$listeners"
#input="(v) => $emit('input', v)"
/>
and instead of using data prop you should change it to value :
props: {
value: {
type: [String], // multiple type also defenition accepted
default: "",
},
}
then in your parent simply use child component like this :
<your-child-component v-model="someData" />
If I understood you correctly, you need to emit event from child or to use computed property with getter/setter:
const { ref, onMounted, watch } = Vue
const app = Vue.createApp({
setup() {
const items = ref({})
const getData = () => {
items.value = ({id: 1, message: 'aaa'})
/*axios.get('/api/Hotel/' + 2).then((response) => {
this.data = response.data;
this.fixed = true,
console.log(this.data);
});*/
}
onMounted(async() => await getData())
// 👇 react to chenges from child
const changed = (val) => {
items.value.message = val.message
}
return {
//fixed: ref(false),
items, changed
};
},
})
app.component('ChildComponent', {
template: `
<main>
<q-card class="height: 500px; width: 500px">
<q-card-section>
<div class="text-h6">Terms of Agreement</div>
<div class="q-pa-md">
<div class="q-gutter-md" style="max-width: 300px">
<div>
<!-- 👇 listen to updating -->
<q-input filled v-model="text.message" #update:model-value="change" label="Filled" />
</div>
</div>
</div>
</q-card-section>
<q-separator />
<q-separator />
<q-card-actions align="right">
<q-btn flat label="Decline" color="primary" v-close-popup />
<q-btn flat label="Accept" color="primary" v-close-popup />
</q-card-actions>
</q-card>
</main>
`,
props:['items'],
setup(props, {emit}) {
const text = ref(props.items)
// 👇 emit event with changed value
const change = () => { emit('changed', text.value) }
// 👇 watch for the props changes
watch(
() => props.items,
(newValue, oldValue) => {
text.value = newValue;
}
);
return {
text, change
};
},
})
app.use(Quasar)
app.mount('#q-app')
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons" rel="stylesheet" type="text/css">
<link href="https://cdn.jsdelivr.net/npm/quasar#2.5.5/dist/quasar.prod.css" rel="stylesheet" type="text/css">
<div id="q-app">
{{items}}
<!-- 👇 listen to child event -->
<child-component :items="items" #changed="changed"></child-component>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue#3/dist/vue.global.prod.js"></script>
<script src="https://cdn.jsdelivr.net/npm/quasar#2.5.5/dist/quasar.umd.prod.js"></script>
I have two child components I have to pass dynamically props from first child to parent and from parent to second child.
Parent
<script>
data: () => ({
model: {}
}),
methods: {
changeData(payload) {
this.model.personalData = {...payload}
}
}
</script>
<template>
<first-child #changeData="(payload) => changeData(payload)"/>
<second-child :enter-object="model" />
</template>
Child one
<script>
data: () => ({
model: {}
}),
methods: {
changeData() {
this.$emit("changeData", this.model);
}
}
</script>
<template>
<v-text-field v-model="model.name" #input="changeData()">
<v-text-field v-model="model.email" #input="changeData()">
</template>
Child two
<script>
props: {
enterObject: {
type: Object,
required: false,
default: () => ({})
}
},
data: () => ({
model: {}
}),
watch: {
enterObject: {
immediate: true,
handler() {
Object.assign(this.model.personalData, this.enterObject.personalData);
}
}
</script>
<template>
<div>
<div v-if="model.personalData.name || model.personalData.email">
<span class="mr-3">{{ model.personalData.name }}</span>
<span>{{ model.personalData.email }}</span>
</div>
<div v-else>
No data
</div>
</div>
</template>
I get data in parent component with no problem, but this data doesn't pass to second child, why I have always "No data" ?
I tested your code and found a few things:
You need to create "personalData" inside the model in "childTwo".
<template>
<div>
// I changed the validation for personalData
<div v-if="model.personalData">
<span class="mr-3">{{ model.personalData.name }}</span>
<span>{{ model.personalData.email }}</span>
</div>
<div v-else>No data</div>
</div>
</template>
export default {
props: {
enterObject: {
type: Object,
required: false,
default: () => ({})
}
},
data: () => ({
model: {
personalData: {}
}
}),
watch: {
enterObject: {
deep: true,
handler() {
// Add a validation in the handler, you can use Object assign inside the validation.
if(this.enterObject) {
Object.assign(this.model.personalData, this.enterObject.personalData)
}
}
}
}
It's worked for me.I hope it helps you.
You have to assign the value of the object using this.$set for more about object reactivity click here
your Parent component should be like this:-
here is the working example
<template>
<div>
<first-child #change-data="(payload) => changeData(payload)" />
<second-child :enter-object="model" />
</div>
</template>
<script>
import FirstChild from "./FirstChild";
import SecondChild from "./SecondChild";
export default {
data: () => ({
model: {},
compKey: 0,
}),
components: {
FirstChild,
SecondChild,
},
methods: {
changeData(payload) {
this.$set(this.model, "test", payload);
//this.model.test = payload;
},
},
};
</script>
I have changed the value of dialogVisible to true, but the dialog box just doesn't display
I modified the dialogVisible value of the subcomponent to true through ref, and passed the ID of each piece of data through props. I think there is nothing wrong with what I did. Originally, I wanted to implement the modification function, but now I can’t even display the dialog box. Can someone help me?
parent component
<template>
<div>
<NavMenu></NavMenu>
<listQuery></listQuery>
<DialogAddAffairsType></DialogAddAffairsType>
<el-table :data="tableData" stripe fit class="el-table" :header-cell-style="{background:'#f5f7fa',color:'#606266'}">
<el-table-column prop="id" label="ID" width="180">
</el-table-column>
<el-table-column prop="typename" label="类型名称" width="180">
</el-table-column>
<el-table-column prop="createdAt" label="创建时间">
</el-table-column>
<el-table-column label="操作">
<template slot-scope="scope">
<el-button size="mini" #click="handleEdit(scope.row.id)">编辑(edit)</el-button>
<el-button size="mini" type="danger" #click="handleDelete(scope.row.id, scope.$index)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 前组件后数据 -->
<editAffairsType :editAffairsType="affairsTypeId" ref="editAffairsType"></editAffairsType>
<Pager></Pager>
</div>
</template>
<script>
import axios from "axios";
import ListQuery from "#/components/ListQuery/index.vue";
import DialogAddAffairsType from "#/components/DialogAddAffairsType/index.vue";
import Pager from "#/components/Pager/index.vue";
import NavMenu from "#/components/NavMenu/index.vue";
import editAffairsType from "#/components/DialogAffairsType/index.vue";
export default {
name: 'AffairsTypeList',
components: {
ListQuery,
DialogAddAffairsType,
Pager,
NavMenu,
editAffairsType,
},
methods: {
getAllAffairsTypes() {
axios({
method: 'GET',
url: 'http://localhost:8080/api/affairsType/allAffairsTypes'
}).then(response => {
const data = response.data;
console.log("是否取到数据", data);
this.tableData = data;
})
},
handleDelete(id, index) {
this.$confirm("永久删除该事务类型, 是否继续?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
})
.then(() => {
axios({
method: 'DELETE',
url: 'http://localhost:8080/api/affairsType/deleteById',
params: {
id: id
}
}).then(response => {
if (response.status == 200) {
this.tableData.splice(index, 1);
}
})
this.$message({
type: "success",
message: "删除成功!"
});
})
.catch(() => {
this.$message({
type: "info",
message: "已取消删除"
});
});
},
handleEdit(id) {
this.affairsTypeId = id;
this.$refs.editAffairsType.dialogVisible = true;
console.log("数据准备成功")
console.log(this.change[0])
return this.affairsTypeId;
}
},
// 在实例创建完成后被立即同步调用(https://cn.vuejs.org/v2/api/#created)
created() {
this.getAllAffairsTypes();
},
data() {
return {
tableData: [],
affairsTypeId: "",
}
}
}
</script>
<style>
.el-table {
margin: 0 auto;
}
</style>
child component
<template>
<div>
<el-dialog title="修改事务类型" :visible.sync="dialogVisible" width="35%">
<span>
<el-form :model="AffairsType" :label-position="labelPosition" label-width="auto">
<el-form-item label="类型名称" required>
<el-input v-model="AffairsType.typename" :placeholder="placeholder.typename" style="width:50%"></el-input>
</el-form-item>
</el-form>
</span>
<span slot="footer">
<el-button #click="dialogVisible = false">取 消</el-button>
<el-button type="primary" #click="dialogVisible = false">确 定</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
export default {
name: "editAffairsType",
// https://www.bilibili.com/video/BV1Zy4y1K7SH?p=66
props: {
affairsTypeId:{
// type:Number,
// required:true,
}
},
data() {
return {
dialogVisible: false,
}
},
methods: {
// change() {
// this.dialogVisible = this.changeAffairsType.dialogVisible;
// },
},
created(){
},
}
</script>
<style>
</style>
I'm trying to build a simple page builder which has a row elements, its child column elements and the last the component which I need to call. For this I designed and architecture where I'm having the dataset defined to the root component and pushing the data to its child elements via props. So let say I have a root component:
<template>
<div>
<button #click.prevent="addRowField"></button>
<row-element v-if="elements.length" v-for="(row, index) in elements" :key="'row_index_'+index" :attrs="row.attrs" :child_components="row.child_components" :row_index="index"></row-element>
</div>
</template>
<script>
import RowElement from "../../Components/Builder/RowElement";
export default {
name: "edit",
data(){
return{
elements: [],
}
},
created() {
this.listenToEvents();
},
components: {
RowElement,
},
methods:{
addRowField() {
const row_element = {
component: 'row',
attrs: {},
child_components: [
]
}
this.elements.push(row_element)
}
},
}
</script>
Here you can see I've a button where I'm trying to push the element and its elements are being passed to its child elements via props, so this RowElement component is having following code:
<template>
<div>
<column-element v-if="child_components.length" v-for="(column,index) in child_components" :key="'column_index_'+index" :attrs="column.attrs" :child_components="column.child_components" :row_index="row_index" :column_index="index"></column-element>
</div>
<button #click="addColumn"></button>
</template>
<script>
import ColumnElement from "./ColumnElement";
export default {
name: "RowElement",
components: {ColumnElement},
props: {
attrs: Object,
child_components: Array,
row_index: Number
},
methods:{
addColumn(type, index) {
this.selectColumn= false
let column_element = {
component: 'column',
child_components: []
};
let component = {}
//Some logic here then we are emitting event so that it goes to parent element and there it can push the columns
eventBus.$emit('add-columns', {column: column_element, index: index});
}
}
}
</script>
So now I have to listen for event on root page so I'm having:
eventBus.$on('add-columns', (data) => {
if(typeof this.elements[data.index] !== 'undefined')
this.elements[data.index].child_components.push(data.column)
});
Now again I need these data accessible to again ColumnComponent so in columnComponent file I have:
<template>
//some extra div to have extended features
<builder-element
v-if="!loading"
v-for="(item, index) in child_components"
:key="'element_index_'+index" :column_index="column_index"
:element_index="index" class="border bg-white"
:element="item" :row_index="row_index"
>
</builder-element>
</template>
<script>
export default {
name: "ColumnElement",
props: {
attrs: Object,
child_components: Array,
row_index: Number,
column_index: Number
},
}
</script>
And my final BuilderElement
<template>
<div v-if="typeof element.component !== 'undefined'" class="h-10 w-10 mt-1 mb-2 mr-3 cursor-pointer font-bold text-white rounded-lg">
<div>{{element.component}}</div>
<img class="h-10 w-10 mr-3" :src="getDetails(item.component, 'icon')">
</div>
<div v-if="typeof element.component !== 'undefined'" class="flex-col text-left">
<h5 class="text-blue-500 font-bold">{{getDetails(item.component, 'title')}}</h5>
<p class="text-xs text-gray-600 mt-1">{{getDetails(item.component, 'desc')}}</p>
</div>
</template>
<script>
export default {
name: "BuilderElement",
data(){
return{
components:[
{id: 1, title:'Row', icon:'/project-assets/images/row.png', desc:'Place content elements inside the row', component_name: 'row'},
//list of rest all the components available
]
}
},
props: {
element: Object,
row_index: Number,
column_index: Number,
element_index: Number,
},
methods:{
addElement(item,index){
//Some logic to find out details
let component_element = {
component: item.component_name,
attrs: {},
child_components: [
]
}
eventBus.$emit('add-component', {component: component_element, row_index: this.row_index, column_index: this.column_index, element_index: this.element_index});
},
getDetails(component, data) {
let index = _.findIndex(this.components, (a) => {
return a.component_name === component;
})
console.log('Component'+ component);
console.log('Index '+index);
if(index > -1) {
let component_details = this.components[index];
return component_details[data];
}
else
return null;
},
},
}
</script>
As you can see I'm again emitting the event named add-component which is again listened in the root component so for this is made following listener:
eventBus.$on('add-component', (data) => {
this.elements[data.row_index].child_components[data.column_index].child_components[data.element_index] = data.component
});
which shows the data set in my vue-devtools but it is not appearing in the builder element:
Images FYR:
This is my root component:
This is my RowComponent:
This is my ColumnComponent:
This is my builder element:
I don't know why this data not getting passed to its child component, I mean last component is not reactive to props, any better idea is really appreciated.
Thanks
The issue is with the way you're setting your data in the addComponent method.
Vue cannot pick up changes when you change an array by directly modifying it's index, something like,
arr[0] = 10
As defined in their change detection guide for array mutations,
Vue wraps an observed array’s mutation methods so they will also
trigger view updates. The wrapped methods are:
push()
pop()
shift()
unshift()
splice()
sort()
reverse()
So you can change.
this.elements[data.row_index].child_components[data.column_index].child_components[data.element_index] = data.component
To,
this.elements[data.row_index].child_components[data.column_index].child_components.splice(data.element_index, 1, data.component);
I am trying to pass data to the UserModal. But the issue I am facing here is that the value of
user_clicked field is set when the openuserdialog method runs(checked in console: the value is assigned) but I am not able to pass it as an argument to the modal. Please help me solve the problem.
<v-data-table :items="users" :disable-initial-sort="true" :mustSort="true" hide-actions>
<template slot="items" slot-scope="props">
<td>{{ props.item.file_type.name }}</td>
<td>{{ props.item.created_at | moment }}</td>
<td><a #click="openUserDialog(props.item.id, props.item.user_type)" href='javascript:void(0);' class="details-link"><span class="hidden-xs-only">UserTypes</span><span class="hidden-sm-and-up">User Types</span></a></td>
</template>
</v-data-table>
<v-dialog v-model="userDialog" max-width="1275">
<UserModal :document="user_clicked" />
<div class="text-xs-right">
<v-btn class='vue-file-button text-right' #click="closeUserDialog" >Close</v-btn>
</div>
</v-dialog>
<script>
import UserModal from 'views/users/shortlisted_users.vue';
export default {
components: {
UserModal
},
data: function() {
return {
userDialog: false,
user_clicked: ''
}
}
methods: {
openUserDialog(document_id, user_type) {
this.userDialog = true;
this.user_clicked = user_type;
console.log(this.user_clicked);
},
closeUserDialog(document_id) {
this.userDialog = false;
}
}
</script>
Update 1
openUserDialog(document_id, user_type) {
this.user_clicked = user_type;
this.userDialog = true;
console.log(this.user_clicked);
}
Update 2
<template>
<div>
<v-card id="users-card">
<Users :users="users"></Users>
</v-card>
</div>
</template>
<script>
import 'vue-awesome/icons';
import Icon from 'vue-awesome/components/Icon';
import Users from 'views/user/_user_table.vue';
export default {
components: {
Icon,
Users
},
props: ['document'],
data: () => ({
users: [],
tab_view: 'tab-users-card'
}),
created: function() {
console.log(this.document);
this.fetchUsers(this.document);
},
methods: {
fetchUsers(document) {
this.$axios.get('/my_account/users/document_suggested_users.json', {
params: {
document: document.id
}
})
.then(response => {
this.users = response.data;
})
},
}
};
</script>
The problem is that you are trying to use document in the created handler of the component which is far too early in its life-cycle.
Instead, one approach is to use a watch handler in your UserModal like this:
watch: {
document: function () {
console.log(this.document);
if (this.document) {
this.fetchUsers(this.document);
}
}
}
Try to declare your prop like so:
props: {
document: Object
}