I am trying to load v-select (vuetify) component dynamically using a JSON object which contains different properties
I have multiple select components where options will be provided by an API, I want to load v-select component dynamically each time as this schema can be used for other components as well such as v-text-field
Problem is the genders array (other similar arrays as well such as departments and managers) will be provided by an API hence it needs to be dynamically loaded on to v-select component but using vm (View Modal) I can't do this.
I have tried using computed properties and methods to retrieve genders array, but no luck.
Vue
<component :is="schema.component" v-model="jsonData[schema.value]" v-bind="schema.props" v-on="schema.on">
Script
<script>
import {
VSelect
} from 'vuetify/lib';
export default {
components: {
VSelect
},
data() {
const vm = this;
return {
genders:['Male', 'Female', 'Other'],
jsonData:{},
schema: {
component: 'v-select',
value: 'GENDER',
props: {
items: vm.genders
},
on: {
}
}
}
}
}
</script>
The problem is that in vm is not what you expect. Use computed properties when you want to set value depending on another variable of the component.
data() {
return {
genders:['Male', 'Female', 'Other'],
jsonData:{}
}
},
computed: {
schema () {
return {
component: 'v-select',
value: 'GENDER',
props: { items: this.genders },
on: {}
}
}
}
Related
I'm sending from the parent component a prop: user. Now in the child component I want to make a copy of it without it changing the prop's value.
I tried doing it like this:
export default defineComponent({
props: {
apiUser: {
required: true,
type: Object
}
},
setup(props) {
const user = ref(props.apiUser);
return { user };
}
});
But then if I change a value of the user object it also changes the apiUser prop. I thought maybe using Object.assign would work but then the ref isn't reactive anymore.
In Vue 2.0 I would do it like this:
export default {
props: {
apiUser: {
required: true,
type: Object
}
},
data() {
return {
user: {}
}
},
mounted() {
this.user = this.apiUser;
// Now I can use this.user without changing this.apiUser's value.
}
};
Credits to #butttons for the comment that lead to the answer.
const user = reactive({ ...props.apiUser });
props: {
apiUser: {
required: true,
type: Object
}
},
setup(props) {
const userCopy = toRef(props, 'apiUser')
}
With the composition API we have the toRef API that allows you to create a copy from any source reactive object. Since the props object is a reactive, you use toRef() and it won't mutate your prop.
This is what you looking for: https://vuejs.org/guide/components/props.html#one-way-data-flow
Create data where you add the prop to
export default {
props: ['apiUser'],
data() {
return {
// user only uses this.apiUser as the initial value;
// it is disconnected from future prop updates.
user: this.apiUser
}
}
}
Or if you use api composition:
import {ref} from "vue";
const props = defineProps(['apiUser']);
const user = ref(props.apiUser);
You also may want to consider using computed methods (see also linked doc section from above) or v-model.
Please note that the marked solution https://stackoverflow.com/a/67820271/2311074 is not working. If you try to update user you will see a readonly error on the console. If you don't need to modify user, you may just use the prop in the first place.
As discussed in comment section, a Vue 2 method that I'm personally fond of in these cases is the following, it will basically make a roundtrip when updating a model.
Parent (apiUser) ->
Child (clone apiUser to user, make changes, emit) ->
Parent (Set changes reactively) ->
Child (Automatically receives changes, and creates new clone)
Parent
<template>
<div class="parent-root"
<child :apiUser="apiUser" #setUserData="setUserData" />
</div>
</template>
// ----------------------------------------------------
// (Obviously imports of child component etc.)
export default {
data() {
apiUser: {
id: 'e134',
age: 27
}
},
methods: {
setUserData(payload) {
this.$set(this.apiUser, 'age', payload);
}
}
}
Child
<template>
<div class="child-root"
{{ apiUser }}
</div>
</template>
// ----------------------------------------------------
// (Obviously imports of components etc.)
export default {
props: {
apiUser: {
required: true,
type: Object
}
},
data() {
user: null
},
watch: {
apiUser: {
deep: true,
handler() {
// Whatever clone method you want to use
this.user = cloneDeep(this.apiUser);
}
}
},
mounted() {
// Whatever clone method you want to use
this.user = cloneDeep(this.apiUser);
},
methods: {
// Whatever function catching the changes you want to do
setUserData(payload) {
this.$emit('setUserData', this.user);
}
}
}
Apologies for any miss types
I have App.vue component and its child ItemsList.vue.
In App.vue I have this:
export default {
name: 'App',
components: {
ItemsList
},
data() {
return {
items_list: [
getRandomItem(),
getRandomItem(),
getRandomItem(),
]
}
}
}
The data is an array of randomly generated items of the same structure.
I want to return a new array of newly generated items when clicking a button but as I see, data() method is being called only once on start and then return just what was generated at the moment.
How do I rerun this method to return a new dataset?
The data() method is called only once when the instance of the component is created.
It's a bit like a lifecycle hook.
You can use a function to generate a new array and replace the items_list of the component
data () {
return {
items_list: []
}
},
methods {
newList () {
this.items_list = [
getRandomItem(),
getRandomItem(),
getRandomItem(),
]
}
},
mounted () {
this.newList()
}
Documentation
Component's data function
Vuejs - Lifecycle Diagram
I am trying to send this.TC from typing.js to ending-page.js which are sibling components. Emits and event hubs not working. But emit from typing.js to parent works as I want. (There will be only one more call in this app, so i don't want use Vuex if it isnt necessary for this - i want to do it with simple emits ) Here's my code:
Parent:
<template>
<div id = "app">
<typing v-if = "DynamicComponent === 'typing'" />
<ending_page v-else-if = "DynamicComponent === 'ending_page'" />
</div>
</template>
<script>
/* Importing siblings components to parent component */
import typing from './components/typing/index.vue'
import ending_page from './components/ending-page/index.vue'
export default {
name: 'app',
components: {
typing,
ending_page
},
data() {
return {
DynamicComponent: "typing",
};
},
methods: {
updateDynamicComponent: function(evt, data){
this.DynamicComponent = evt;
},
},
};
</script>
typing.js:
import { eventBus } from "../../main";
export default {
name: 'app',
components: {
},
data() {
return {
/* Text what is in input. If you write this.input = "sometext" input text will change (It just works from JS to HTML and from HTML to JS) */
input: "",
/* Object of TypingCore.js */
TC: "somedata",
/* Timer obejct */
timer: null,
is_started: false,
style_preferences: null,
};
},
ICallThisFunctionWhenIWantToEmitSomething: function(evt) {
/* Sending data to ending_page component */
this.$root.$emit('eventname', 'somedata');
/* Calling parent to ChangeDynamicComponent && sending TC.data what will be given to ending_page (I think it looks better with one syntax here) */
this.$emit('myEvent', 'ending_page', this.TC.data);
}
},
};
ending-page.js:
import { eventBus } from "../../main";
export default {
name: 'ending-page',
components: {},
data () {
return {
data: "nothing",
}
},
computed: {
},
props: {
},
methods: {
},
/* I know arrow functions etc but i was trying everyting */
created: function () {
this.$root.$on('eventname', function (data) {
console.log(data)
this.title = data
this.$nextTick()
})
}
}
It is an example of how to share data between siblings components.
Children components emits events to parent. Parent components send data to children.
So, the parent has the property title shared between the children. When typing emits
the input event the directive v-modelcapture it an set the value on parent.
Ref:
https://v2.vuejs.org/v2/guide/components-props.html#One-Way-Data-Flow
https://v2.vuejs.org/v2/guide/components.html#Using-v-model-on-Components
https://benjaminlistwon.com/blog/data-flow-in-vue-and-vuex/
Vue.component('typing', {
props: {
value: ''
},
template: '<button #click="emit">Click to change</button>',
methods: {
emit() {
this.$emit('input', `changed on ${Date.now()}`);
}
}
});
Vue.component('ending-page', {
props: {
title: ''
},
template: '<div>{{ title }}</div>',
});
var app = new Vue({
el: '#app',
data() {
return {
title: 'unchanged',
};
},
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<typing v-model="title"></typing>
<ending-page :title="title"></ending-page>
</div>
One can try communication using vuex,
the data you want to share make it on this.$store.state or if recalling for functions use mutation(sync functions) and actions(async functions)
https://vuex.vuejs.org/
I like what Jeffrey Way suggested once, just create a global events object (which accidentally can be another Vue instance) and then use that as an event bus for any global communication.
window.eventBus = new Vue();
// in components that emit:
eventBus.$emit('event', data);
// in components that listen
eventBus.$on('event');
I'm just playing around with some component instance rendering within a Vue application and I was wondering, when pushing components to an array - how do we then access the data() from that given instance of the component?
So say I have something like this in App.vue (the "Grandfather" of all of my components). I have managed to push instances of both the CodeBlock and QuoteBlock components to the pageBlocks array (i.e., my front end app appends the component where I want it to be). Here is a snippet of my App.vue file:
components: {
CodeBlock,
QuoteBlock
},
data () {
return {
pageBlocks: []
}
},
methods: {
addPageBlock (componentNomen) {
this.pageBlocks.push({ componentName: componentNomen })
},
saveDraftPage () {
for (let pageBlock of this.pageBlocks) {
console.log(pageBlock.data)
}
}
}
And here is an example of my CodeBlock data (the Quote block is "modelled" almost like for like except for a few variable name changes to distinguish it inside the component):
export default {
name: 'CodeBlock',
props: [ 'type' ],
computed: {},
data () {
return {
debug: true,
codeBlock: null,
codeBlockRows: [{
'id': 1,
'text': '$ click to edit this code block'
}],
}
},
}
I've stripped out most of this component to keep things simple.
So, my question is, if the pageBlocks array in App.vue contains instances of the above exported component...how do I access the data within?
In my naiavity I thought it would be as simple as something like this:
for (let pageBlock of this.pageBlocks) {
console.log(pageBlock.data);
}
But, alas, no luck yet...any tips?
I'm using Vue v1.0.28 and vue-resource to call my API and get the resource data. So I have a parent component, called Role, which has a child InputOptions. It has a foreach that iterates over the roles.
The big picture of all this is a list of items that can be selected, so the API can return items that are selected beforehand because the user saved/selected them time ago. The point is I can't fill selectedOptions of InputOptions. How could I get that information from parent component? Is that the way to do it, right?
I pasted here a chunk of my code, to try to show better picture of my problem:
role.vue
<template>
<div class="option-blocks">
<input-options
:options="roles"
:selected-options="selected"
:label-key-name.once="'name'"
:on-update="onUpdate"
v-ref:input-options
></input-options>
</div>
</template>
<script type="text/babel">
import InputOptions from 'components/input-options/default'
import Titles from 'steps/titles'
export default {
title: Titles.role,
components: { InputOptions },
methods: {
onUpdate(newSelectedOptions, oldSelectedOptions) {
this.selected = newSelectedOptions
}
},
data() {
return {
roles: [],
selected: [],
}
},
ready() {
this.$http.get('/ajax/roles').then((response) => {
this.roles = response.body
this.selected = this.roles.filter(role => role.checked)
})
}
}
</script>
InputOptions
<template>
<ul class="option-blocks centered">
<li class="option-block" :class="{ active: isSelected(option) }" v-for="option in options" #click="toggleSelect(option)">
<label>{{ option[labelKeyName] }}</label>
</li>
</ul>
</template>
<script type="text/babel">
import Props from 'components/input-options/mixins/props'
export default {
mixins: [ Props ],
computed: {
isSingleSelection() {
return 1 === this.max
}
},
methods: {
toggleSelect(option) {
//...
},
isSelected(option) {
return this.selectedOptions.includes(option)
}
},
data() {
return {}
},
ready() {
// I can't figure out how to do it
// I guess it's here where I need to get that information,
// resolved in a promise of the parent component
this.$watch('selectedOptions', this.onUpdate)
}
}
</script>
Props
export default {
props: {
options: {
required: true
},
labelKeyName: {
required: true
},
max: {},
min: {},
onUpdate: {
required: true
},
noneOptionLabel: {},
selectedOptions: {
type: Array
default: () => []
}
}
}
EDIT
I'm now getting this warning in the console:
[Vue warn]: Data field "selectedOptions" is already defined as a prop. To provide default value for a prop, use the "default" prop option; if you want to pass prop values to an instantiation call, use the "propsData" option. (found in component: <default-input-options>)
Are you using Vue.js version 2.0.3? If so, there is no ready function as specified in http://vuejs.org/api. You can do it in created hook of the component as follows:
// InputOptions component
// ...
data: function() {
return {
selectedOptions: []
}
},
created: function() {
this.$watch('selectedOptions', this.onUpdate)
}
In your InputOptions component, you have the following code:
this.$watch('selectedOptions', this.onUpdate)
But I am unable to see a onUpdate function defined in methods. Instead, it is defined in the parent component role. Can you insert a console.log("selectedOptions updated") to check if it is getting called as per your expectation? I think Vue.js expects methods to be present in the same component.
Alternatively in the above case, I think you are allowed to do this.$parent.onUpdate inside this.$watch(...) - something I have not tried but might work for you.
EDIT: some more thoughts
You may have few more issues - you are trying to observe an array - selectedOptions which is a risky strategy. Arrays don't change - they are like containers for list of objects. But the individual objects inside will change. Therefore your $watch might not trigger for selectedOptions.
Based on my experience with Vue.js till now, I have observed that array changes are registered when you add or delete an item, but not when you change a single object - something you need to verify on your own.
To work around this behaviour, you may have separate component (input-one-option) for each of your input options, in which it is easier to observe changes.
Finally, I found the bug. I wasn't binding the prop as kebab-case