Window is not defined in nuxt js - vue.js

So, I wanted to bind style with the height of another element that I got from getHeight function, but I kept getting an error that said window is not defined.
Can someone please give me a solution?
Here is my source code:
<template>
<div class="container">
<p class="section-title">past event</p>
<div class="columns is-multiline">
<div
class="column is-one-third is-centered past-events"
v-for="(event, index) in events.slice(0, 2)"
:key="index"
>
<EventCard :event="event" />
</div>
<div class="column is-one-third is-centered">
<div class="link-box" :style="{ height: getHeight() }">
<nuxt-link to="/past-events">
<p style="color: #ffffff; cursor: pointer" class="see-all">
Lihat List Event Lainnya
</p>
</nuxt-link>
</div>
</div>
</div>
<a class="see-all-btn"> </a>
</div>
</template>
<script>
import EventCard from "~/components/EventCard.vue";
export default {
name: "PastEvents",
components: {
EventCard
},
props: ["events"],
data() {
return {};
},
mounted() {
this.getHeight();
},
methods: {
getHeight() {
const height = window.getComputedStyle(
document.querySelector(".past-events")
).height;
console.log(height);
return height + "px";
}
}
};
</script>

Nuxt uses server-sided rendering. This means that when your code is being executed on the server, it does not have something like window. After all, it is not a browser.
The easiest way around this is by wrapping anything that should not be pre-rendered to html, with something like vue-no-ssr. This particular library renders a dummy component on the server, then actually renders the component when it gets to the browser.

Yes window doesn't exist during the mounted lifecycle hook. I assume you're trying to place something based on its position?
In that case, you might be able to utilize CSS to do it for you. You can place elements using the View Height/View Width units. Combine that with CSS calc() and you might get the solution you need.
Example:
.element {
/* make element positon relative to the window */
position: fixed;
/* set position - note vw/vh are % of window */
/* this put the top of your element -200px from the bottom of your window */
top: calc(100vh - 200px);
}
If you're doing something more complex, using Javascript's element.getBoundingClientRect() will likely provide what you need. See this answer for more info.

Related

How to conditionally render multiple components based on route without affecting layout in vue3

I have a navbar and two side menus which I use in multiple pages. But for a number of pages I don't want to have the side menu.
I have tried using v-if with $router.name, but because I was using a grid system, the layout would break. Then also it was impossible to add more than one component to not render the side menus.
This is the template:
<template>
<TheNavbar/>
<div class="container">
<TheLeftMenu/>
<router-view/>
<TheRightMenu/>
</div>
</template>
My solution to avoid the grid system to squash the router-view component was to divide the template in two, one using the sidemenus, the other without them.
Then in order to have multiple pages to not load the side menus I used a computed property to hold an array with all the routes to not render the side menus:
<template>
<div v-if="blockedRoutes.includes($route.name) == false">
<TheNavbar/>
<div class="container">
<TheLeftMenu/>
<router-view/>
<TheRightMenu/>
</div>
</div>
<div v-else>
<TheNavbar/>
<router-view/>
</div>
</template>
computed: {
blockedRoutes () {
return ['Register', 'SignIn', 'About']
}
}
you can use the simple method of a flex container;
<template>
<router-view v-slot="{ Component, route }">
<div class="holder">
<div>
<TheLeftMenu/>
</div">
<div class="main">
<transition name="fade" mode="out-in"><component :is="Component" /></transition>
</div>
<div v-if="!route.meta.hideRmenu">
<TheRightMenu/>
</div>
</div>
</router-view>
</template>
<style>
.holder{
display : flex;
width : 100%;
/*if you want your container not to grow with the page do the following*/
overflow: hidden;
height : 100vh;
}
.holder .main{
flex : 1;
flex-direction: row;
/*if you want your container not to grow with the page do the following*/
max-height : 100vh;
overflow : auto;
}
.holder :not(.main){
min-width: 32px;
}
</style>
This way you can, if you want, also add transitions.
If you don't want to use transitions, you just need to remove the transitions tags.
This aproach uses is focused for Vue 3 and uses Scoped Slots

Why does my Vue transition have no effect?

I use vue with Axios to read an RSS feed of book titles and display them in a list. Users have a search box to filter the results in the list. This works fine.
However, the transition does not have any effect.
HTML index file
<ul class="booklist">
<my-book
v-for="book in bookFeed"
v-bind:id="book.id"
v-bind:title="book.node_title"
v-bind:body="book.body"
v-bind:path="book.path"
v-bind:author="book.author"
v-bind:coverimg="book.medium_img" >
</my-book>
</ul>
Vue js file
Vue.component('my-book', {
props: ['title','body','coverimg','id','path','author'],
template: `
<transition name='section'>
<li class="row-animated border-bo-dd mb10 pb10 imgshadow" >
...html content of a single book item
</li>
</transition>
`
});
CSS file
.section-leave-active,
.section-enter-active {
transition: .5s;
}
.section-enter {
transform: translateY(100%);
}
.section-leave-to {
transform: translateY(-100%);
}
Any idea where my mistake is? Thank you for any hint.
rhodes
Try to use transition-group component and place it instead of ul tag like :
<transition-group name="section" tag="ul" class="booklist">
<my-book
v-for="book in bookFeed"
v-bind:id="book.id"
v-bind:title="book.node_title"
v-bind:body="book.body"
v-bind:path="book.path"
v-bind:author="book.author"
v-bind:coverimg="book.medium_img" >
</my-book>
</transition-group>

How to fix my simple custom v-on directive that does not execute a method immediately?

I am here to ask some help about why is my custom directive not working properly. I am trying to create my own v-on (named v-myOn) directive that would just change the background color of the text when it is clicked. The problem is that the method is executed instantly when vue js application is started (meaning the element background has the color style already) and not when a certain event happened which is when the element is clicked. Thanks in advance!
Update: Problem as what Phil stated is I execute the function directly in the template. But in this case I am trying to pass an argument to the method changeColor. So how could i prevent it from executing while being able to pass arguments?
Here is my code in App.vue:
<template>
<div class="container">
<div class="row">
<div class="col-xs-12 col-sm-8 col-sm-offset-2 col-md-6 col-md-offset-3">
<h1 v-myOn:click="changeColor('blue')" :style="{background: color}" ref="heading">Directives Exercise</h1>
<!-- Exercise -->
<!-- Build a Custom Directive which works like v-on (Listen for Events) -->
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return{
color: ''
}
},
directives: {
myOn : {
bind(el, binding, vnode) {
el.addEventListener(binding.arg, binding.value)
}
}
},
methods: {
changeColor(color)
{
this.color = color;
}
}
}
</script>
<style>
</style>
You have to wrap in an arrow function otherwise it will get executed immediately as you have discovered and #Phil has pointed out.
<h1 v-myOn:click="() => changeColor('blue')" :style="{background: color}" ref="heading">Directives Exercise</h1>

Nesting a slot in a slot for vue

Update: Here's a simplified version of what I'm trying to achieve here (from the threaded conversation below):
Accept Component A - Accept Component B - Accept a condition - if
condition is true : wrap Component B with Component A [and render]- else only
render component B.
I'm interested in creating a component that renders a wrapper conditionally. I figured a theoretical approach like this would probably be best**:**
<template>
<div>
<slot v-if="wrapIf" name="wrapper">
<slot name="content"></slot>
</slot>
<slot v-else name="content"></slot>
</div>
</template>
<script>
export default {
props: {
wrapIf: Boolean,
}
}
</script>
Then when we implement, it would look something like this:
...
<wrapper-if :wrap-if="!!link">
<a :href="link" slot="wrapper"><slot></slot></a>
<template slot="content">
content
</template>
</wrapper-if>
The idea being that, in this case, if there is a link, then let's wrap the content with the wrapper slot (which can be any component/element). If there isn't, then let's just render the content without the wrapped link. Pretty simple logic, but it seems that I'm misunderstanding some basic vue functionality because this particular example does not work.
What is wrong with my code or is there some kind of native api that already achieves this or perhaps a dependency that does this sort of thing already?
The output should look like this:
wrapIf === true
<a href="some.link">
content
</a>
wrapIf === false
content
Just focus on the content itself, and let the component worry about whether or not to wrap the default or named content slot.
If you need the wrapper to be dynamic, a dynamic component should solve that. I've updated my solution accordingly. So if you need the wrapper to be a label element, just set the tag property to it, and so on and so forth.
const WrapperIf = Vue.extend({
template: `
<div>
<component :is="tag" v-if="wrapIf" class="wrapper">
<slot name="content"></slot>
</component>
<slot v-else name="content"></slot>
</div>
`,
props: ['wrapIf', 'tag']
});
new Vue({
el: '#app',
data() {
return {
link: 'https://stackoverflow.com/company',
tagList: ['p', 'label'],
tag: 'p',
wrap: true
}
},
components: {
WrapperIf
}
})
.wrapper {
display: block;
padding: 10px;
}
p.wrapper {
background-color: lightgray;
}
label.wrapper {
background-color: lavender;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<wrapper-if :wrap-if="wrap" :tag="tag">
<a :href="link" slot="content">
content
</a>
</wrapper-if>
<div>
Change wrapper type:
<select v-model="tag">
<option v-for="tag in tagList">{{tag}}</option>
</select>
</div>
<button #click="wrap = !wrap">Toggle wrapper</button>
</div>

Jump further down the page, in a separate component

Using Vue, I have a home page, that let's say consists of two components.
Introduction Component
List Component
I want to have a button in the Intro component, that jumps the user down the page to the List Component. Now, usually I would do something like -
CLICK HERE...
<div id="identifier">...TO JUMP HERE</div>
But, for some reason it is not working, and I believe it may be because I am technically jumping between two components, even though they are on the same page (the CLICK HERE... is on one, and the ...TO JUMP HERE is on the other)
Am I correct in that being the issue? and if so, is there a way to fix this?
In the example below, this works as expected.
Vue.component('intro', {
template: `
<section>
<h2>Intro component!</h2>
Goto list anchor
</section>
`
})
Vue.component('list', {
template: `
<section id="list">
<h2>List component</h2>
<h3>This is a long list:</h3>
<ul>
<li v-for="i in 500">list item {{i}}</li>
</ul>
</section>
`
})
new Vue({
el: "#app",
})
Vue.config.devtools = Vue.config.productionTip = false
h2 {
color: salmon;
background-color: lavender;
padding: 10px 0;
}
#list {
margin-top: 300px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<main id="app">
<intro></intro>
<list></list>
</main>