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
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>
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>
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>
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>
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>