I have the following Vue JS component that is part of my grid system.
<bl-column type="section" classList="col--4-12 col--6-12--m col--1-1--s">
...
</bl-column>`
I want to set the type of the element to "" (standard), "" or "" dynamically by, as in the above example, adding a type variable that contains section or article.
This is my Column.Vue file:
<template>
<{type} :class="classList">
<slot></slot>
</{type}>
</template>
<script>
export default {
name: "Column",
props: ['classList', 'type'],
data() {
return {
classList: this.classList || '',
type: this.type || 'div',
};
}
};
</script>
This obviously doesn't work and throws an error, but you get the idea of setting the element type. Is there a way to perform this without using the render() function?
You have a easier way to render dynamic component. The doc https://v2.vuejs.org/v2/guide/components.html#Dynamic-Components
<template>
<component :is="type" :class="classList">
<slot></slot>
</component>
</template>
<script>
export default {
name: "Column",
props: ['classList', 'type'],
data() {
return {
classList: this.classList || '',
type: this.type || 'div',
};
}
};
</script>
Online example: https://jsfiddle.net/fotcpyc4/
<template>
<component :is="type">
<slot />
</component>
</template>
<script>
export default {
name: 'Heading',
props: {
type: {
type: String,
default: () => 'h1',
},
},
};
</script>
Related
In Vue2 I'm trying to access child components' data and then put into parent component's data without triggering an event. In the following example I want to save count:20 into parent component, please tell me if there's any mistake, thanks!
Child Component
<template>
<div></div>
</template>
<script>
export default {
data() {
return {
count: 20,
};
},
};
</script>
Parent Component
<template>
<div>
<child ref="child1"></child>
{{count}}
</div>
</template>
<script> import child from './child.vue'
export default {
components: {
child
},
data() {
return{
count:this.$refs.child1.count
}
},
}
</script>
warn message in VScode
Property 'count' does not exist on type 'Vue | Element | Vue[] | Element[]'.
Property 'count' does not exist on type 'Vue'.
warn message in browser
[Vue warn]: Error in data(): "TypeError: undefined is not an object (evaluating 'this.$refs.child1')"
Let me preface with I would recommend using the Vue framework as intended. So passing data from a child to the parent should be done with $emit or using a vuex store for centralized state management.
With that out of the way you will want to wait until the parent component is mounted to set the count data attribute.
Child
<template>
<div></div>
</template>
<script>
export default {
data() {
return {
count: 20,
};
},
};
</script>
Parent
<template>
<div>
<child ref="child1"></child>
{{ count }}
</div>
</template>
<script>
import Child from "./components/Child";
export default {
components: {
Child
},
data() {
return{
count: 0
}
},
mounted () {
this.count = this.$refs.child1.count
}
};
</script>
This will work, however it WILL NOT BE reactive. This can all be greatly simplified AND made reactive with the following changes:
Child
<template>
<div></div>
</template>
<script>
export default {
data() {
return {
count: 20,
};
},
watch: {
count (currentValue) {
this.$emit('update', currentValue);
}
},
beforeMount () {
this.$emit('update', this.count)
}
};
</script>
Parent
<template>
<div>
<child #update="count = $event"></child>
{{ count }}
</div>
</template>
<script>
import Child from "./components/Child";
export default {
components: {
Child
},
data() {
return{
count: 0
}
}
};
</script>
Quick link to show a working example: https://codesandbox.io/s/interesting-kalam-et0b3?file=/src/App.vue
I am migrating things from Vue.js 2 to Vue.js 3. During my migration, I just mentioned that eslint does warn me, because I am mutating props.
This is an example of an element that causes this:
<template>
<ToggleField
v-model="item.checked"
:name="`item.${name}.checked`"/>
</template>
<script>
import ToggleField from "./ToggleField";
export default {
name: 'TestField',
components: {ToggleField},
props: {
name: {
type: String,
required: true,
},
item: {
type: Object,
},
},
}
</script>
This element is deeply nested and every parent element passes the :item-attribute to the next "level" until it's finally displayed and changeable due v-model.
This is an example:
Parent view
<template>
<CustomForm name="test" :item="item" />
<!-- Reflect changes on item here -->
{{ item }}
</template>
<script>
import CustomForm from "./CustomForm";
export default {
components: {
CustomForm
},
data: () => ({
item:
{name: 'Foo', 'checked': false},
}),
}
</script>
CustomForm
<template>
<!-- Do other fancy stuff here -->
<TestField :name="name" :item="item"/>
</template>
<script>
import TestField from "./TestField";
export default {
name: 'CustomForm',
components: {TestField},
props: {
name: {
type: String,
required: true,
},
item: {
type: Object,
},
},
}
</script>
TestField
<template>
<ToggleField
v-model="item.checked"
:name="`item.${name}.checked`"/>
</template>
<script>
import ToggleField from "./ToggleField";
export default {
name: 'TestField',
components: {ToggleField},
props: {
name: {
type: String,
required: true,
},
item: {
type: Object,
},
},
}
</script>
So my question is: How can I update the item and reflect the changes to it's parent (and it's parent, and it's parent again, if necessary) without running into the prop-mutation?
I can't pass multiple elements. How can I do this?
export default {
props: {
elem: {
type: Object,
required: true,
},
whichScreen: whichScreen
},
You can add the whichScreen prop like below :
export default {
props : {
elem : {
type: Object,
required: true,
},
whichScreen : String
},
}
you can pass props to the component like below :
<my-component :elem="{ 'key' : 'value' }" :which-screen="'Screen 1'"></my-component>
Complete Working Sample :
Vue.component('my-component', {
template: '#tmpl-my-component',
props : {
elem : {
type: Object,
required: true,
},
whichScreen : String
},
});
new Vue({
el: '#app'
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<my-component :elem="{ 'key' : 'value' }" :which-screen="'Screen 1'"></my-component>
</div>
<template id="tmpl-my-component">
<div>
<div><h4>Prop `elem` :</h4> {{elem}}</div>
<div><h4>Prop `whichScreen` :</h4> {{whichScreen}}</div>
</div>
</template>
Here is how to use props in the child component.
Parent component:-
<template>
<div id="app">
<child data="Shreekanth is here"/> // Child component and Ddta attribute passing to child component
</div>
</template>
<script>
import Child from './components/Child.vue' // Child component imported
export default {
name: 'App',
components: {
Child
}
}
</script>
Child component:-
<template>
<div>
<div>{{data}}</div> // Used data attribute used in child template
</div>
</template>
<script>
export default {
name: 'Home',
props: {
data: String // How data attribute registed in child component
}
}
</script>
I have a problem with using the v-model in my own component. Namely, I dont want to use State or Bus.
At present, the component returns the single value correctly in App.js, it duplicates itself.
I can not deal with it, please help and explain me the problem.
Thanks a lot!
App.js:
<template>
<b-container>
<SectionSelector :AddSection="AddSection"/>
<component
v-for="(section, index) in sections"
:key="index"
:is="section.type"
:sectionIndex="index"
:sectionData="section[index]"
#sectionDataEmit="sectionDataEmit"/>
</b-container>
</template>
<script>
import SectionSelector from './components/SectionSelector.vue';
import FullText from './components/sections/FullText.vue';
import FullImage from './components/sections/FullImage.vue';
import ImageRightTextLeft from './components/sections/ImageRightTextLeft.vue';
import ImageLeftTextRight from './components/sections/ImageLeftTextRight.vue';
export default {
data() {
return {
sections: []
}
},
methods: {
AddSection(sectionData) {
this.sections.push(sectionData);
},
updateSection(sectionIndex, sectionData) {
this.sections[sectionIndex] = sectionData;
},
sectionDataEmit(emitData) {
// eslint-disable-next-line
console.log(emitData.position, emitData.content);
this.sections[emitData.position].fields.text = emitData.content;
}
},
components: {
SectionSelector,
FullText,
FullImage,
ImageRightTextLeft,
ImageLeftTextRight
}
}
</script>
SectionSelector.vue:
<template>
<b-row>
<b-dropdown id="dropdown-1" text="Select" class="m-md-2">
<b-dropdown-item v-for="(section, index) in sections"
:key="index"
#click="PushSection(index)"> {{ section.type }} </b-dropdown-item>
</b-dropdown>
</b-row>
</template>
<script>
export default {
props: ['AddSection'],
data() {
return {
sections: [
{
type: 'FullText',
fields: {
text: ''
}
},
{
type: 'FullImage',
fields: {
url:''
}
},
{
type: 'ImageRightTextLeft',
fields: {
img: '',
text: ''
}
},
{
type: 'ImageLeftTextRight',
fields: {
img: '',
text: ''
}
}
]
}
},
methods: {
PushSection(index) {
this.AddSection(this.sections[index])
}
}
}
</script>
FullText.vue:
<template>
<b-row>
<h3>Full text {{ sectionIndex+1 }}</h3>
<b-textarea
:value="sectionData"
#input="sectionDataEmit"></b-textarea>
</b-row>
</template>
<script>
export default {
props: ['sectionIndex', 'sectionData'],
methods: {
sectionDataEmit(value) {
let emitData = {
position: this.sectionIndex,
content: value
}
this.$emit('sectionDataEmit', emitData)
}
}
}
</script>
At present, the code causes duplication sections.fields.text (visible in the chrome extension Vue)
There is a place in your code that uses object[index]=. Do not do that with Vue data objects. Instead use Vue.set(object, index, value).
updateSection(sectionIndex, sectionData) {
Vue.set(sections,sectionIndex, sectionData);
},
This is based on the rule that Vue cannot react to new properties added to the object after data is initialized.
I have some components that look like this.
<template>
<q-layout>
<v-input v-model="something" />
</q-layout>
</template>
<script>
import { QLayout } from 'quasar'
import { Input } from 'vedana'
export default {
name: 'index',
components: {
QLayout,
Input
},
data () {
return {
something: ''
}
}
}
this v-input component looks like this:
<template>
<input
:type="type ? type : 'text'"
class="v-input"/>
</template>
<script>
export default {
props: ['type'],
name: 'v-input'
}
</script>
When I enter data into the input something does not bind to whatever is in the value of the input that is inside of v-input.
How do I achieve this?
To enable the use of v-model the inner component must take a value property.
Bind the value to the inner <input> using :value, not v-model (this would mutate the prop coming from the parent). And when the inner <input> is edited, emit an input event for the parent, to update its value (input event will update the variable the parent has on v-model).
Also, if you have a default value for the type prop, declare it in props, not in the template.
Here's how your code should be
<template>
<input
:type="type"
:value="value"
#input="$emit('input', $event.target.value)"
class="v-input" />
</template>
<script>
export default {
props: {
type: {default() { return 'text'; }},
value: {} // you can also add more restrictions here
},
name: 'v-input'
}
</script>
Info about what props can have: Components / Passing data With Props.
Demo below.
Vue.component('v-input', {
template: '#v-input-template',
props: {
type: {default() { return 'text'; }},
value: {} // you can also add more restrictions here
},
name: 'v-input'
});
new Vue({
el: '#app',
data: {
something: "I'm something"
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app">
<p>Parent something: {{ something }}</p>
<hr>
Child: <v-input v-model="something" />
</div>
<template id="v-input-template">
<input
:type="type"
:value="value"
#input="$emit('input', $event.target.value)"
class="v-input" />
</template>
https://v2.vuejs.org/v2/guide/components.html#sync-Modifier
<template>
<q-layout>
<v-input :value.sync="something" />
</q-layout>
</template>
<template>
<input
:type="type ? type : 'text'"
v-model="inputVal"
class="v-input"/>
</template>
<script>
export default {
props: ['type', 'value'],
name: 'v-input',
data:function(){
return {
inputVal: ''
};
},
watch:{
value: function(newValue){
this.$emit('update:value', newValue)
}
}
}
</script>
You need to pass your value to the input component using the .sync modifier so the changes will sync back to the parent.