I am trying to render components dynamically within my child component named ChildTabs. I would like to render the components based on the array that passed into the component from the parent view named Contatcs.
So for example if I pass the the form_type from the contact_type named "Big" it would render on my tabs vue. However I have other data that contains more than one component I am trying to render, such as medium which contains multiple forms such as Red Green & Blue.
I have an idea of creating a for loop in my method using the prop data form_type, so I can retrieve my list of forms I am trying to call, but that is where I do not know what to do next. I am already importing the forms, I am not sure how to render them.
Any suggestions are welcomed.
Contacts.vue
<template>
<div class="row">
<ChildTabs
:form_type="contact_types"
/>
</div>
</template>
<script>
'use strict';
import ChildTabs from '../tabs';
export default {
name: 'contacts-view',
data: function () {
return {
form_data: {},
failed_validations: [],
contact_type: '',
contact_types: [
{
label: 'Big',
value: 'big',
form_type: [
'Red'
]
},
{
label: 'Medium',
value: 'medium',
form_type: [
'Red',
'Green',
'Blue'
]
},
{
label: 'Small',
value: 'small',
form_type: [
'Blue',
'Green'
]
},
],
}
},
props: {
},
computed: {
},
methods: {
},
components: {
ChildTabs
}
}
</script>
Tabs.vue
<template>
<!--=============================================================-->
<!-- Contact Forms -->
<!--=============================================================-->
</template>
<script>
'use strict';
import Red from './forms/red';
import Green from './forms/green';
import Blue from './forms/blue';
export default {
name: 'forms-tab',
data: function () {
return {
}
},
props: {
form_type: {
type: Array,
required: false,
default: []
},
},
computed: {
},
methods: {
RenderComponent: function ()
{
this.$props.form_type
}
},
created: function () {
this.RenderComponent();
},
components: {
Red,
Blue,
Green
}
}
</script>
You can use dynamic component in Vue
<template>
<div>
<div v-for="type in form_type" :key="type">
<component :is="getCompentName(type)"/>
</div>
</div>
</template>
<script>
export default {
...
methods: {
getCompentName(type) {
switch (type) {
case 'red':
return 'red'
case 'blue':
return 'blue'
case 'green':
return 'green'
default:
return 'red'
}
}
}
}
</script>
Related
I am trying to develop guided tour with shepherd: https://www.npmjs.com/package/vue-shepherd but I cannot get the element. So here is my component for guide tour:
<template>
<div></div>
</template>
<script>
import { useShepherd } from 'vue-shepherd';
export default {
props: {
element: {
required: true,
},
id: {
type: Number,
required: true,
},
title: {
type: String,
},
text: {
type: String,
required: true,
},
position: {
type: String,
required: true,
},
},
mounted() {
this.tour.start();
},
data() {
return {
tour: null,
};
},
methods: {
createTour() {
this.tour = useShepherd({
useModalOverlay: true,
});
this.tour.addStep({
title: this.title,
text: this.text,
attachTo: { element: this.element, on: this.position },
buttons: [
{
action() {
return this.back();
},
classes: 'shepherd-button-secondary',
text: 'Back',
},
{
action() {
return this.next();
},
text: 'Next',
},
],
id: this.id,
});
this.tour.start();
},
},
created() {
this.createTour();
},
};
</script>
and here is my parent component:
<button ref="button">
Click
</button>
<guide :element="element" :title="'Tour'" :text="'Example'" :position="'bottom'" :id="1" />
and the mounted of the parent element:
mounted() {
this.element = this.$refs.button;
},
but the tour doesnt attach the the button element. it just appears in the middle of the page. Why do you think it is?
Looks like a usage problem in vue hooks. The child component's hooks fire before the parent component's hooks. Therefore, at the time of the creation of the tour, the element does not exist. Vue-shepherd does not use vue reactivity.
Use
mounted() {
this.$nextTick(() => {
this.createTour();
});
},
Codesanbox
But it's better to change the component structure. If you are using vue3 you can use my package
In this case it will look like this
<template>
<button v-tour-step:1="step1">
Click
</button>
</template>
<script>
import { defineComponent, inject, onMounted } from "vue";
export default defineComponent({
setup() {
const tour = inject("myTour");
onMounted(() => {
tour.start();
});
const step1 = {
/* your step options */
}
return {
step1,
};
}
});
</script>
I created a text editor component for my Vue3 project which is based on Quill (for documentation --> https://quilljs.com/docs/api/#setcontents):
<template>
<div class="form-control" v-bind:class="inputClasses" ref="editor"></div>
</template>
<script>
import Quill from 'quill';
import 'quill/dist/quill.core.css';
import 'quill/dist/quill.bubble.css';
import 'quill/dist/quill.snow.css';
import GENERAL_COMPONENT_CONSTANTS from "../constants/GeneralComponentConstants";
export default {
props: {
modelValue: { type: String, default: '' },
},
data() {
return {
editor: null
};
},
mounted() {
var _this = this;
this.editor = new Quill(this.$refs.editor, {
modules: {
toolbar: [
[{ header: [1, 2, 3, 4, false]} ],
["bold", "italic", "underline", "link", "image"],
],
},
//theme: 'bubble',
theme: "snow",
formats: ["bold", "underline", "header", "italic", "link"],
placeholder: this.placeholder,
});
this.editor.root.innerHTML = this.modelValue;
this.editor.setText('Default Value');
this.editor.on("text-change", function () {
return _this.update();
});
},
methods: {
update: function update() {
this.$emit(
"update:modelValue",
this.editor.getText() ? this.editor.root.innerHTML : ""
);
},
},
computed: {
}
}
</script>
It works fine and as a default value, I set the text this.editor.setText('Default Value'); But I am getting Uncaught DOMException: Failed to execute 'setStart' on 'Range': The offset 4294967294 is larger than the node's length error when I try to delete the whole default value.
I created a select2 wrapper in vue3 with options API everything working fine but the problem is that when getting values from calling API it's not selected the default value in the select2 option. but when I created a static array of objects it does. I don't know why it's working when it comes from the API
Parent Component
Here you can I passed the static options array in options props and my selected value is 2 and it's selected in my Select2 component, but when passed formattedCompanies it's not which is the same format as the static options array then why is not selected any reason here..?
<template>
<Form #submitted="store()" :processing="submitting">
<div class="row">
<div class="col-lg-6">
<div class="form-group">
<label>Company Name</label>
<Select2
:options="options"
v-model="selected"
placeholder="Select Company"
/>
<ValidationError :errors="errors" error-key="name" />
</div>
</div>
</div>
</Form>
</template>
<script>
import Form from "#/components/Common/Form";
import Select2 from "#/components/Common/Select2";
export default {
components: {
Select2,
Form
},
data() {
return {
selected : 2,
companies : [],
options: [ // static array
{ id: 1, text: 'hello' },
{ id: 2, text: 'hello2' },
{ id: 3, text: 'hello3' },
{ id: 4, text: 'hello4' },
{ id: 5, text: 'hello5' },
],
}
},
mounted() {
this.getAllMedicineCompanies()
},
computed:{
formattedCompanies() {
let arr = [];
this.companies.forEach(item => {
arr.push({id: item.id, text: item.name})
});
return arr;
}
},
methods: {
getAllMedicineCompanies(){
axios.get('/api/get-data?provider=companies')
.then(({ data }) => {
this.companies = data
})
},
}
}
</script>
Select2 Component
Here is what my select2 component look like, did I do anything wrong here, please anybody help me
<template>
<select class="form-control">
<slot/>
</select>
</template>
<script>
export default {
name: "Select2",
props: {
options: {
type: [Array, Object],
required: true
},
modelValue: [String, Number],
placeholder: {
type: String,
default: "Search"
},
allowClear: {
type: Boolean,
default: true
},
},
mounted() {
const vm = this;
$(this.$el)
.select2({ // init select2
data: this.options,
placeholder: this.placeholder,
allowClear: this.allowClear
})
.val(this.modelValue)
.trigger("change")
.on("change", function () { // emit event on change.
vm.$emit("update:modelValue", this.value);
});
},
watch: {
modelValue(value) { // update value
$(this.$el)
.val(value)
.trigger("change");
},
options(options) { // update options
$(this.$el)
.empty()
.select2({data: options});
},
},
destroyed() {
$(this.$el)
.off()
.select2("destroy");
}
}
</script>
Probably when this Select2 mounted there is no companies. It is empty array after that it will make API call and it it populates options field and clear all options.
Make:
companies : null,
Change it to
<Select2
v-if="formattedCompanies"
:options="formattedCompanies"
v-model="selected"
placeholder="Select Company"
/>
It should be like this:
<template>
<Form #submitted="store()" :processing="submitting">
<div class="row">
<div class="col-lg-6">
<div class="form-group">
<label>Company Name</label>
<Select2
v-if="formattedCompanies"
:options="formattedCompanies"
v-model="selected"
placeholder="Select Company"
/>
<ValidationError :errors="errors" error-key="name" />
</div>
</div>
</div>
</Form>
</template>
<script>
import Form from "#/components/Common/Form";
import Select2 from "#/components/Common/Select2";
export default {
components: {
Select2,
Form
},
data() {
return {
selected : 2,
companies : null,
options: [ // static array
{ id: 1, text: 'hello' },
{ id: 2, text: 'hello2' },
{ id: 3, text: 'hello3' },
{ id: 4, text: 'hello4' },
{ id: 5, text: 'hello5' },
],
}
},
mounted() {
this.getAllMedicineCompanies()
},
computed:{
formattedCompanies() {
let arr = [];
this.companies.forEach(item => {
arr.push({id: item.id, text: item.name})
});
return arr;
}
},
methods: {
getAllMedicineCompanies(){
axios.get('/api/get-data?provider=companies')
.then(({ data }) => {
this.companies = data
})
},
}
}
</script>
The problem was that my parent component and Select2 component mounted at the same time that's why my computed value is not initialized so the selected value is not selected in the option,
problem solved by setTimeOut function in mounted like this
Select2 Component
<script>
mounted() {
const vm = this;
setTimeout(() => {
$(this.$el)
.select2({ // init select2
data: this.options,
placeholder: this.placeholder,
allowClear: this.allowClear
})
.val(this.modelValue)
.trigger("change")
.on("change", function () { // emit event on change.
vm.$emit("update:modelValue", this.value);
});
}, 500)
},
</script>
I have 'd' array in data and I want to watch if its elements gets changed, but some of the elements are nested.
My example is basically if I change the value Apple to Axe watcher should execute the methods inside of it, How do I achieve it?
<template>
<div>
<button #click="change">Click me</button>
{{ d }}
</div>
</template>
<script>
export default {
watch: {
d: function() {
this.hello();
},
},
data() {
return {
d: [{ a: 'apple' }, { b: 'bananana' }, 'c'],
};
},
methods: {
hello() {
alert('Executed');
},
change() {
this.d[0].a = 'axe';
},
},
};
</script>
<style></style>
Use deep: true on your watcher
watch: {
d: {
handler(val) {
this.hello();
},
deep: true
}
},
My svg shape elements and they attributes was query from server side then
how to render that dynamically in vue to use v-for without static html tag?
Sounds like a job for a Render Function. For example:
Create SvgElement.vue:
render: function (createElement) {
return createElement(
this.shapeType,
{
attrs: this.attrObj
},
this.$slots.default
)
},
props: {
shapeType: {
type: String,
required: true
},
attrString: {
type: string
}
},
computed: {
attrObj() {
// Convert this.attrString into an object
// eg return { cx: 50, cy: 50, r: 10, fill: 'red' }
}
}
Then use in MyComponent.vue
<template>
<svg width="400" height="400">
<SvgElement
v-for="svg in svgArray"
:key="svg.key"
:shapeType="svg.type"
:attrString="svg.attr"
/>
</svg>
</template>
<script>
import SvgElement from './SvgElement'
export default {
components: {
SvgElement
},
data () {
return {
svgArray: [
{ type: 'rect', attr: 'attrString', key: 'shape1' },
{ type: 'circle', attr: 'attrString', key: 'shape2' }
]
}
}
}
</script>