Is there a way to create custom prop types (and extend it with validation) for Vue.js props?
In the below example you'll find the Object prop background. Instead of an Object, I would like to have a custom prop type Image.
Image would check if src and alt are filled, rest would be optional.
What we have now:
export default {
props: {
background: {
type: Object,
src: String,
srcset: String,
alt: String,
title: String,
},
},
};
What I'd like to have:
class customPropImage {
// magic ...
}
export default {
props: {
background: Image,
},
};
Sure you can do it. According to the Vue documentation you can set the type to the constructor of your custom type. With custom validation it could look something like this:
function CustomImage () {
// Magic
}
Vue.component('blog-post', {
props: {
myImage: {
type: CustomImage,
validator: function (value) {
return true; // or false based on value of myImage
}
}
}
})
Here is an example on codesandbox
Related
I am trying to build a component that creates filter buttons and then sends the type attribute in the filters object through the event bus to be handled elsewhere in my app. However, when I added the array of objects (filters) in the data section, I am getting an error of this.filter is not defined when I click on a button.
I would like to keep the filters array in this component because I am also trying to dynamically change the active class to whichever button has been clicked.
Am I missing something that has to do with props? Why am I unable to display the buttons when the data and v-for was on another component? These were the questions I have been asking myself in order of solving this issue.
<template>
<div>
<button
v-for="(filter, index) in filters"
:key="index"
:class="{ active: index === activeItem }"
#click="emitFilter(), selectItem(index)"
:filter="filter"
>
{{ filter.name }}
</button>
</div>
</template>
<script>
import EventBus from '#/components/EventBus'
export default {
props: {
filter: { type: String }
},
data() {
return {
activeItem: 0,
filterResult: '',
filters: [
{ name: 'All', type: 'all' },
{ name: 'Holidays', type: 'holiday' },
{ name: 'Seasons', type: 'season' },
{ name: 'Events', type: 'custom' }
]
}
},
methods: {
emitFilter() {
this.filterResult = this.filter
EventBus.$emit('filter-catagories', this.filterResult)
},
selectItem(index) {
this.activeItem = index
}
}
}
</script>
My button component is used in a filters component
<template>
<div>
<span>filters</span>
<FilterBtn />
</div>
</div>
</template>
<script>
import FilterBtn from '#/components/FilterBtn'
export default {
components: {
FilterBtn
}
// data() {
// return {
// filters: [
// { name: 'All', type: 'all' },
// { name: 'Holidays', type: 'holiday' },
// { name: 'Seasons', type: 'season' },
// { name: 'Events', type: 'custom' }
// ]
// }
// }
}
</script>
As you can see, the commented section is where I had my filters originally, but I had to move them to the button component in order to add the active class.
I'm assuming you were actually trying to access the filter iterator of v-for="(filter, index) in filters" from within emitFilter(). For this to work, you'd need to pass the filter itself in your #click binding:
<button v-for="(filter, index) in filters"
#click="emitFilter(filter)">
Then, your handler could use the argument directly:
export default {
methods: {
emitFilter(filter) {
this.filterResult = filter
//...
}
}
}
You are passing a prop called filter typed string to your component. When you output {{ filter.name }} you are actually referring to this property instead of the variable filter you create within the v-for loop.
Unless you passed a property called "filter" to your component, this property will be undefined. Therefore outputting filter.name will result in this error message.
Yea you dont pass an prop to your component thats why its undefined.
<FilterBtn filter="test" />
Here i pass an prop named filter with the value of test.
Sure you could pass dynamic props. Just bind it.
<FilterBtn :filter="yourData" />
I need to ask: Are you passing an object or an string?
Because you defined your prop to be a string, but you actually use it as an object
{{ filter.name }}
Maybe you should also set the type to Object.
props: {
filter: { type: Object }
},
Given the following component:
<script>
export default {
name: 'MyComponent',
props: {
blueprint: {
type: Object,
default () {
return {
attribute: 0,
otherAttribute: 5
}
}
}
},
data () {
return {
attribute: this.blueprint.attribute,
otherAttribute: this.blueprint.otherAttribute
}
}
}
</script>
I want to use the blueprint prop to populate data fields with some default values which could also be defined when using the component.
But how can I pass only one field of prop blueprint?
When I do this:
<my-component :blueprint="{attribute: someVar}" />
the otherAttribute of the default blueprint will be gone, of course.
Can I only set the one field of the prop and merge it with the default of the other, something like this:
<my-component :blueprint.attribute="someVar" />
<!-- It doesn't work like this, but maybe you get the idea what I want -->
Sadly the blueprint prop has too many fields to pass each field on it's own.
yeah, your Answer is fine. Here is my solution
<script>
export default {
name: 'MyComponent',
props: {
blueprint: {
type: Object
}
},
data () {
return {
blueprintDefault: {
attribute: 0,
otherAttribute: 5
}
}
},
mounted () {
this.blueprint = {...this.blueprintDefault, ...this.blueprint}
}
}
</script>
I found a solution that seems to work for me. My component now looks like this:
<script>
export default {
name: 'MyComponent',
props: {
blueprint: {
type: Object
}
},
data () {
return {
attribute: this.blueprint.attribute ?? 0,
otherAttribute: this.blueprint.otherAttribute ?? 5
}
}
}
</script>
I removed the default part of the prop and now set the default values directly in the data instead. That way if my blueprint prop does not include all attributes, the other default values will still be there.
I'm trying to build a form using "v-for" for input component and then generate a pdf file with PDFMake using data from inputs. But I didn't know how to pass the data from input component back to parent.
I read a lot of topics, but can't find a way to do this.
Here is short code without additional inputs, checkboxes etc. I plan to use around 15 inputs with different parameters to generate final PDF. Some of parameters also will be used to change final data depending of conditional statements.
Everything is work fine if code in one file, without loop and components. But not now.
Here is parent:
<template lang="pug">
.form
Input(v-for="data in form.client_info" v-bind:key="data.id" v-bind:data="data")
button(#click="pdfgen") Download PDF
</template>
<script>
export default {
components: {
Input: () => import('#/components/items/form/input')
},
data() {
return {
client_name: '',
client_email: '',
form: {
client_info: [
{id:'client_name', title:'Name'},
{id:'client_email', title: 'Email'},
{id:'foo', title: 'foo'}
],
}
}
},
methods: {
pdfgen: function () {
var pdfMake = require('pdfmake/build/pdfmake.js')
if (pdfMake.vfs == undefined) {
var pdfFonts = require('pdfmake/build/vfs_fonts.js')
pdfMake.vfs = pdfFonts.pdfMake.vfs;
}
if (this.foo) {
var foo = [
'Foo: ' + this.foo
];
} else {
foo = ''
]
}
var docDefinition = {
content: [
'Name: ' + this.client_name,
'Email: ' + this.client_email,
'\n',
foo
]
}
pdfMake.createPdf(docDefinition).download('Demo.pdf');
}
}
}
</script>
Here is a children (Input component):
<template lang="pug">
label.form_item
span.form_item_title {{ data.title }}
input.form_item_input(:v-model="data.id" type="text")
</template>
<script>
export default {
props: ['data']
}
</script>
Any ideas how to make it work?
You'll want to use a method that vue has build-in named $emit().
Before going into how to do that, a quick explanation. Because vue attempts to make data flow one-directional there is not a super quick way to just pass data back to a parent. What Vue proposes instead is to pass a method to the child component that, when called, will 'emit' the value it changed to it's parent and the parent can then do what it wants with that value.
So, in your parent component you'll want to add a method that will handle a change when the child emits. This could look something like:
onChildValueChanged(value){ this.someValue = value }
The value we passed to the function will be coming from our child component. We will need to define in our child component what this function should do. In your child component you could have a function that looks like so:
emitValueChange(event){ this.$emit('childFunctionCall', this.someChildValue) }
Next we need to tie those two functions together by adding an attribute on our child template. In this example that might look like:
<Child :parentData="someData" v-on:childFunctionCall="onChildValueChanged"></Child>
What that above template is doing is saying that when the function on:childFunctionCall is 'emited' then our function in the parent scope should fire.
Finally, in the child template we just need to add some event that calls out emiter. That could look like:
<button v-on:click="emitToParent">This is a button</button>
So when our button is clicked, the emiter is called. This triggers the function in our child component named 'emitToParent' which in turn calls the function we passed to our child component.
You'll have to tailor your use case to match the exam
I found a solution using Vuex.
So now my components look like this.
Here is parent:
<template lang="pug">
.form
Input(v-for="data in formClient" v-bind:key="data.id" v-bind:data="data")
button(#click="pdfgen") Download PDF
</template>
<script>
export default {
components: {
Input: () => import('#/components/items/form/input'),
store: () => import('#/store'),
},
computed: {
formClient() { return this.$store.getters.client }
}
}
</script>
Here is a children (Input component):
<template lang="pug">
label.form_item
span.form_item_title {{ data.title }}
input.form_item_input(v-model="data.value" :type="data.input_type")
</template>
<script>
export default {
props: ['data'],
computed: {
form: {
get () {
return this.$store.state.obj.message
},
set (value) {
this.$store.commit('updateMessage', value)
}
}
}
}
</script>
Here is a store module:
<script>
export default {
actions: {},
mutations: {},
state: {
form: {
client: [
{id:'client_name', title:'Name', value: ''},
{id:'client_email', title: 'Email', value: ''},
{id:'foo', title: 'foo', value: ''}
]
}
},
getters: {
client: state => {
return state.form.client;
}
}
}
</script>
Now I can read updated data from store directly from PDFMake function.
Is it possible to set the name of a Method in my Vue application based on the value of a prop? So my Vue Application looks like:
<script>
export default {
name: 'CheckboxFilter',
props: {
tax: '',
identifier: 'Category'
}
}
</script>
Then in my Methods I want to name a method based on the value of the identifier prop.
methods: {
updateSelected + this.identifier (e) {
this.$store.commit('updateSelectedCategories', e.target.value)
}
}
Is something like this possible to do where the name of the method is influenced by the value of a prop?
EDIT: the reason why I am trying to do this is I have a component that has checkbox input filters. The layout of this component will always be the same the only difference will be the values present on those checkboxes. I need to keep track of which checkboxes are select independently from each component. So my thought for doing this is to pass an identifier prop to use as a naming convention for the method where I store the value of the selected checkboxes for each component independently in my Vuex store.
Instead, pass it as an argument to your vuex store:
const store = new Vuex.Store({
state: {
attribute: {
tag: true,
bag: false
}
},
mutations: {
setAttr(state, { value, attribute }) {
state.attribute[attribute] = value;
}
}
});
Vue.component('custom-checkbox', {
props: ['attribute'],
template: '<div><input type="checkbox" v-model="checkBox">{{attribute}}</div>',
computed: {
checkBox: {
get() {
return this.$store.state.attribute[this.attribute]
},
set(value) {
this.$store.commit('updateMessage', {
value,
attribute: this.attribute
});
}
}
}
});
new Vue({
el: '#app',
store,
template: '<div><custom-checkbox attribute="tag" /><custom-checkbox attribute="bag" /></div>'
});
<html>
<body>
<div id="app"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vuex/3.1.0/vuex.min.js"></script>
</body>
</html>
That way, you're not trying to rely on dynamic data in your static code.
I've a dynamic view:
<div id="myview">
<div :is="currentComponent"></div>
</div>
with an associated Vue instance:
new Vue ({
data: function () {
return {
currentComponent: 'myComponent',
}
},
}).$mount('#myview');
This allows me to change my component dynamically.
In my case, I have three different components: myComponent, myComponent1, and myComponent2. And I switch between them like this:
Vue.component('myComponent', {
template: "<button #click=\"$parent.currentComponent = 'myComponent1'\"></button>"
}
Now, I'd like to pass props to myComponent1.
How can I pass these props when I change the component type to myComponent1?
To pass props dynamically, you can add the v-bind directive to your dynamic component and pass an object containing your prop names and values:
So your dynamic component would look like this:
<component :is="currentComponent" v-bind="currentProperties"></component>
And in your Vue instance, currentProperties can change based on the current component:
data: function () {
return {
currentComponent: 'myComponent',
}
},
computed: {
currentProperties: function() {
if (this.currentComponent === 'myComponent') {
return { foo: 'bar' }
}
}
}
So now, when the currentComponent is myComponent, it will have a foo property equal to 'bar'. And when it isn't, no properties will be passed.
You can also do without computed property and inline the object.
<div v-bind="{ id: someProp, 'other-attr': otherProp }"></div>
Shown in the docs on V-Bind - https://v2.vuejs.org/v2/api/#v-bind
You could build it like...
comp: { component: 'ComponentName', props: { square: true, outlined: true, dense: true }, model: 'form.bar' }
<component :is="comp.component" v-bind="{...comp.props}" v-model="comp.model"/>
I have the same challenge, fixed by the following:
<component :is="currentComponent" v-bind="resetProps">
{{ title }}
</component>
and the script is
export default {
…
props:['title'],
data() {
return {
currentComponent: 'component-name',
}
},
computed: {
resetProps() {
return { ...this.$attrs };
},
}
<div
:color="'error'"
:onClick="handleOnclick"
:title="'Title'"
/>
I'm came from reactjs and I found this solve my issue
If you have imported you code through require
var patientDetailsEdit = require('../patient-profile/patient-profile-personal-details-edit')
and initalize the data instance as below
data: function () {
return {
currentView: patientDetailsEdit,
}
you can also reference the component through the name property if you r component has it assigned
currentProperties: function() {
if (this.currentView.name === 'Personal-Details-Edit') {
return { mode: 'create' }
}
}
When you use the <component> inside a v-for you can change the answer of thanksd as follow:
methods: {
getCurrentProperties(component) {
if (component === 'myComponent') {
return {foo: baz};
}
}
},
usage
<div v-for="object in object.items" :key="object._your_id">
<component :is="object.component" v-bind="getCurrentProperties(object.component)" />
</div>
Let me know if there is an easier way.