How can I use a sidenavbar toggle function in another header component in Vue3 - vue.js

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)
},
})

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>

router link in v-if is working but url does not change

i have succeed create router link in v-if but the url is does not change, what is expected is url change so if user click "go back one page" it is goes to previews page
<template>
<div class="container-fluid">
<div class="row" v-if="productAllData">
<div v-for="item in productAll" :key="item._id" class="col-md-2 col-xs-6">
<div class="card card-margin-right card-buttom" #click="clickProductById(item._id)">
<img v-bind:src="item.image[0].filename" />
<div class="card-body">
<h5 class="card-title">{{item.name}}</h5>
<p>
{{item.description}}<br>
<span class="font-weight-bold">{{item.fprice}} / {{item.unit}}<br></span>
stock : {{item.quantity}}<br>
</p>
</div>
</div>
</div>
</div>
<product-by-id :id="id" :test="`/productall/${id}`" v-if="productByIdData">
<router-link :to="`/productall/${id}`"></router-link>
</product-by-id>
</div>
</template>
in above code there is router link in v-if
<product-by-id :id="id" :test="`/productall/${id}`" v-if="productByIdData">
<router-link :to="`/productall/${id}`"></router-link>
</product-by-id>
and that code is working, since if i console log there is "/productall/603aaaf5dead94fee94d2811"
but why my url is not change still "http://localhost:8080/productall" what i expected is "http://localhost:8080/productall/603aaaf5dead94fee94d2811"
I set up an example routing implementation. A couple of components with a router configuration.
/router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter);
import Home from '#/components/stackoverflow/router-params-example/Home'
import ProductAll from '#/components/stackoverflow/router-params-example/ProductAll'
const routes = [
{
path: '/',
name: 'home',
component: Home
},
{
path: '/productall/:id',
name: 'productall',
component: ProductAll,
props: true
}
]
export default new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
App.vue
<template>
<div id="app" class="container">
<router-view></router-view>
</div>
</template>
Home.vue
<template>
<div class="parent">
<h4>Home</h4>
<div class="row">
<div class="col-md-6">
<router-link class="btn btn-primary" :to="{ name: 'productall', params: { id: productId } }">Product All</router-link>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
productId: '603aaaf5dead94fee94d2811'
}
}
}
</script>
ProductAll.vue
<template>
<div class="product-all">
<h4>Product All</h4>
<div class="row">
<div class="col-md-6">
<router-link class="btn btn-secondary" :to="{ name: 'home' }">Back</router-link>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
id: {
type: String,
required: true
}
}
}
</script>

Data not updating when API changed on Vue Js

Ok, I have a problem, here I make a data post with a tag, and when I click the tag the other data posts are not updated by the tag.
i'm get the api with created function, Do I have to use vuex?
can everyone help me, thanks very much
this is my template
<div class="col-md-4 mb-4" v-for="post in posts" :key="post.id">
<div class="card h-100 shadow-sm border-0 rounded-lg">
<div class="card-img">
<img :src="path+'/storage/posts/'+post.image" class="w-100"
style="height: 200px;object-fit: cover;border-top-left-radius: .3rem;border-top-right-radius: .3rem;">
</div>
<div class="card-body">
<router-link :to="{name: 'detail_post', params: {slug: post.slug}}"
class="text-dark text-decoration-none">
<h6>{{ post.title }}</h6>
</router-link>
</div>
<div class="card-footer bg-white">
<i class="fa fa-calendar" aria-hidden="true"></i> {{ post.created_at }}
</div>
</div>
</div>
this is my JavaScript
<script>
//import axios
import axios from 'axios'
export default {
name: 'PostIndex',
data() {
return {
path: axios.defaults.baseURL,
posts: []
}
},
created() {
//get post homepage
axios.get(`/api/tag/${this.$route.params.slug}`).then(response => {
this.posts = response.data.data.data;
})
}
}
</script>

How to pass data to the $slots in child component?

I have two components
Navigation
Alert
Alert is Navigation's child
Navigation code
<template>
<div class="col-lg-5">
<base-alert :icon="`fa fa-facebook`"></base-alert>
</div>
</template>
<script>
export default {
data() {
return {
error: 'This is error'
}
}
}
</script>
Base alert code
<slot>
<span v-if="icon" class="alert-inner--icon">
<i :class="icon"></i>
</span>
<span v-if="$slots.text" class="alert-inner--text">
<slot name="text"></slot>
</span>
</slot>
Currently this is what I got
My problem right now how do i pass the error data to the base-alert $slots.text?
Regards
You need to add <template slot="text">{{ error }}</template> as below. Where text is the name of slot you have mentioned in Base Alert
Navigation Code
<template>
<div class="col-lg-5">
<base-alert :icon="`fa fa-facebook`">
<template slot="text">{{ error }}</template>
</base-alert>
</div>
</template>
<script>
import baseAlert from "./base-alert";
export default {
components: { baseAlert },
data() {
return {
error: "This is error"
}
}
}
</script>
Base Alert
<template>
<div>
<slot>
<span v-if="icon" class="alert-inner--icon">
<i :class="icon"></i>
</span>
<span v-if="$slots.text" class="alert-inner--text">
<slot name="text"></slot>
</span>
</slot>
</div>
</template>
<script>
export default {
name: "base-alert",
props: ["icon"]
};
</script>
<style scoped></style>
Pass your error message down as a prop to the child.
Navigation code
<template>
<div class="col-lg-5">
<base-alert :error="error" :icon="`fa fa-facebook`"></base-alert>
</div>
</template>
<script>
export default {
data() {
return {
error: 'This is error'
}
}
}
</script>
Base alert code
<slot>
<span v-if="icon" class="alert-inner--icon">
<i :class="icon"></i>
</span>
<span v-if="error" class="alert-inner--text">
<div>{{ error }}</div>
</span>
</slot>

Passing events between Vue parent and child

I am trying to emit an event from a child and listen for it on the parent, but I am always getting errors like Invalid handler for event "new-tab": got undefined.
My app:
<div class="ibox" id="app">
<div class="ibox-content">
<h2>Table</h2>
<div class="details">
<table-tabs
v-on:new-tab="newTab"
v-on:close-tab="closeTab"
ref="tableTabs"
name="table-tabs"
></table-tabs>
</div>
</div>
</div>
TableTabs component:
<template>
<div>
<b-card no-body>
<b-tabs card>
<b-tab active>
<template slot="title">
<i class="fa fa-tablet-alt"></i> Orders
</template>
<orders-table
name="orders-table"
></orders-table>
</b-tab>
<b-tab v-for="order in tabs" :key="i">
<template slot="title">
<div>{{ order.name }}</div>
<b-button type="button" class="close float-right" aria-label="Close" #click="closeTab(order.id)">
<span aria-hidden="true">×</span>
</b-button>
</template>
<items-table
name="items-table"
:api-url="'/api/items/' + order.id"
></items-table>
</b-tab>
</b-tabs>
</b-card>
</div>
</template>
<script>
export default {
name: 'table-tabs',
data() {
return {
tabs: [],
}
},
methods: {
closeTab(id) {
for (let i = 0; i < this.tabs.length; i++) {
if (this.tabs[i].id === id) {
this.tabs.splice(i, 1);
}
}
},
newTab(item) {
this.tabs.push(item);
}
}
}
</script>
OrdersTable component:
<template>
<div>
<vuetable ref="vuetable"
:api-url="apiUrl"
:fields="fields"
pagination-path="pagination"
#vuetable:pagination-data="onPaginationData"
>
<div slot="name-slot" slot-scope="{ rowData }">
<a href="#" class="float-left" #click="newTab(rowData.order)">
{{ rowData.order.name }}
<span class="fa fa-search-plus"></span>
</a>
</div>
</vuetable>
<div class="row">
<div class="col-md-6">
<vuetable-pagination-info
ref="paginationInfo"
></vuetable-pagination-info>
</div>
<div class="col-md-6">
<vuetable-pagination-bootstrap
ref="pagination"
class="pull-right"
#vuetable-pagination:change-page="onChangePage"
></vuetable-pagination-bootstrap>
</div>
</div>
</div>
</template>
<script>
import Vuetable from 'vuetable-2/src/components/Vuetable';
import VuetablePaginationBootstrap from '../VuetablePaginationBootstrap';
import VuetablePaginationInfo from 'vuetable-2/src/components/VuetablePaginationInfo';
import TableFieldDef from './table-field-def';
export default {
name: 'orders-table',
components: {
Vuetable,
VuetablePaginationBootstrap,
VuetablePaginationInfo
},
data() {
return {
apiUrl: '/api/orders?include=items',
fields: TableFieldDef,
}
},
methods: {
newTab(order) {
this.$emit('new-tab', order);
}
}
}
</script>
Why does this.$emit('new-tab', order) always result in the handler newTab being undefined.
Vue 2.5.21
The parent in this case is the TableTabs component. If the parent needs to listen to events emitted by a child component then it needs to add the event listener to the child component, which is the OrdersTable component in this case. So instead of this ..
<table-tabs
v-on:new-tab="newTab"
v-on:close-tab="closeTab"
ref="tableTabs"
name="table-tabs"
></table-tabs>
You should do this (inside the TableTabs component) ..
<orders-table
name="orders-table"
v-on:new-tab="newTab"
></orders-table>