Nuxt/Vue: How to position custom loading component - vue.js

I use nuxt and I followed this guide to make custom loading component:
https://nuxtjs.org/api/configuration-loading/#use-a-custom-loading-component
This does work, but the loader is in the same position as the original nuxt loader bar:
You can see, I really added a very big and very simple loader with a red div.
At the bottom you can see my headebar (black bar with letter «s»)
so everything is moved downwards.
What I would like to achieve is that the loader takes the position of the page content instead to keep the header and the footer in place.
Right now, it all shifts down to make space for the custom loader on top.
Is there a solution for that?
Thanks in advance
Cheers
J

Well I built a big workaround:
I set a loading component in my nuxt.config:
loading: '~/components/Loading/Loading.vue',
This component should never display anything, but has these two methods:
methods: {
start () {
this.$store.commit('ui/SHOW_LOADER')
},
finish () {
this.$store.commit('ui/HIDE_LOADER')
}
}
With those I mutate the vuex ui store.
export const mutations = {
SHOW_LOADER (state) {
state.nuxtLoader = true
},
HIDE_LOADER (state) {
state.nuxtLoader = false
}
}
nuxtLoader: false, <=> nuxtLoader: true,
Within this store I set a getter:
export const getters = {
nuxtLoader: state => state.nuxtLoader
}
And on my layouts I display <nuxt /> or the CustomLoader component (which holds an animated SVG) depending on the ui store nuxtLoader property.
<template>
<div>
<HeaderBar />
<main id="main" class="Layout__content">
<CustomLoader v-if="nuxtLoader" />
<nuxt v-else />
</main>
<Footer />
</div>
</template>
Now I give the user a feedback with a custom loader placed between headerbar and footer. 💪
I am still open for less work-aroundy and slicker solutions.
Cheers
J

Related

How can I can show a div if an object doesn't have a value outside of the object scope

I have a small Nuxt issue that I can't work out how to get around.
Essentially, I have an object (used for a carousel slider).
<template>
<div
:class="[$style.swiperSlide, 'swiper-slide']"
v-for="slide in slides"
:key="slide.id">
<nuxt-img
:class="[$style.img]"
:alt="slide.alt"
:src="imgSources(slide)"
sizes="sm:100vw"
/>
<div :class="[$style.info, 'info-b']" v-if="slide.info">
{{ slide.info }}
</div>
</div>
<button :class="[$style.infoOpen]"
#click="showTab"
v-if="slideInfoAvailable"
>
Close
</button>
</template>
<script>
export default {
props: {
slides: {
type: Array,
required: true,
default: () => []
}
},
computed: {
slideInfoAvailable() {
return this.slide?.info
}
},
mounted() {
const swiper = new Swiper(".swiper-container", {
. . .
});
},
methods: {
imgSources(slide) {
return `/img${slide.imgPath}.jpg`;
},
};
</script>
All works o.k, the problem is that I have a button outside of this v-for that I need to only be visible if there's slide.info but because this div is outside of the v-for it can't tell if it's available.
Cannot read property 'info' of undefined
The easiest way out of this is to add the button inside of the slider - but I can't for Z-index CSS issues. It has to be outside of the 'slider' div.
Any ideas how I can only show the button if there's slide.info? For some of my slides, there won't be.
<slider
:slides="[
{
imgPath: '/demo',
info: 'Demo info for this slide',
alt: 'Homepage'
},
{
imgPath: '/demo2',
alt: 'Homepage'
},
]"
/>
One way I could do it would be to see if .slide-active .style.info exists. If it doesn't exist then I can hide the button as slide-active is added to the active div by the slider API.
The issue is coming from the fact that you probably have some async fetching and that slides are not available upon initial render. To prevent this, you can use a computed with some optional chaining like this
export default {
computed: {
slideInfoAvailable() {
return this.slide?.info
}
}
}
Then, call it like this
<button :class="[$style.infoOpen]" #click="showTab" v-if="slideInfoAvailable">
You cannot use ?. directly in the template.
You could also do the classic way of
<button :class="[$style.infoOpen]" #click="showTab" v-if="slide && slide.info">
but it does not look as sexy IMO (but you do not need any computed).
And yeah, for this kind of thing, better to handle it with Vue than relying on some hacky dirty CSS tricks!

How to listen for a page $emit in a nuxt layout?

Nuxt 2.15.6; I want to switch the layout of my menu component by dynamically switching menu components in my root layout.
default.vue
<template>
<component :is="navLayout"></component>
<Nuxt :navLayout="navLayout = $event" />
</template>
data() {
return {
navLayout: "default"
};
},
In the "child" components of , my pages eg. login.vue (/login) I $emit an event;
...
import nav2 from "#/layouts/nav2";
...
created() {
this.$emit("navLayout", nav2);
},
Now it seems to be the <Nuxt> component is not able to catch the event. I also tried calling a <Nuxt #navLayout="test()" /> method.
How can I avoid this.$root.$emit(...); in my login.vue and
this.$root.$on("navLayout", navLayout => {
this.navLayout = navLayout;
});
in default.vue?
EDIT: This answer works fine as it looks like you cannot do it right now: https://github.com/nuxt/nuxt.js/issues/8122#issuecomment-709443008
In the child component
<button #click="$nuxt.$emit('eventName', 'nice payload')">nice</button>
On the default layout
<script>
export default {
created() {
this.$nuxt.$on('eventName', ($event) => this.test($event))
},
methods: {
test(e) {
console.log('test ok >>', e)
},
},
}
</script>
Putting a listener on Nuxt itself does not work.
<Nuxt #navLayout="navLayout = $event" :navLayout="navLayout" />
I can see the event go and the listener plugged to <nuxt></nuxt> but it does not trigger any method with the listener...
PS: works for <nuxt-child></nuxt-child> at least.
Maybe I don't understand your question correctly, but it seems to me that you are trying to do yourself what layouts are meant to do. This would mean that you would create a layout with the menu component for login, a default layout etc. like this:
login.vue
<template>
<LoginMenu>
<Nuxt/>
</template>
default.vue
<template>
<DefaultMenu>
<Nuxt/>
</template>
On your page you would do:
export default {
layout: login
}
And then that would load the layout with the login menu component. On all other pages it would load the default menu.
More info here: https://nuxtjs.org/examples/layouts/

How can i just have one Component instead of different components in the context of Vuejs

I am new to Vuejs, I am using 4 different components, where in every component i have called the API. Here 4 different components are all same except their contents.
Now, i want to make my code effective, so i want to have a single component, the reason why i created different components is my another App.vue component has 4 different buttons, so whenever you click any of it, it will open the respective component.
But now i want to have only one component instead of four different components, and whenever the buttons in App.vue component is clicked it should open the exact content in single component(instead of 4 components).
Please do help me with this, by sharing your ideas and if any examples.
In this context, you can use props, which is a way of passing data to "child" components.
https://v2.vuejs.org/v2/guide/components-props.html
Example
App.vue
<template>
<div id="app">
<SingleComponent :button="button" />
</div>
</template>
<script>
import SingleComponent from "#/components/SingleComponent.vue";
// #/ means src/
export default {
name: "App",
components: {
SingleComponent,
},
data: () => ({
button: 2,
}),
};
</script>
SingleComponent.vue
<template>
<div>
<button v-if="button === 0">...</button>
<button v-else-if="button === 1">...</button>
<button v-else-if="button === 2">...</button>
<button v-else-if="button === 3">...</button>
</div>
</template>
<script>
export default {
name: "SingleComponent",
props: {
button: {
type: Number,
required: true,
},
},
};
</script>
You should also take a look at slots, it is very important in Vue.js and that could also solve your problem.
https://v2.vuejs.org/v2/guide/components-slots.html

Vue- best practice for loops and event handlers

I am curious if it is better to include methods within loops instead of using v-if. Assume the following codes work (they are incomplete and do not)
EX: Method
<template >
<div>
<div v-for="(d, i) in data" v-bind:key="i">
<span v-on:click="insertPrompt($event)">
{{ d }}
</span>
</div>
</div>
</template>
<script>
export default {
data() {
data:[
.....
]
},
methods:{
insertPrompt(e){
body.insertBefore(PROMPT)
}
}
}
</script>
The DOM would be updated via the insertPrompt() function which is just for display
EX: V-IF
//Parent
<template >
<div>
<div v-for="(d, i) in data" v-bind:key="i">
<child v-bind:data="d"/>
</div>
</div>
</template>
<script>
import child from './child'
export default {
components:{
child
},
data() {
data:[
.....
]
},
}
</script>
//Child
<template>
<div>
<span v-on:click="display != display">
{{ d }}
</span>
<PROMPT v-if="display"/>
</div>
</template>
<script>
import child from './child'
export default {
components:{
child
},
data(){
return {
display:false
}
},
props: {
data:{
.....
}
},
}
</script>
The PROMPT is a basic template that is rendered with the data from the loop data click.
Both methods can accomplish the same end result. My initial thought is having additional conditions within a loop would negatively impact performance?!?!
Any guidance is greatly appreciated
Unless you are rendering really huge amounts of items in your loops (and most of the times you don't), you don't need to worry about performance at all. Any differences will be so small nobody will ever notice / benefit from having it a tiny touch faster.
The second point I want to make is that doing your own DOM manipulations is often not the best idea: Why do modern JavaScript Frameworks discourage direct interaction with the DOM
So I would in any case stick with the v-if for conditionally rendering things. If you want to care about performance / speed here, you might consider what exactly is the way your app will be used and decide between v-if and v-show. Citing the official documentation:
v-if is “real” conditional rendering because it ensures that event
listeners and child components inside the conditional block are
properly destroyed and re-created during toggles.
v-if is also lazy: if the condition is false on initial render, it
will not do anything - the conditional block won’t be rendered until
the condition becomes true for the first time.
In comparison, v-show is much simpler - the element is always rendered
regardless of initial condition, with CSS-based toggling.
Generally speaking, v-if has higher toggle costs while v-show has
higher initial render costs. So prefer v-show if you need to toggle
something very often, and prefer v-if if the condition is unlikely to
change at runtime.
https://v2.vuejs.org/v2/guide/conditional.html#v-if-vs-v-show
There are numerous solutions to solving this issue, but let's stick to 3. Options 2 and 3 are better practices, but option 1 works and Vue was designed for this approach even if hardcore developers might frown, but stick yoru comfort level.
Option 1: DOM Manipulation
Your data from a click, async, prop sets a condition for v-if or v-show and your component is shown. Note v-if removes the DOM element where v-show hides the visibility but the element is still in the flow. If you remove the element and add its a complete new init, which sometimes works in your favor when it come to reactivity, but in practice try not to manipulate the DOM as that will always be more expensive then loops, filters, maps, etc.
<template >
<div>
<div v-for="(d, i) in getData"
:key="i">
<div v-if="d.active">
<child-one></child-one>
</div>
<div v-else-if="d.active">
<child-two></child-two>
</div>
</div>
</div>
</template>
<script>
import ChildOne from "./ChildOne";
import ChildTwo from "./ChildTwo";
export default {
components: {
ChildOne,
ChildTwo
},
data() {
return {
data: [],
}
},
computed: {
getData() {
return this.data;
},
},
mounted() {
// assume thsi woudl come from async but for now ..
this.data = [
{
id: 1,
comp: 'ChildOne',
active: false
},
{
id: 2,
comp: 'ChildTwo',
active: true
},
];
}
}
</script>
Option 2: Vue's <component> component
Always best to use Vue built in component Vue’s element with the is special attribute: <component v-bind:is="currentTabComponent"></component>
In this example we pass a slug or some data attribute to activate the component. Note we have to load the components ahead of time with the components: {}, property for this to work i.e. it has to be ChildOne or ChildTwo as slug string. This is often used with tabs and views to manage and maintain states.
The advantage of this approach is if you have 3 form tabs and you enter data on one and jump to the next and then back the state / data is maintained, unlike v-if where everything will be rerendered / lost.
Vue
<template >
<div>
<component :is="comp"/>
</div>
</template>
<script>
import ChildOne from "./ChildOne";
import ChildTwo from "./ChildTwo";
export default {
components: {
ChildOne,
ChildTwo
},
props: ['slug'],
data() {
return {
comp: 'ChildOne',
}
},
methods: {
setComponent () {
// assume prop slug passed from component or router is one of the components e.g. 'ChildOne'
this.comp = this.slug;
}
},
mounted() {
this.nextTick(this.setModule())
}
}
</script>
Option 3: Vue & Webpack Async and Dynamic components.
When it comes to larger applications or if you use Vuex and Vue Route where you have dynamic and large number of components then there are a number of approaches, but I'll stick to one. Similar to option 2, we are using the component element, but we are using WebPack to find all Vue files recursively with the keyword 'module'. We then load these dynamically / asynchronous --- meaning they will only be loaded when needed and you can see this in action in network console of browser. This means I can build components dynamically (factory pattern) and render them as needed. Example, of this might be if a user adds projects and you have to build and config views dynamically for projects created e.g. using vue router you passed it a ID for a new project, then you would need to dynamically load an existing component or build and load a factory built one.
Note: I'll use v-if on a component element if I have many components and I'm unsure the user will need them. I don't want to maintain state on large collections of components because I will end up memory and with loads of observers / watches / animations will most likely end up with CPU issues
<template >
<div>
<component :is="module" v-if="module"/>
</div>
</template>
<script>
const requireContext = require.context('./', true, /\.module\.vue$/);
const modules = requireContext.keys()
.map(file =>
[file.replace(/(.*\/(.+?)\/)|(\.module.vue$)/g, ''), requireContext(file)]
)
.reduce((components, [name, component]) => {
// console.error("components", components)
components[name] = component.default || component
return components
}, {});
export default {
data() {
return {
module: [],
}
},
props: {
slug: {
type: String,
required: true
}
},
computed: {
getData() {
return this.data;
},
},
methods: {
setModule () {
let module = this.slug;
if (!module || !modules[module]) {
module = this.defaultLayout
}
this.module = modules[module]
}
},
mounted() {
this.nextTick(this.setModule())
}
}
</script>
My initial thought is having additional conditions within a loop would negatively impact performance?
I think you might be confused by this rule in the style guide that says:
Never use v-if on the same element as v-for.
It's only a style issue if you use v-if and v-for on the same element. For example:
<div v-for="user in users" v-if="user.isActive">
But it's not a problem if you use v-if in a "child" element of a v-for. For example:
<div v-for="user in users">
<div v-if="user.isActive">
Using v-if wouldn't have a more negative performance impact than a method. And I'm assuming you would have to do some conditional checks inside your method as well. Remember that even calling a method has some (very small) performance impact.
Once you use Vue, I think it's a good idea not to mix it up with JavaScript DOM methods (like insertBefore). Vue maintains a virtual DOM which helps it to figure out how best to update the DOM when your component data changes. By using JavaScript DOM methods, you won't be taking advantage of Vue's virtual DOM anymore.
By sticking to Vue syntax you also make your code more understandable and probably more maintainable other developers who might read or contribute to your code later on.

Lazy loading a specific component in Vue.js

I just make it quick:
In normal loading of a component (for example "Picker" component from emoji-mart-vue package) this syntax should be used:
import {Picker} from "./emoji-mart-vue";
Vue.component("picker", Picker);
And it works just fine.
But when I try to lazy load this component I'm not sure exactly what code to write. Note that the following syntax which is written in the documentation doesn't work in this case as expected:
let Picker = ()=>import("./emoji-mart-vue");
The problem, I'm assuming, is that you're using
let Picker = ()=>import("./emoji-mart-vue");
Vue.component("picker", Picker);
to be clear, you're defining the component directly before the promise is resolved, so the component is assigned a promise, rather than a resolved component.
The solution is not clear and depends on "what are you trying to accomplish"
One possible solution:
import("./emoji-mart-vue")
.then(Picker=> {
Vue.component("picker", Picker);
// other vue stuff
});
This will (block) wait until the component is loaded before loading rest of the application. IMHO, this defeats the purpose of code-spliting, since the application overall load time is likely worse.
Another option
is to load it on the component that needs it.
so you could put this into the .vue sfc that uses it:
export default {
components: {
Picker: () => import("./emoji-mart-vue")
}
};
But this would make it so that all components that use it need to have this added, however, this may have benefits in code-splitting, since it will load only when needed the 1st time, so if user lands on a route that doesn't require it, the load time will be faster.
A witty way to solve it
can be done by using a placeholder component while the other one loads
const Picker= () => ({
component: import("./emoji-mart-vue"),
loading: SomeLoadingComponent
});
Vue.component("picker", Picker);
or if you don't want to load another component (SomeLoadingComponent), you can pass a template like this
const Picker= () => ({
component: import("./emoji-mart-vue"),
loading: {template:`<h1>LOADING</h1>`},
});
Vue.component("picker", Picker);
In PluginPicker.vue you do this:
<template>
<picker />
</template>
<script>
import { Picker } from "./emoji-mart-vue";
export default {
components: { Picker }
}
</script>
And in comp where you like to lazy load do this:
The component will not be loaded until it is required in the DOM, which is as soon as the v-if value changes to true.
<template>
<div>
<plugin-picker v-if="compLoaded" />
</div>
</template>
<script>
const PluginPicker = () => import('./PluginPicker.vue')
export default {
data() = { return { compLoaded: false }}
components: { PluginPicker }
}
// Another syntax
export default {
components: {
PluginPicker: () => import('./PluginPicker.vue')
}
}
</script>