I currently have duplicated layout that the only difference is a prop I pass to a component:
default.vue
<template>
<div class="page">
<SkipToContent />
<Header />
<Nuxt />
<Footer />
</div>
</template>
front.vue
<template>
<div class="page">
<SkipToContent />
<Header light="true" />
<Nuxt />
<Footer />
</div>
</template>
Is there any way to pass that light: true from page to layout so I can use only default.vue layout?
I know I could emit some event on mounted but would like to prevent using lifecycle hooks
Passing data up from a child is ideally done by emitting events, and you don't need to use lifecycle hooks for that (a custom event listener would work).
But I think a cleaner solution would be to factor out the common markup into a component that receives a light prop, and use them in both layouts:
<!-- components/CommonLayout.vue -->
<template>
<div class="page">
<SkipToContent />
<Header :light="light" />
<Nuxt />
<Footer />
</div>
</template>
<script>
export default {
props: {
light: Boolean,
}
}
</script>
<!-- layouts/default.vue -->
<template>
<CommonLayout />
</template>
<!-- layouts/front.vue -->
<template>
<CommonLayout light />
</template>
demo
Related
I have Vue.js app and the key part of the code of component looks as following:
specific-modal.vue:
<template>
<v-modal-dialog
:visible="isModalVisible"
title="CONNECTION"
okText="Save"
cancelText="Cancel"
:isSaveDisabled="isDisabled"
:width="550"
:height="620"
#ok="onOk"
#cancel="onCancel"
#hiding="onHiding"
>
...
base-modal.vue:
<template>
<DxPopup
v-bind="$attrs"
:hide-on-outside-click="true"
:drag-enabled="true"
:wrapper-attr="popupAttributes"
position="center"
v-on="$listeners"
#hiding="onHiding"
>
<slot />
<slot name="footer">
<DxToolbarItem template="save-template" toolbar="bottom" location="after" />
<template #save-template>
<DxButton text="Ok" #click="onOk" />
</template>
<DxToolbarItem template="cancel-template" toolbar="bottom" location="after" />
<template #cancel-template>
<DxButton text="Cancel" #click="onCancel" />
</template>
</slot>
</DxPopup>
</template>
<script>
import { DxPopup, DxToolbarItem } from "devextreme-vue/popup";
import DxButton from 'devextreme-vue/button';
...
The #save-template is underlined with red line and there is a message:
'v-slot' directive must be owned by a custom element, but 'slot' is not.eslint-plugin-vue
How to overcome this isssue?
EDIT:
If I do as following:
<slot />
<slot name="footer">
<DxToolbarItem template="save-template" toolbar="bottom" location="after" />
<custom-element>
<template #save-template>
<DxButton text="TEST" #click="onOk" />
</template>
</custom-element>
<DxToolbarItem template="cancel-template" toolbar="bottom" location="after" />
<custom-element>
<template #cancel-template>
<DxButton text="Cancel" #click="onCancel" />
</template>
</custom-element>
</slot>
When I run the app, I'm getting the following error:
[Vue warn]: Unknown custom element: <custom-element> - did you register the component correctly? For recursive components, make sure to provide the "name" option.
I'm not sure how to do it.
<template #save-template is a shorthand for <template v-slot='save-template'.
So the eslint rule is complaining that the <template v-slot='save-template' is owned (has a parent) that is not a custom element (which is ).
<slot> is a like a placeholder that external components can use to add data. But in your code it seems like you are saying a placeholder has provided a placeholder inside it.
To overcome this either change parent slot tag to something else. Or, wrap template tag in a tag like:
<template>
<DxPopup
v-bind="$attrs"
:hide-on-outside-click="true"
:drag-enabled="true"
:wrapper-attr="popupAttributes"
position="center"
v-on="$listeners"
#hiding="onHiding"
>
<slot />
<slot name="footer">
<DxToolbarItem template="save-template" toolbar="bottom" location="after" />
<!-- Add custom element wrapping here -->
<custom-element>
<template #save-template>
<DxButton text="TEST" #click="onOk" />
</template>
</custom-element>
</slot>
</DxPopup>
</template>
More details:
https://eslint.vuejs.org/rules/valid-v-slot.html
https://vuejs.org/guide/components/slots.html#named-slots
I've found the solution on Stackoverflow:
<template>
<DxPopup
v-bind="$attrs"
:hide-on-outside-click="true"
:drag-enabled="true"
:wrapper-attr="popupAttributes"
position="center"
v-on="$listeners"
#hiding="onHiding"
>
<slot />
<slot name="footer">
<DxToolbarItem template="save-template" toolbar="bottom" location="after" />
<DxToolbarItem template="cancel-template" toolbar="bottom" location="after" />
</slot>
<template #save-template>
<DxButton text="TEST" #click="onOk" />
</template>
<template #cancel-template>
<DxButton text="Cancel" #click="onCancel" />
</template>
</DxPopup>
</template>
I've product listing page where I use multiple ProductCard component. I need to add Spinner to Add to cart button when clicking on it, but if I just add Spinner to v-else I've Spinners in every ProductCard. How to control only this ProductCard where I click Add to cart button?
Parent's component:
<ProductCard
v-for="product in products"
:key="product.id"
:product="product"
/>
Child's component:
<template>
<div id="productCart">
<div>
<NuxtLink :to="`/product/${item.slug}`" />
<NuxtLink v-if="item.name" :to="`/product/${item.slug}`">
<h5
id="productName"
v-text="item.name"
/>
</NuxtLink>
<Button
v-if="!loading"
#click="addToBuyCart({ product: item })"
v-text="$t('labels.buy')"
/>
<Spinner v-else />
</div>
</div>
</template>
loading is defined in composable, and it's updating in addToBuyCart function by making it true, then adding to cart (api), then making it false
As pointed by IVO, in such situations you should check against a value specific for each item in the loop, item ID for example, instead of a boolean which lacks specificity and therefore cannot be used for such scenarios.
<template>
<div id="productCart">
<div>
<NuxtLink :to="`/product/${item.slug}`" />
<NuxtLink v-if="item.name" :to="`/product/${item.slug}`">
<h5
id="productName"
v-text="item.name"
/>
</NuxtLink>
<Button
v-if="loading !== item.id"
#click="addToBuyCart({ product: item })"
v-text="$t('labels.buy')"
/>
<Spinner v-else />
</div>
</div>
</template>
const addToBuyCart = ({product}) => {
loading = product.id
// some logic or request
loading = null
}
What is the difference between these two components in Nuxt3 and how do I use them correctly?
If I want to use pages/... what is the right approach here to create links and jump from page to page?
Everything is pretty much explained in the documentation: https://v3.nuxtjs.org/migration/pages-and-layouts/
You need to use this in app.vue
<template>
<nuxt-layout>
<nuxt-page /> <!-- used to display the nested pages -->
</nuxt-layout>
</template>
With a default /layouts/default.vue file
<template>
<div>
this is coming from the layout
<slot /> <!-- required here only -->
</div>
</template>
You will get this on / (with /pages/index.vue)
<template>
<div>index page</div>
</template>
And with the following structure, you will achieve dynamic pages
/pages/users/index.vue
<script setup>
definePageMeta({
layout: false
});
function goToDynamicUser() {
return navigateTo({
name: 'users-id',
params: {
id: 23
}
})
}
</script>
<template>
<div>
<p>
index page
</p>
<button #click="goToDynamicUser">navigate to user 23</button>
</div>
</template>
/pages/users/[id].vue
<script setup>
definePageMeta({
layout: false
});
const route = useRoute()
</script>
<template>
<pre>{{ route.params.id }}</pre>
</template>
I've removed the layout here to show how to disable it, but you can totally let the default here or even provide a custom one.
So, nuxt-page is to be used when you want to display the pages in your app (replacing <nuxt /> and <nuxt-child />) while <slot /> is to be used in the layout (as any other component using the slot tag).
Is it possible to pass as a prop when calling another component?
Essentially, I have components, and views, I build my views using various components. I want to have 1 styled component which I can reuse, so I was thinking to have a WebsiteLayout.vue:
<template>
<a-layout-content :style="{ padding: '0 24px', minHeight: '280px' }" />
{{ content }}
</template>
<script>
export default {
name: "View",
components: {
},
props: ["content"],
};
</script>
And in my App.vue:
<template>
<Content content=<router-view /> />
</template>
This isnt correct, but wondering if something like this is possible, and how I could achieve it?
With Vue 3 and Router 4, you can do something like
<router-view v-slot="{ Component }">
<SomeComponent>
<component :is="Component" />
</SomeComponent>
</router-view>
and then
//SomeComponent.js
<template>
<div class="wrapper">
<slot></slot>
</div>
</template>
here is an example with transition component as a wrapper for the router-view component
to know more about scoped slots you can see this
I want to load 3 Components at a time like that:
<app-header />
<app-main />
<app-footer />
But I want to load Router View also in this page.
<app-header />
<router-view />
<app-footer />
While I will click on the router-link then <app-main /> will be vanish and <router-view /> will be visible.
Is there any better way to handle it without if or show?
You can pass router-view via slot to your app-main component like this:
<app-header />
<app-main>
<router-view/>
</app-main>
<app-footer />
Also you need to insert a slot tag in your app-main component like this:
<template>
<!-- your code -->
<slot></slot>
<!-- ... -->
</template>
For more details visit https://v2.vuejs.org/v2/guide/components-slots.html