How to create a button which changes languages with Vue-i18n - vue.js

I have a navbar which need to change between languages with two different buttons. I've used Vue-CLI and Vue-I18N for template syntax but I can't change languages between them. Solutions at documentation didn't helped me so much. My Header.vue , main.js and App.vue are below. I'm waiting for your answers. Thanks.
Header.vue
<template>
<div>
<b-navbar
toggleable="lg"
type="light"
variant="light"
>
<b-navbar-brand href="#">{{ $t('johnDoe') }}</b-navbar-brand>
<b-navbar-toggle target="nav-collapse"></b-navbar-toggle>
<b-collapse
id="nav-collapse"
is-nav
>
<b-navbar-nav>
<b-nav-item href="#">{{ $t('home') }}</b-nav-item>
<b-nav-item href="#">{{ $t('about') }}</b-nav-item>
<b-nav-item href="#">{{ $t('projects') }}</b-nav-item>
<b-nav-item href="#">{{ $t('education') }}</b-nav-item>
<b-nav-item href="#">{{ $t('contact') }}</b-nav-item>
</b-navbar-nav>
<!-- Right aligned nav items -->
<b-navbar-nav class="ml-auto">
<b-nav-form>
<b-button
size="sm"
class="my-2 my-sm-0 btn-info"
type="submit"
#click="i18n.locale = 'en'"
>{{ $t('english') }}</b-button>
<b-button
size="sm"
class="my-2 my-sm-0"
type="submit"
#click="i18n.locale = 'tr'"
>{{ $t('turkish') }}</b-button>
</b-nav-form>
</b-navbar-nav>
</b-collapse>
</b-navbar>
</div>
</template>
<script>
export default {
name: 'HelloI18n'
}
</script>
<i18n>
{
"en": {
"johnDoe": "John Doe",
"home": "Home",
"about": "About Me",
"projects": "Projects",
"education": "Education",
"contact": "Contact",
"english": "English",
"turkish": "Turkish"
},
"tr": {
"johnDoe": "John Doe",
"home": "Anasayfa",
"about": "Hakkımda",
"projects": "Projelerim",
"education": "Eğitim",
"contact": "İletişim",
"english": "İngilizce",
"turkish": "Türkçe"
}
}
</i18n>
<style scoped>
</style>
App.vue
<template>
<div id="app">
<Header></Header>
</div>
</template>
<script>
import Header from "./components/Header.vue";
export default {
name: "app",
components: {
Header,
}
};
</script>
<style>
#app {
font-family: "Avenir", Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
}
</style>
main.js
import '#babel/polyfill'
import 'mutationobserver-shim'
import Vue from "vue";
import './plugins/bootstrap-vue'
import App from "./App.vue";
import i18n from './i18n'
Vue.config.productionTip = false;
new Vue({
i18n,
render: h => h(App)
}).$mount("#app");

You have a typo in the button #click event. i18n is accesible from the template with "$i18n" and no "i18n" so, your button code must be:
<b-button
size="sm"
class="my-2 my-sm-0 btn-info"
type="submit"
#click="$i18n.locale = 'en'"
>{{ $t('english') }}</b-button>

Related

Vue 3: How to use the render function to render different components that themselves render different components with slots, events, etc?

The title is basically my question. But the following code should make clearer what my goal is.
I have a version of this:
// AppItem.vue
<script>
import { h } from 'vue'
import { AppItem1 } from './item-1';
import { AppItem2 } from './item-2';
import { AppItem3 } from './item-3';
const components = {
AppItem1,
AppItem2,
AppItem3,
};
export default {
props: {
level: Number
},
render() {
return h(
components[`AppItem${this.level}`],
{
...this.$attrs,
...this.$props,
class: this.$attrs.class + ` AppItem--${this.level}`,
},
this.$slots
)
}
}
</script>
// AppItem1.vue
<template>
<AppBlock class="AppItem--1">
<slot name="header">
#1 - <slot name="header"></slot>
</slot>
<slot></slot>
</AppBlock>
</template>
// AppItem2.vue
<template>
<AppBlock class="AppItem--2">
<template #header>
#2 - <slot name="header"></slot>
</template>
<slot></slot>
</AppBlock>
</template>
// AppBlock.vue
<template>
<div class="AppBlock">
<div class="AppBlock__header">
<slot name="header"></slot>
</div>
<div class="AppBlock__body">
<slot></slot>
</div>
</div>
</template>
And my goal would be to use <AppItem> like...
<AppItem level="1">
<template #header>
Animal
</template>
<AppItem level="2">
<template #header>
Gorilla
</template>
<p>The gorilla is an animal...</p>
</AppItem>
<AppItem level="2">
<template #header>
Chimpanzee
</template>
<p>The Chimpanzee is an animal...</p>
</AppItem>
</AppItem>
...and have it render like...
<div class="AppBlock AppItem AppItem--1">
<div class="AppBlock__header">
Animal
</div>
<div class="AppBlock__body">
<div class="AppBlock AppItem AppItem--2">
<div class="AppBlock__header">
Gorilla
</div>
<div class="AppBlock__body">
<p>The gorilla is an animal...</p>
</div>
</div>
<div class="AppBlock AppItem AppItem--2">
<div class="AppBlock__header">
Chimpanzee
</div>
<div class="AppBlock__body">
<p>The Chimpanzee is an animal...</p>
</div>
</div>
</div>
</div>
Why does it not work? What I'm I misunderstaning?
The right way to get the AppItem--N component by name is to use the resolveComponent() function:
const appItem = resolveComponent(`AppItem${props.level}`);
I also fixed a couple of other small problems and had to rewrite the <setup script> to the setup() function. Now it works.
Playground
const { createApp, h, resolveComponent } = Vue;
const AppBlock = { template: '#appblock' }
const AppItem1 = { components: { AppBlock }, template: '#appitem1' }
const AppItem2 = { components: { AppBlock }, template: '#appitem2' }
const AppItem = {
components: {
AppItem1, AppItem2, AppBlock
},
props: {
level: Number
},
setup(props, { attrs, slots, emit, expose } ) {
const appItem = resolveComponent(`AppItem${props.level}`);
return () =>
h(appItem, {
...attrs,
...props,
class: (attrs.class ? attrs.class : '') + " AppItem--" + props.level,
}, slots);
}
}
const App = { components: { AppItem } }
const app = createApp(App)
app.mount('#app')
#app { line-height: 1; }
[v-cloak] { display: none; }
<div id="app">
<app-item :level="1">
<template #header>
<h4>Animal</h4>
</template>
<app-item :level="2">
<template #header>
Gorilla
</template>
<p>The gorilla is an animal...</p>
</app-item>
<app-item :level="2">
<template #header>
Chimpanzee
</template>
<p>The Chimpanzee is an animal...</p>
</app-item>
</app-item>
</div>
<script src="https://unpkg.com/vue#3/dist/vue.global.prod.js"></script>
<script type="text/x-template" id="appitem1">
<app-block class="AppItem">
<slot name="header">
#1 - <slot name="header"></slot>
</slot>
<slot></slot>
</app-block>
</script>
<script type="text/x-template" id="appitem2">
<app-block class="AppItem">
<template #header>
#2 - <slot name="header"></slot>
</template>
<slot></slot>
</app-block>
</script>
<script type="text/x-template" id="appblock">
<div class="AppBlock">
<div class="AppBlock__header">
<slot name="header"></slot>
</div>
<div class="AppBlock__body">
<slot></slot>
</div>
</div>
</script>

How can I use a sidenavbar toggle function in another header component in Vue3

Im using Vue3 in Laravel 9 with Inertia.js and I´m trying to create a Sidenavbar with a headerbar.
I would like to toggle the Sidenavbar with a "Menu" Button in the Header Component.
But I have no idea how can i use the toggle function for my Sidenavbar in my headerbar.
The Toggle function in the Sidenavbar is working fine.
Screenshot with Header and Sidebar
Layout/App.vue
<template>
<Header />
<div class="app">
<Nav />
<slot />
</div>
</template>
<script>
import Nav from "./Nav";
import Header from "./header.vue";
export default {
components: { Nav, Header },
};
</script>
Sidenavbar Nav.vue
<template>
<aside :class="`${is_expanded ? 'is-expanded' : ''}`">
<h3>Menu</h3>
<div class="menu">
<router-link to="/" class="button">
<span class="material-symbols-rounded">home</span>
<span class="text">Home</span>
</router-link>
<router-link to="/about" class="button">
<span class="material-symbols-rounded">description</span>
<span class="text">About</span>
</router-link>
<router-link to="/team" class="button">
<span class="material-symbols-rounded">group</span>
<span class="text">Team</span>
</router-link>
<router-link to="/contact" class="button">
<span class="material-symbols-outlined">admin_panel_settings</span>
<span class="text">Administration</span>
</router-link>
</div>
<div class="flex"></div>
<div class="menu">
<router-link to="/settings" class="button ">
<span class="material-symbols-rounded">settings</span>
<span class="text">Settings</span>
</router-link>
</div>
<div class="menu-toggle-wrap">
<button class="menu-toggle" #click="ToggleMenu">
<span class="material-symbols-outlined menu-icon">menu</span>
<span class="material-symbols-outlined arrow-back">arrow_back</span>
</button>
</div>
</aside>
</template>
<script >
import {ref} from 'vue'
export default {
data() {
return {
is_expanded: ref(localStorage.getItem("is_expanded") === "true"),
visible: false
};
},
methods: {
ToggleMenu() {
this.is_expanded = !this.is_expanded;
localStorage.setItem("is_expanded", this.is_expanded);
}
},
mounted() {
console.log(`The initial count is ${this.is_expanded}.`);
}
}
</script>
Header Header.vue
<template>
<div class=" header border-2">
<div class="menu-toggle-wrap">
<button class="menu-toggle" #click="">
<span class="material-symbols-outlined menu-icon">menu</span>
</button>
</div>
</div>
</template>
<script>
export default {
}
</script>
Here is my app.js file
import { createApp, h } from 'vue'
import { createInertiaApp } from '#inertiajs/inertia-vue3'
export const toggleMenu = new Vue();
createInertiaApp({
resolve: name => require(`./pages/${name}`),
setup({ el, App, props, plugin }) {
createApp({ render: () => h(App, props) })
.use(plugin)
.mount(el)
},
})

Vuejs Html tag / attribute in data()

I need to do {{item.icon}} pull as a html data not string but ı don't know how to do that, is there are anyway to do that please help me out
I have this code:
<div class="box my-5" v-for="(item, index) in items" :key="index" >
<div class="innerBox">
<router-link to="/ninethPage">
<div class="card Fcard d-flex flex-row justify-content-center align-items-center" style="padding: 1rem 2rem !important">
<span v-html="icon"> </span>
<p>{{item.title}}</p>
</div>
</router-link>
<router-view></router-view>
</div>
</div>
</div>
export default {
el: '#app',
data() {
return {
items: [
{title: 'Android', icon: <i class="fab fa-android mx-3 img-fluid" style="font-size: 1.5rem;" ></i>},
{title: 'IOS', icon: <i class="fab fa-apple mx-3 img-fluid" style="font-size: 1.5rem;" ></i>}
]
}
},
components:{
Header
}
}
`
icon: <i class=... is JSX syntax that creates an element and needs to be used with render function instead of a template. It should be a string, icon: '<i class=...'.
There is no icon property, it should be <span v-html="item.icon">.
It's impractical to specify the whole icon element. If only <i> classes differ, it can be icon: 'fa-android', and be used with:
<i class="class="fab mx-3 img-fluid" style="font-size: 1.5rem" :class="item.icon"/>

TinyMce breaking TailwindCss animation when loaded inside a slide-over element

I have a simple Vue3 application that is making heavy use of TailwindUI components. I'm trying to place a TinyMce editor inside of a slide over component and that works fine. The issue is the entry animation.
On first entry, it slides in like it's supposed to. However, if it is closed and reopened the entry animation is gone. The whole time the exit animation continues to work without issue. Is there a way I can do this and keep the animation intact?
Here is a CodeSandBox with the issue reproduced in it's simplest form.
Here is the relevant code:
App.vue
<template>
<button #click="open = true">Open Menu</button>
<SlideOver :open="open" #close="open = false" />
</template>
<script>
import { ref } from "vue";
import SlideOver from "./components/slide-over.vue";
export default {
name: "App",
components: {
SlideOver,
},
setup() {
const open = ref(false);
return { open };
},
};
</script>
slide-over.vue
<!-- This example requires Tailwind CSS v2.0+ -->
<template>
<TransitionRoot as="template" :show="open">
<Dialog
as="div"
static
class="fixed inset-0 overflow-hidden"
#close="$emit('close')"
:open="open"
>
<div class="absolute inset-0 overflow-hidden">
<DialogOverlay class="absolute inset-0" />
<div class="fixed inset-y-0 right-0 pl-10 max-w-full flex">
<TransitionChild
as="template"
enter="transform transition ease-in-out duration-500 sm:duration-700"
enter-from="translate-x-full"
enter-to="translate-x-0"
leave="transform transition ease-in-out duration-500 sm:duration-700"
leave-from="translate-x-0"
leave-to="translate-x-full"
>
<div class="w-screen max-w-md">
<div
class="h-full flex flex-col py-6 bg-white shadow-xl overflow-y-scroll"
>
<div class="px-4 sm:px-6">
<div class="flex items-start justify-between">
<DialogTitle class="text-lg font-medium text-gray-900">
Panel title
</DialogTitle>
<div class="ml-3 h-7 flex items-center">
<button
class="bg-white rounded-md text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
#click="$emit('close')"
>
<span class="sr-only">Close panel</span>
<XIcon class="h-6 w-6" aria-hidden="true" />
</button>
</div>
</div>
</div>
<div class="mt-6 relative flex-1 px-4 sm:px-6">
<TinyMceEditor api-key="no-api-key" />
</div>
</div>
</div>
</TransitionChild>
</div>
</div>
</Dialog>
</TransitionRoot>
</template>
<script>
import {
Dialog,
DialogOverlay,
DialogTitle,
TransitionChild,
TransitionRoot,
} from "#headlessui/vue";
import { XIcon } from "#heroicons/vue/outline";
import TinyMceEditor from "#tinymce/tinymce-vue";
export default {
components: {
Dialog,
DialogOverlay,
DialogTitle,
TransitionChild,
TransitionRoot,
XIcon,
TinyMceEditor,
},
props: {
open: {
type: Boolean,
default: false,
},
},
setup() {},
};
</script>
In my opinion, this is a problem with loading the TinyMce Editor (I don't know exactly what the problem is). I added a delay in loading the editor after opening the modal using watchEffect based on the props open with setTimeout in it and v-if on the TinyMceEditor tag. It may not be a perfect and aesthetic solution, but the animation works smoothly.
Here is a code in codesandbox.io.
And code here: slide-over.vue (App.vue stays the same)
<template>
<TransitionRoot as="template" :show="open">
<Dialog
as="div"
static
class="fixed inset-0 overflow-hidden"
#close="$emit('close')"
:open="open"
>
<div class="absolute inset-0 overflow-hidden">
<DialogOverlay class="absolute inset-0" />
<div class="fixed inset-y-0 right-0 pl-10 max-w-full flex">
<TransitionChild
as="template"
enter="transform transition ease-in-out duration-500 sm:duration-700"
enter-from="translate-x-full"
enter-to="translate-x-0"
leave="transform transition ease-in-out duration-500 sm:duration-700"
leave-from="translate-x-0"
leave-to="translate-x-full"
>
<div class="w-screen max-w-md">
<div
class="h-full flex flex-col py-6 bg-white shadow-xl overflow-y-scroll"
>
<div class="px-4 sm:px-6">
<div class="flex items-start justify-between">
<DialogTitle class="text-lg font-medium text-gray-900">
Panel title
</DialogTitle>
<div class="ml-3 h-7 flex items-center">
<button
class="bg-white rounded-md text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
#click="$emit('close')"
>
<span class="sr-only">Close panel</span>
<XIcon class="h-6 w-6" aria-hidden="true" />
</button>
</div>
</div>
</div>
<div class="mt-6 relative flex-1 px-4 sm:px-6">
<TinyMceEditor v-if="loadEditor" api-key="no-api-key" />
</div>
</div>
</div>
</TransitionChild>
</div>
</div>
</Dialog>
</TransitionRoot>
</template>
<script>
import { ref, watchEffect } from "vue";
import {
Dialog,
DialogOverlay,
DialogTitle,
TransitionChild,
TransitionRoot,
} from "#headlessui/vue";
import { XIcon } from "#heroicons/vue/outline";
import TinyMceEditor from "#tinymce/tinymce-vue";
export default {
components: {
Dialog,
DialogOverlay,
DialogTitle,
TransitionChild,
TransitionRoot,
XIcon,
TinyMceEditor,
},
props: {
open: {
type: Boolean,
default: false,
},
},
setup(props) {
const loadEditor = ref(false);
watchEffect(() => {
if (props.open) {
setTimeout(function () {
loadEditor.value = true;
}, 10);
} else {
loadEditor.value = false;
}
});
return { loadEditor };
}
};
</script>

How to validate if radio button is selected?

I got these two radio buttons in my page:
<input type="radio" :id="DIGITAL_INVOICE_DIGITAL" name="digitalInvoice" :value="false" v-model="digitalInvoice"/>
<input type="radio" :id="DIGITAL_INVOICE_PAPER" name="digitalInvoice" :value="false" v-model="digitalInvoice"/>
They are not part of <base-radio-list> and I don't want them to be.
As part of making user choice effective, both of them are not selected in the first time, thus, there need to be a validation to check if the user chose or not.
I want to make this validation using vee-validate, how can I make it ?
Below you can find validation using ValidationProvider from vee-validate.
You can work on top of this codesandbox.
Let me know how it goes.
Test.Vue
<template>
<div class="columns is-multiline">
<div class="column is-6">
<p class="control">
<ValidationProvider
ref="provider"
rules="oneOf:1,2"
v-slot="{ errors }"
>
<label class="radio">
<input
name="digitalInvoice"
value="1"
type="radio"
v-model="digitalInvoice"
/>
Yes
</label>
<label class="radio">
<input
name="digitalInvoice"
value="2"
type="radio"
v-model="digitalInvoice"
/>
No
</label>
<br />
<p class="control">
<b>{{ errors[0] }}</b>
</p>
</ValidationProvider>
<br />
</p>
</div>
</div>
</template>
<script>
import { ValidationProvider, extend } from "vee-validate";
import { oneOf } from "vee-validate/dist/rules";
extend("oneOf", {
...oneOf,
message: "Choose one",
});
export default {
name: "radio-buttons-test",
components: {
ValidationProvider,
},
data() {
return {
digitalInvoice: false,
};
},
mounted() {
this.$refs.provider.validate();
},
};
</script>
App.vue
<template>
<div id="app">
<Test />
</div>
</template>
<script>
import Test from "./components/Test";
export default {
name: "App",
components: {
Test,
},
};
</script>
<style>
#app {
font-family: "Avenir", Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
In Vue, the value of the input and the value of the data object are the same.
For the input radios that are more than one element with the same v-model, the name attribute is ignored and the value stored in the data will be equal to the value attribute of the currently selected radio input.
<input type="radio" :id="DIGITAL_INVOICE_DIGITAL" :value="value1" v-model="digitalInvoice"/>
<input type="radio" :id="DIGITAL_INVOICE_DIGITAL" :value="value2" v-model="digitalInvoice"/>
and your vue data is:
data: {
digitalInvoice: "value1"
}
for unchecked try:
data: {
digitalInvoice: ""
}