Vue - passing a variable as a component prop - vue.js

I'm new to vue and I downloaded this Pomodoro timer component for my app (https://github.com/P3trur0/vuemodoro) which works well, except the time isn't adjustable inside the app itself.
Im trying to make an input field where the number of minutes will be entered and passed to the pomodoro timer, using the built in "minutes" property, but I don't understand how or if it's possible to pass variables to component properties in this way.
'''
<div>
<b-field class="timer">
<b-numberinput v-model="number"></b-numberinput>
</b-field>
<Pomodoro :minutes="1"/>
</div>
'''

Okay, so it seems that the Pomodoro component does not support reactive properties, so, while the timer will correctly be set to the initial value of number, it will not update if number changes. But - don't worry - there's an easy way around this: setting a key to the timer:
<Pomodoro :key="number" :minutes="number" />
A key tells Vue to update the component when the key has changed, so, in this case, whenever number changes, the Pomodoro element will be updated. More info on keys here.
Without a key:
With a key:
This is the full code:
<template>
<div id="app">
<b-field class="timer">
<b-numberinput v-model="number"></b-numberinput>
</b-field>
<Pomodoro :key="number" :minutes="number" />
</div>
</template>
<script>
import Pomodoro from "vuemodoro";
export default {
name: "App",
data() {
return {
number: 0,
};
},
components: {
Pomodoro,
},
// rest of the component
};
</script>
Also, you can try out this demo, and view/edit the code behind it

Related

How to change the language of whole application from the dropdown in Navbar component in vue

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

Step bar load with animation

Here is my progress bar from Vue bootstrap components, you can set default number in data with value: number, I want it to increase it automatically whenever I go to next page. Can anyone assist me with this? I tried with props but I have no idea how to do it.
b-progress(:value='value', :max='max', show-progress='', animated='')
b-button.next.mt-3.pb-1(v-if="nextLink" :to="{name: nextLink}")
RegistrationNav(prev-link="registration_goal" next-link="registration_interview")
For a basic example, without using vue lets imagine this way:
You have a parent component that includes the b-progress and some other component or inout for the user:
<template>
<div>
<b-progress :value="currentValue"><b-progress/>
<button #click="currentValue++">Click to increment </button>
</div>
</template>
export default {
name: "Login.vue",
data() {
return {
currentValue: 1
}
}
}

Adding props on runtime to Vue component

I try to create a highly dynamic wizard as a component in Vue. It contains out of three components: the Wizard component itself, a Step component and a single form "MyForm" component. The form can be in edit mode or in read only mode depending on the current step of the wizard.
After some trial and error I finally managed to create such a component and it works. One problem that I struggled to solve was to pass the information if the form is in edit mode or not from the Step component to the child form component.
MyForm.vue
<template>
<form>
<div v-if="inEditMode"><i>Form is in edit mode</i></div>
<div v-else><i>Form is in read only mode</i></div>
</form>
</template>
<script>
import Vue from "vue";
export default Vue.extend({
props: ["inEditMode"]
// mixins: [wizardStepMixin],
});
</script>
Wizard.vue
<Step>
<MyForm/>
</Step>
Step.vue
<slot :isInEditMode="true"/>
Passing/setting a prop to a slot like I did above did not work (prop did not change).
My solution to set the prop isInEdit on the MyForm is to call a function prepareSlot in the Step component before mounting and updating the Step.
prepareSlot() {
this.$slots.default.forEach(element => {
if (!element.data) return
element.componentOptions.propsData = {
...element.componentOptions.propsData,
inEditMode: this.stepNr === this.currentStep
}
})
}
You can find the complete project on https://codesandbox.io/embed/mzr10wzk0j.
Is there a better way to archive that? Is it safe to do it that way?

Determining if slot content is null or empty

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>

Vue Multiselect does not update {{ value }} via v-model

I am using this example for Vue Multiselect "^2.0.0-beta.14" in Laravel 5.3. https://github.com/monterail/vue-multiselect/tree/2.0#install--basic-usage
The plugin renders correctly but I cannot get the selection via v-model. I am expecting #{{ selected }} to update with the current selection.
app.js
Vue.component('dropdown', require('./components/Multiselect.vue'));
VUE JS
<template>
<div>
<multiselect
v-model="value"
:options="options">
</multiselect>
</div>
</template>
<script>
import Multiselect from 'vue-multiselect'
export default {
components: { Multiselect },
data () {
return {
value: null,
options: ['list', 'of', 'options']
}
}
}
</script>
<style src="vue-multiselect/dist/vue-multiselect.min.css"></style>
HTML
<div id="app">
<h3>Dropdown</h3>
<div>
<label class="typo__label">Single select</label>
<dropdown></dropdown>
<pre class="language-json"><code>#{{ value }}</code></pre>
</div>
</div>
NB
The official example uses selected instead of value but this does not work either. According to the docs selection is replaced by value as of V2.
If you are using TypeScript Interfaces with Vue.js 2.0, avoid using a optional properties to store the value from child components. i.e. if your property is
value:? IMyCustomInterface instead use value: MyCustomObject|null and set the object to null in the constructor.
If the property is optional, it will compile fine, but child components won't update it properly.
The reason value is not showing up in root is because the data is isolated to the dropdown component. To get your data from a component to show up in the Root you need to use props.
See this question for a detailed explanation
How to get data from a component in VueJS