Change html element type of component - javascript

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

Vue2 use $ref to load data from child component and put into parent component's data

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

Mutate props in Vue.js 3 - or how do I reflect a prop's changes to it's parents

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?

how to pass props correctly?

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>

VueJS mutating od duplicate v-model (:value, #input)

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.

how to bind v-model to child component that contains input

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.

Categories