I need to build list of dynamical components that I can group in group component. Then I need to send all information about builded components and groups.
I can use <component v-for="componentName in myComponents" :is="componentName"></component>, and get information about components using this.$children.map(component => component.getInformation()), but then I can't move some component to group component, because I have only component name not the component instance with data (it just render with default data).
I also can use this:
<template>
<div ref="container"> </div>
</template>
<script>
import someComponent from 'someComponent.vue'
import Vue from 'vue'
export default {
data () {
return {
myComponents: []
}
},
methods: {
addSomeComponent () {
let ComponentClass = Vue.extend(someComponent);
let instance = new ComponentClass({});
myComponents.push(instance);
instance.$mount();
this.$refs.container.appendChild(instance.$el)
},
getInformation () {
return this.myComponents.map(component => component.getInformation());
}
}
}
</script>
But then I can't use reactivity, directives (e.g. directives for drag and drop), and it's not data driven pattern.
Any suggestions?
<template>
<div class="component">
<template v-for="(child, index) in children()">
<component :is="child" :key="child.name"></component>
</template>
</div>
</template>
<script>
import someComponent from 'someComponent.vue'
import Vue from 'vue'
export default {
methods: {
children() {
let ComponentClass = Vue.extend(someComponent);
let instance = new ComponentClass({});
return [
instance
];
},
}
}
</script>
Related
I am trying to get $refs in Vue 3 using Composition API. This is my template that has two child components and I need to get reference to one child component instance:
<template>
<comp-foo />
<comp-bar ref="table"/>
</template>
In my code I use Template Refs: ref is a special attribute, that allows us to obtain a direct reference to a specific DOM element or child component instance after it's mounted.
If I use Options API then I don't have any problems:
mounted() {
console.log("Mounted - ok");
console.log(this.$refs.table.temp());
}
However, using Composition API I get error:
setup() {
const that: any = getCurrentInstance();
onMounted(() => {
console.log("Mounted - ok");
console.log(that.$refs.table.temp());//ERROR that.$refs is undefined
});
return {};
}
Could anyone say how to do it using Composition API?
You need to create the ref const inside the setup then return it so it can be used in the html.
<template>
<div ref="table"/>
</template>
import { ref, onMounted } from 'vue';
setup() {
const table = ref(null);
onMounted(() => {
console.log(table.value);
});
return { table };
}
On Laravel Inertia:
<script setup>
import { ref, onMounted } from "vue";
// a list for testing
let items = [
{ id: 1, name: "item name 1" },
{ id: 2, name: "item name 2" },
{ id: 3, name: "item name 3" },
];
// this also works with a list of elements
let elements = ref(null);
// testing
onMounted(() => {
let all = elements.value;
let item1 = all[0];
let item2 = all[1];
let item3 = all[2];
console.log([all, item1, item2, item3]);
});
</script>
<template>
<div>
<!-- elements -->
<div v-for="(item, i) in items" ref="elements" :key="item.id">
<!-- element's content -->
<div>ID: {{ item.id }}</div>
<div>Name: {{ item.name }}</div>
</div>
</div>
</template>
<template>
<your-table ref="table"/>
...
</template>
<script>
import { ref, onMounted } from 'vue';
setup() {
const table = ref(null);
onMounted(() => {
table.value.addEventListener('click', () => console.log("Event happened"))
});
return { table };
}
</script>
Inside your other component you can interact with events you already registered on onMounted life cycle hook as with my example i've registered only one evnet
If you want, you can use getCurrentInstance() in the parent component like this code:
<template>
<MyCompo ref="table"></MyCompo>
</template>
<script>
import MyCompo from "#/components/MyCompo.vue"
import { ref, onMounted, getCurrentInstance } from 'vue'
export default {
components : {
MyCompo
},
setup(props, ctx) {
onMounted(() => {
getCurrentInstance().ctx.$refs.table.tempMethod()
});
}
}
</script>
And this is the code of child component (here I called it MyCompo):
<template>
<h1>this is MyCompo component</h1>
</template>
<script>
export default {
setup(props, ctx) {
const tempMethod = () => {
console.log("temporary method");
}
return {
tempMethod
}
},
}
</script>
I am stuck at making a CheckBoxGroup with a prop array as v-model.
I have read the vuejs guide: https://v2.vuejs.org/v2/guide/forms.html#Checkbox which has the v-model array in the data of the same component, but it is obviously pretty useless if I want to make this component reusable and insert the v-model via props and for example check some of the boxes from "outside".
So I tried following:
CheckBoxgroup.vue
<template>
<div>
<label v-for="day in allDays" :key="day">
<input v-model="checkedDays" type="checkbox" :value="day" />
<span>{{ day }}</span>
</label>
<div>Checked days: {{ checkedDays }}</div>
</div>
</template>
<script lang="ts">
import Vue from 'vue'
import { Component, Prop } from 'vue-property-decorator'
#Component
export default class CheckBoxGroup extends Vue {
#Prop() checkedDays!: string[]
#Prop() allDays!: string[]
}
</script>
Index.vue
<template>
<div>
<checkbox-group :checked-days="checkedDays" :all-days="allDays" />
</div>
</template>
<script lang="ts">
import { Vue, Component } from 'vue-property-decorator'
import CheckboxGroup from './checkBoxGroup.vue'
#Component({
components: { CheckboxGroup },
})
export default class Index extends Vue {
// This list would usually come from an API
allDays = ['Monday', 'Tuesday', 'Wednesday']
checkedDays = ['Monday']
}
</script>
So the code is working almost fine, but I am getting
[Vue warn]: Avoid mutating a prop directly since the value will be
overwritten whenever the parent component re-renders...
Is there any way around it? Any help would be appriciated.
you can't mutate the parent state from the children directly, however you can emit the event from child to parent to mutate from there as below:
Vue.component('check-box-group', {
template: `
<div>
<label v-for="day in allDays" :key="day">
<input
v-model="checkedDays"
:value="day"
#click="$emit('update-checked-days', { newCheckedDay: day })"
type="checkbox"
/>
<span>{{ day }}</span>
</label>
<div>Checked days: {{ checkedDays }}</div>
</div>
`,
props: {
checkedDays: {
type: Array, default: () => ([])
},
allDays: {
type: Array, default: () => ([])
},
}
})
new Vue({
el: "#app",
data() {
return {
allDays: ['Monday', 'Tuesday', 'Wednesday'],
checkedDays: ['Monday']
}
},
methods: {
HandleUpdateCheckedDays({newCheckedDay}) {
const indexOfCheckedDay = this.checkedDays.findIndex(checkedDay => checkedDay === newCheckedDay)
if (indexOfCheckedDay === -1) { // if not exists then add to checkedDays
this.checkedDays.push(newCheckedDay)
} else {
this.checkedDays = this.checkedDays.filter((_, i) => i !== indexOfCheckedDay)
}
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.11/vue.min.js"></script>
<div id="app">
<check-box-group
:checked-days="checkedDays"
:all-days="allDays"
#update-checked-days="HandleUpdateCheckedDays"
/>
</div>
note: remember that TS class composition is deprecated.
Thanks for the answer, I actually managed to solve it also with v-model, but it looks kind of hacky and not very reusable for cases if data is injected from the outside Models.
So I will go with your solution.
I am currently using Algolia instantsearch for Vue, which is working fine. However, I am using multiple indexes, so I would like to be able to set a custom Title to each of my indexes.
This is my files:
Home.vue:
<ais-index :appId="angoliaAppId" :apiKey="angoliaAppKey" indexName="contacts" :query="query">
<search-results></search-results>
</ais-index>
<ais-index :appId="angoliaAppId" :apiKey="angoliaAppKey" indexName="users" :query="query">
<search-results></search-results>
</ais-index>
import SearchResults from "../search/Results";
export default {
data() {
return {
query: '',
angoliaAppId: process.env.MIX_ALGOLIA_APP_ID,
angoliaAppKey: process.env.MIX_ALGOLIA_SECRET,
};
},
components: {
SearchResults
}
};
And my component file, looks like this:
../search/Results:
<template>
<ais-results class="ais-Hits__root" v-if="searchStore.query.length > 0" :data="category">
<template slot-scope="{ result }">
<div>
<ais-highlight :result="result" attribute-name="name"></ais-highlight>
</div>
</template>
</ais-results>
</template>
import SearchResults from "../search/Results";
import {
Component
} from "vue-instantsearch";
export default {
mixins: [Component]
};
Now, as said, I would very much like to be able to set a custom title name to each of my <search-results> components, like this:
<search-results :title="My Contacts"></search-results>
Which would then for example add this in the component file:
<h1>{{ title }}</h1>
Seems like you'll have to use props
https://v2.vuejs.org/v2/guide/components.html#Passing-Data-to-Child-Components-with-Props
I have component 'Page' that should display a component which is retrieved via its props.
I managed to get my component loads when I harcode my component path in my component data like this :
<template>
<div>
<div v-if="includeHeader">
<header>
<fv-header/>
</header>
</div>
<component :is="this.componentDisplayed" />
<div v-if="includeFooter">
<footer>
<fv-complete-footer/>
</footer>
</div>
</div>
</template>
<script>
import Header from '#/components/Header/Header';
import CompleteFooter from '#/components/CompleteFooter/CompleteFooter';
export default {
name: 'Page',
props: {
componentPath: String,
includeHeader: Boolean,
includeFooter: Boolean
},
data() {
componentDisplayed: function () {
const path = '#/components/my_component';
return import(path);
},
},
components: {
'fv-header': Header,
'fv-complete-footer': CompleteFooter,
},
}
</script>
But with the data I cannot refer to my props within my function as this is undefined.
I tried to used computed properties instead of data but I have the error "src lazy?0309:5 Uncaught (in promise) Error: Cannot find module '#/components/my_component'. But the module exists! But maybe not at that time ?
computed: {
componentDisplayed: function () {
const path = `#/components/${this.componentPath}`;
return import(path);
},
},
There must be away to deal with that but I am quite a beginner to vue.js :)
Instead of trying to import the component in your child component, instead import it in the parent component and pass the entire component as a prop.
<template>
<div :is="component" />
</template>
<script>
export default {
name: "page",
props: {
component: {
required: true
}
}
};
</script>
And in the parent
<page :component="component" />
and
import Page from './components/Page';
// and further down
data () {
return {
component: HelloWorld
}
}
Using single file architecture I'm trying to pass data (an object) from a parent component to a child:
App.vue
<template>
<div id="app">
<app-header app-content={{app_content}}></app-header>
</div>
</template>
<script>
import appHeader from './components/appHeader'
import {content} from './content/content.js'
export default {
components: {
appHeader
},
data: () => {
return {
app_content: content
}
}
}
</script>
appHeader.vue
<template>
<header id="header">
<h1>{{ app_content }}</h1>
</header>
</template>
<script>
export default {
data: () => {
return {
// nothing
}
},
props: ['app_content'],
created: () => {
console.log(app_content) // undefined
}
}
</script>
Seems to be such a trivial task and probably the solution is quite simple. Thanks for any advice :)
You're almost there.
In order to send the app_content variable from App.vue to the child component you have to pass it as an attribute in the template like so:
<app-header :app-content="app_content"></app-header>
Now, in order to get the :app-component property inside appHeader.vue you will have to rename your prop from app_component to appComponent (this is Vue's convention of passing properties).
Finally, to print it inside child's template just change to: {{ appContent }}