Rendering nested components - vue.js

I am starting to learn VueJS by building a simple website.
Right now I have made three components:
Header
Navigation
Topbar
I want to render navigation and topbar inside header, so I can call the header component inside every page (I haven't found a way to make a "layout" or something, that every page uses).
Header.vue
<template>
<header id="topNav">
<topbar />
<navigation />
</header>
</template>
<script lang="ts">
import Vue from 'vue'
import navigation from '../navigation/navigation.vue'
import Topbar from '../topbar/topbar.vue'
export default Vue.extend({
components: { Topbar, navigation },
})
</script>
<style scoped>
#topNav {
background-color: #fff;
-webkit-box-shadow: 0 0 35px 0 rgb(154 161 171 / 15%);
box-shadow: 0 0 35px 0 rgb(154 161 171 / 15%);
position: fixed;
left: 0;
right: 0;
top: 0;
z-index: 1001;
padding: 0 12px;
}
</style>
Topbar
<template>
<div id="topBar">
<h2>this is the topbar</h2>
</div>
</template>
<script lang="ts">
import Vue from 'vue'
export default Vue.extend({})
</script>
<style scoped>
#topBar {
border-bottom: 1px solid #dee2e6;
height: 70px;
padding: 0 10px;
z-index: 100;
position: fixed;
left: 0;
right: 0;
}
</style>
And when I call it on for example my home page (index.vue) nothing renders.
<template>
<main>
<header />
<div id="page-content">
<h1>Home</h1>
</div>
</main>
</template>
<script lang="ts">
import Vue from 'vue'
import Header from '../components/header/header.vue'
export default Vue.extend({
name: 'Home',
components: { Header },
})
</script>
I've tried reading the documentation and search around, but haven't been able to figure out what I'm doing wrong.

Since the question is tagged with a Nuxt tag, I'll recommend looking into Nuxt layouts: https://nuxtjs.org/docs/concepts/views#layouts
There is a default named default that you could use by creating the file /layouts/default.vue and passing the components inside of it.
You can of course change that with a layout: 'yolo' if you want another 'yolo` layout.
Pro tip: you don't need to import the components yourself in Nuxt.

You have named your component header, which is a standard html element. Therefore the browser will probably just try to render a standard <header> element instead of your component.
Therefore it is advised to always use multi word component names. See docs here. You can use eslint in your code editor to help you spot these mistakes.
PS: if you are learning vue from the start, I would advise you to use the composition api with the script setup approach, as it makes things easier and provide the opportunity to write clearer code as components grow.

Related

Vue 3: how to change body background color only in specific page

with vue 3 i need to change the body background color only when i visit a signin page.
My index.html is:
<body class="body">
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
How can i change the body background color from a component or from vue router?
In the App component you can put a conditional class on the top level element if the route matches your desired route. As long as your top level element is full height and width of the body.
<template>
<div class="background" :class="{ otherColor: isSignIn }">
<div id="app"></div>
</div>
</template>
<script>
export default {
computed: {
isSignIn() {
return this.$route.path === '/signIn';
}
}
}
</script>
<style>
.background {
height: 100vh;
width: 100vw;
background-color: #ffffff;
}
.otherColor {
background-color: #d4d4d4; // whatever background color you want.
}
</style>

List Transitions work only for "enter" not for "leave"

Following the example in the docs, I'm using transition-group for a list of items. Strangely it works when items appear (enter), not when they disappear (leave), meaning they slide down in an animated fashion when appearing, but disappear instantly without animation: the leave animation failed. Why?
<template>
<div v-if="notifications.length">
<transition-group name="notifications">
<span
v-for="notification in notifications"
:key="notification.id"
>
<!-- content -->
</span>
</transition-group>
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
computed: {
...mapState({
notifications: state => state.notifications.notifications
})
}
}
</script>
<style lang="scss" scoped>
.notifications-enter-active,
.notifications-leave-active {
transition: all 0.5s;
}
.notifications-enter {
transform: translateY(-100%);
}
.notifications-leave-to {
opacity: 0;
}
</style>
Store
export const mutations = {
DELETE_NOTIFICATION (state, id) {
state.notifications.splice(
state.notifications.findIndex(item => item.id === id),
1
)
}
}
I couldn't reproduce the exact symptom with that code (demo 1), which only transitions on leave instead of enter in your scenario. The reason for that is because the span is display: inline, which prevents the transition.
The Vue docs provide a tip for this:
One important note is that these FLIP transitions do not work with elements set to display: inline. As an alternative, you can use display: inline-block or place elements in a flex context.
So, you can apply display: flex on the transition-group:
<template>
<transition-group class="container">
...
</transition-group>
</template>
<style>
.container {
display: flex;
}
</style>
demo 2
Or display: inline-block on the span to be transitioned:
<template>
<span class="notification-item">
...
</span>
</template>
<style>
.notification-item {
display: inline-block;
}
</style>
demo 3
Turns out by replacing <div v-if="notifications.length"> with <div v-if="notifications"> transitions now work. Even though this doesn't make any sense to me.
If anyone can explain in a comment that'd be nice :)

Vue.js Scroll Snap to Component

I'm trying to scroll snap to components in Vue.js 3. I can get scroll snap to work correctly with vanilla HTML and CSS. Here's what it looks like: Scroll Snap Demo
I'm trying to copy that simple layout but using App.js as the container and components as the divs. Here's what it looks like in App.vue:
<template class="container">
<CompI class="snapAlign"/>
<CompII class="snapAlign"/>
<CompIII class="snapAlign"/>
<CompIV class="snapAlign"/>
</template>
<script>
import CompI from './components/CompI.vue'
import CompII from './components/CompII.vue'
import CompIII from './components/CompIII.vue'
import CompIV from './components/CompIV.vue'
export default {
name: 'App',
components: {
CompI,
CompII,
CompIII,
CompIV
}
}
</script>
<style>
*{
margin: 0;
padding: 0;
text-align: center;
}
.container{
overflow-y: scroll;
scroll-snap-type: y mandatory;
}
.snapAlign{
scroll-snap-align: start;
}
</style>
Here's what I've got for components, they're all basically the same as each other:
<template>
<div class="one">
<h1>One</h1>
</div>
</template>
<script>
export default {
name: 'CompI'
}
</script>
<style scoped>
.one{
height: 100vh;
width: 100vw;
background-color: green;
}
</style>
I also tried using vue-scroll-snap but it doesn't seem to work on components the way the guides show it working on divs. This was the guide I tried: Vue Scroll Snap Guide
Here's what I've got for App.vue using that method:
<template>
<vue-scroll-snap >
<CompI/>
<CompII/>
<CompIII/>
<CompIV/>
</vue-scroll-snap>
</template>
<script>
import CompI from './components/CompI.vue'
import CompII from './components/CompII.vue'
import CompIII from './components/CompIII.vue'
import CompIV from './components/CompIV.vue'
import VueScrollSnap from "vue-scroll-snap"
export default {
name: 'App',
components: {
CompI,
CompII,
CompIII,
CompIV,
VueScrollSnap
}
}
</script>
<style>
*{
margin: 0;
padding: 0;
text-align: center;
}
</style>
The components are the same as the other method.
The pages appear correctly but there's no scroll snap effect with either of these attempts. I'd like to try and use the components as full screen elements to snap to, the app will scale better like that.
+++ Solution +++
Ivo Gelov was correct, remove the class from <template>, place the components inside a div and give it the class "container". For some reason that alone wasn't enough, more style was needed on that class then it works. Here's the final CSS for that class:
.container{
width: 100%;
height: 100vh;
margin: 0 auto;
overflow-x: hidden;
overflow-y: scroll;
scroll-snap-type: y mandatory;
}

VueJs: How to change CSS background image for each page route?

I am probably making this more complicated than it needs to be, but does anyone have any ideas on how I can swap out custom banner images for each of my pages (when route changes to any thing other than index page)? I have the slots working , but need way to change background image in the non-index banners. Here's my code:
App.vue and shows only the main page banner:
<template>
<div id="app">
<BannerHome v-if="isHomeView" />
<main id="routerView">
<RouterView :key="$route.fullPath" />
</main>
<Footer />
</div>
</template>
<script>
import BannerHome from '#/components/BannerHome.vue'
import Footer from '#/components/Footer.vue'
export default {
name: 'App',
components: {
BannerHome,
Footer
},
computed: {
isHomeView() {
return this.$route.fullPath === '/'
}
}
}
</script>
Example of non-index page (About.vue):
<template>
<article>
<banner>
<template v-slot:titleTop>Mission Focused</template>
<template v-slot:titleBottom>
Dedicated To Service
</template>
</banner>
<section class="container-md pb-5">
<h1>About Page</h1>
</section>
</article>
</template>
<script>
import Banner from '#/components/Banner.vue'
export default {
name: 'About',
components: {
Banner
}
}
</script>
Banner component for non-index pages (Banner.vue). Note the background image. How can I swap this out for each non-index page? Right now the background image is shown for ALL non-index pages.
<template>
<header>
<MainNav />
<div class="container">
<h1><em><slot name="titleTop"></slot><br />
<span class="pl-3"><slot name="titleBottom"></slot></span></em>
</h1>
</div>
</header>
</template>
<script>
import MainNav from '#/components/MainNav.vue'
export default {
name: 'Banner',
components: {
MainNav
}
}
</script>
<style lang="scss" scoped>
header {
position: relative;
width: 100%;
// set this page height here
height: 20em;
text-transform: uppercase;
}
header::before {
content: "";
display: block;
position: absolute;
left: 0;
bottom: 6em;
width: 100%;
// set this page height here
height: calc(20em + 10em);
z-index: -1;
transform: skewY(-3.5deg);
background: linear-gradient(rgba(0, 0, 0, 0.6), rgba(0, 0, 0, 0.6)),
url('../assets/mockphoto.jpg') < -----how to swap out this image?
no-repeat left top,
linear-gradient(#4e4376, #2b5876);
background-size: cover;
border-bottom: 0.2em solid #fff;
}
h1 {
font-size: 1.75rem!important;
font-weight: 700!important;
letter-spacing: 0.01em;
padding: 2.5rem 0 0 0;
// text-shadow: 0.022em 0.022em 0.022em #111;
color: #fff;
}
</style>
Btw, I am also using Gridsome...so if there is also an easy way to access page data via GraphQL for this purpose I can also use that. Thanks for any help !
So, I actually just need to add the /deep/ keyword to the parent CSS like so:
/deep/ header::before {
background: linear-gradient(rgba(0, 0, 0, 0.6), rgba(0, 0, 0, 0.6)),
url('../assets/mockphoto.jpg')
no-repeat left top,
linear-gradient(#4e4376, #2b5876);
}
Docs: https://vue-loader.vuejs.org/guide/scoped-css.html#deep-selectors

Initialize components in for loop from array data

Trying initialize custom elements (3 buttons) in for loop but first element missing text.
LeftMenu.vue
<template>
<div id="left-menu">
<MenuButton v-for="mytext in buttonList" v-bind:key="mytext" v-bind:mytext="mytext"/>
</div>
</template>
<script>
import MenuButton from './components/MenuButton.vue'
export default {
name: 'left-menu',
components: {
MenuButton
},
computed: {
buttonList() {
return ["Test1", "Test2", "Test3"];
}
}
}
</script>
<style>
#left-menu {
width: 200px;
height: 100%;
position: absolute;
left: 0;
top: 0;
}
</style>
MenuButton.vue
<template>
<div id="left-menu-button">
{{mytext}}
</div>
</template>
<script>
export default {
name: 'left-menu-button',
props: {
mytext: String
}
}
</script>
<style>
#left-menu-button {
width: 180px;
height: 50px;
margin-left: 10px;
margin-bottom: 5px;
}
</style>
main.js
import Vue from 'vue'
import LeftMenu from './LeftMenu.vue'
import MenuButton from './components/MenuButton.vue'
Vue.config.productionTip = false
new Vue({
render: h => h(LeftMenu)
}).$mount('#left-menu')
new Vue({
render: h => h(MenuButton)
}).$mount('#left-menu-button');
I am new to vue and still trying to figure out how all part are connected and working together. It just seems very strange that I got 3 buttons but only last two of them have text and first one does not...may be someone can point me to my mistake.
You've assigned an id of left-menu-button to each of your buttons. You've then told Vue to mount something into that id. The first element (i.e. first button) with that id will be treated as the mounting element, which blows away the text.
You should remove the ids from all elements within your templates. The only id should be the one within your HTML file. For styling purposes use classes instead of ids. Then create a single Vue instance (just one call to new Vue, not two) targeting the id of the element inside your HTML file.
It is possible to create multiple Vue instance directly using new Vue but that is rarely necessary. To do that you would need to have multiple target elements within your HTML file.