I am trying to create my first own components and wishes a simple solution how to create optional arguments, so I mean arguments which can be, but not needed to be used.
E.g. I can decide on usage wether I want to use
<my component :name="name" :description="description">
or only
<my component :name="name">
So in my view I just want to import the component "my component" regularly.
How should my component look like?
I've tried something like:
<template>
<input
:id="name"
:name="name"
type="text"
:description="description"
/>
</template>
<script>
export default {
name: "adsInputText",
props: {
name: String,
description: null
},
</script>
This is only a simplified abstraction of my problem. I have already played through several variants, but just couldn't get it right.
Do you know a simple approach to this?
Thanks a lot
Matthias
If you wish to add an optional prop then you should indicate a type and might want to add a default value for it:
props: {
name: String,
description: {
type: String,
default: null
}
},
That way if you don't indicate description prop when using a component you'll get null from this.description instead of undefined.
Related
I created a language selection dropdown in my Navbar component. So here is my navbar component:
<div>
<h6>{{ translate("welcomeMsg")}} </h6>
<select name="lang" v-model="lang">
<option value="en">English</option>
<option value="de">Deutsch</option>
</select>
</div>
<script>
export default {
mixins: [en, de],
data() {
return {
lang: "en",
};
},
methods: {
translate(prop) {
return this[this.lang][prop];
}
}
}
</script>
So the parent of this component is an Index.vue which is main component in my application.
<div id="app">
<Topnav/>
<Navbar/>
<router-view></router-view>
<Footer/>
</div>
Currently, I am able to change the language in my Navbar component. So according to the selected value in the dropdown in Navbar component, welcomeMsg is changing. What I am trying to do is I want to put this pieve of code to TopBar "{{ translate("welcomeMsg")}} ", and according to the value of the dropdown in Navbar component, I want to change this value.
Can you help me with this or can you give me an idea how to do it?
If I understand you correctly, you want to use translate method inside Topnav component.
This method is however defined in Navbar, so it's not accessible in Topnav.
To use it elsewhere you could create a mixin with this method to import it to any component. I don't recommend this solution though as mixins are making the code messy.
Another solution is to create a component with translate method defined inside. Let this component do just that: translate a message passed by prop and render it inside some div:
<div>
{{ translatedMessage }}
</div>
<script>
mixins: [en, de],
props: {
message: {
type: String,
default: ''
},
language: {
type: String,
default: 'en'
}
},
computed: {
translatedMessage() {
return this[this.language][this.message];
}
}
</script>
You can reuse this component anywhere in the application. You would still need to pass a language prop somehow, possibly the solution would be to use vuex store to do this, since language is probably global for entire application.
For easier and more robust solutions I would use vue-i18n, which #Abregre has already suggested in his comment: https://stackoverflow.com/a/70694821/9463070
If you want a quick solution for a full-scale application and you don't have a problem with dependencies, you could try to use vue-i18n.
It's a plugin for vue that does exactly this for multi-locale websites/apps in Vue.
https://www.npmjs.com/package/vue-i18n
EDIT
Then in order to use it globally in your app, you should use vuex.
Keep the language selection state there and then wherever you want to use it, you make a computed function with the state.language getter.
The translate function should be a global registered filter
https://v2.vuejs.org/v2/guide/filters.html
<b-checkbox v-model="selectedTiers" :native-value="i" type="checkType(i)" #input="update">
{{ $t('general.specificTier', { tier: i }) }}
</b-checkbox>
Hi everyone, I am using Buefy and Vue.js. I want the type to be flexible. That is why I am using the method here. according to different I, it outputs a different string. But type here doesn't recognize the method here. I also can't use string + string here.
Here is information about checkbox of buefy.
You can use v-bind directive to dynamically alter the attributes.
Here is an example to get your started:
<template>
<div id="app">
<!-- Example with string manipulation -->
<b-checkbox :type="`is-${type}`">TEST 1</b-checkbox>
<!-- Example by reading off compenent-data -->
<b-checkbox :type="checkboxType">TEST 2</b-checkbox>
</div>
</template>
<script>
export default {
name: "App",
components: {},
data() {
return {
type: 'success',
checkboxType: "is-success"
};
}
};
</script>
One last thing, you can still use a method there (just add : before the attribute name - like :type="checkType(i)"), but the function would only be evaluated once and any further data changes won't be reflected on the type attribute (won't update on data change)
I am testing out vue3 and wanted to make a transparent wrapper component (something I will use fairly often) with the new version. With the new syntax it should be as simple v-bind="attrs". This does not seem to be working for me however and there is not much documentation as of yet. Anyone know what the problem could be? See codepen for more details as to how I am implementing it as well.
https://codepen.io/sumcoding/pen/mdJqwgj
I don't know if codepen is a good place to reproduce this since I don't know how to make an import -> import { reactive } from "vue"; so your myValue would be reactive.
Anyway I have got it working using <input v-model="attrs.modelValue">.
Here is codepen
BTW
Since you have registered props, you don't need to return them in a setup method ;-)
The transparent wrapper component pattern for Vue.js 3 final can be implemented like this
<template>
<label>{{ label }}</label>
<input
type="text"
:value="modelValue"
v-bind="$attrs"
#input="$emit('update:modelValue', $event.target.value)"
/>
</template>
<script>
export default {
name: 'InputWrapper',
props: {
modelValue: {
type: String,
default: ''
},
label: {
type: String,
default: ''
}
},
emits: ['update:modelValue']
};
</script>
You can view an example as well as another variation of the pattern for Vue.js 3 here.
I'm using a third party component, and it expects a prop to be a particular type.
I wish to use a different type.
Is there a way to override the type of a child component prop?
Or would I have to use a computed property and modify the prop in the parent component to the type the child component requires?
In general i think the best way would be to do as you proposed: To "use a computed property and modify the prop in the parent component to the type the child component requires". If you have to do so in several places, you could outsource the logic into a mixin that will be imported whenever the given third-party component is used.
If modifying the type of the property in the parent component isn't an option, the best way to fulfill your needs would be creating your own component that extends from the third party component and overriding the needed property. (https://v2.vuejs.org/v2/api/#extends)
Keep in mind however that the dependency containing the third-party might get updated over time. You should probably stick to a fixed version if following this approach.
I created a simple example on how to extend and override components (You can check it out as CodeSandbox here: https://codesandbox.io/s/qq9y7nm8n4
App.vue:
<template>
<div id="app">
<h2>ExtendedComponent: </h2>
<extended-component :message="1" />
<h2>BaseComponent: </h2>
<base-component message="This shall be a string" />
</div>
</template>
<script>
import ExtendedComponent from "./components/ExtendedComponent";
import BaseComponent from "./components/BaseComponent";
export default {
name: "App",
components: {
ExtendedComponent,
BaseComponent
}
};
</script>
BaseComponent.vue:
<template>
<div>
<h3>prop type: {{ typeof message }}</h3>
</div>
</template>
<script>
export default {
name: "BaseComponent",
props: {
message: {
type: String,
required: false
}
}
};
</script>
ExtendedComponent.vue:
<script>
import BaseComponent from "./BaseComponent";
export default {
name: "ExtendedComponent",
extends: BaseComponent,
props: {
message: {
type: Number,
required: false
}
}
};
</script>
I have a little Loading component, whose default text I want to be 'Loading...'. Good candidate for slots, so I have something like this as my template:
<p class="loading"><i class="fa fa-spinner fa-spin"></i><slot>Loading...</slot></p>
That allows me to change the loading message with e.g. <loading>Searching...</loading>. The behaviour I would like, though, is not just to display the default message if no slot content is supplied, but also if the slot content is null or blank. At the moment if I do e.g.<loading>{{loadingMessage}}</loading> and loadingMessage is null, no text is displayed (where I want the default text to be displayed). So ideally I need to test this.$slots.default. This tells me whether content was passed in, but how do I find whether or not it was empty? this.$slots.default.text returns undefined.
You'd need a computed property which checks for this.$slots. With a default slot you'd check this.$slots.default, and with a named slot just replace default with the slot name.
computed: {
slotPassed() {
return !!this.$slots.default[0].text.length
}
}
And then use it in your template:
<template>
<div>
<slot v-if="slotPassed">Loading...</slot>
<p v-else>Searching...</p>
</div>
</template>
You can see a small example here. Notice how fallback content is displayed and not "default content", which is inside the slot.
Edit:
My wording could've been better. What you need to do is check for $slots.X value, but computed property is a way to check that. You could also just write the slot check in your template:
<template>
<div>
<slot v-if="!!$slots.default[0].text">Loading...</slot>
<p v-else>Searching...</p>
</div>
</template>
Edit 2: As pointed out by #GoogleMac in the comments, checking for a slot's text property fails for renderless components (e.g. <transition>, <keep-alive>, ...), so the check they suggested is:
!!this.$slots.default && !!this.$slots.default[0]
// or..
!!(this.$slots.default || [])[0]
#kano's answer works well, but there's a gotcha: this.$slots isn't reactive, so if it starts out being false, and then becomes true, any computed property won't update.
The solution is to not rely on a computed value but instead on created and beforeUpdated (as #MathewSonke points out):
export default {
name: "YourComponentWithDynamicSlot",
data() {
return {
showFooter: false,
showHeader: false,
};
},
created() {
this.setShowSlots();
},
beforeUpdate() {
this.setShowSlots();
},
methods: {
setShowSlots() {
this.showFooter = this.$slots.footer?.[0];
this.showHeader = this.$slots.header?.[0];
},
},
};
UPDATE: Vue 3 (Composition API)
For Vue 3, it seems that the way to check whether a slot has content has changed (using the new composition API):
import { computed, defineComponent } from "vue";
export default defineComponent({
setup(_, { slots }) {
const showHeader = computed(() => !!slots.header);
return {
showHeader,
};
},
});
note: I can't find any documentation on this, so take it with a pinch of salt, but seems to work in my very limited testing.
this.$slots can be checked to see if a slot has been used.
It is important to note that this.$slots is not reactive. This could cause problems when using this.$slots in a computed value.
https://v2.vuejs.org/v2/api/?redirect=true#:~:text=Please%20note%20that%20slots%20are%20not%20reactive.
This means we need to ensure that this.slots is checked whenever the component re-renders. We can do this simply by using a method instead of a computed property.
https://v2.vuejs.org/v2/guide/computed.html?redirect=true#:~:text=In%20comparison%2C%20a%20method%20invocation%20will%20always%20run%20the%20function%20whenever%20a%20re%2Drender%20happens
<template>
<div>
<slot v-if="hasHeading" name="heading"/>
</div>
</template>
<script>
export default{
name: "some component",
methods: {
hasHeading(){ return !!this.slots.heading}
}
}
</script>