Vuetify loader component until page fully loaded - vue.js

Is there a way to display a Vuetify loader component until all the elements are loaded? If not it displays for a moment the {{ and }} and is not very aesthetic.
I have a big page and a lot of items, applying v-cloak on each item don't consider to be cool either, I prefer to display a loader.

The Vuetify overlay component is not a bad choice in this kind of instance. You can chose an opaque overlay with an indeterminate loader that you then hide when everything has loaded:
<template>
<div>
<div id="the-content">
This is the stuff that gets hidden until it's loaded...
</div>
<v-overlay
:opacity="1"
:value="overlay"
>
<v-progress-circular indeterminate size="64">
Loading...
</v-progress-circular>
</v-overlay>
</div>
</template>
<script>
export default {
data: () => ({
overlay: true,
}),
mounted() {
// hide the overlay when everything has loaded
// you could choose some other event, e.g. if you're loading
// data asynchronously, you could wait until that process returns
this.overlay = false
},
}
</script>
Note: in this example, you're not likely to see the loading overlay since the content on this page takes such a short time to actually load.

Related

How to understand the mounted() function in Vue.Js with v-for

I try to understand how the mounted hook works on Vue.js. If I understand correctly, the mounted function is called when all DOM elements are rendered. So in this scenario, I will have access to the DOM elements with document.getElementById or this.$el.somenthing.
Is this right?
But why I get the following behavior? Lets say I have the following structure:
mounted() {
this.buildTextTargetWidth();
},
methods: {
buildTextTargetWidth() {
console.log(document.getElementsByClassName('typographyTextPreview')); // Empty HTML-Collection. But, curiously, when I open the array in the console, there is the right element.
console.log(document.getElementsByClassName('typographyTextPreview')[0]); // Undefined
},
}
So far so good. The result is undefined. But for my understanding it should not!
Lets try to add some async stuff to wait 500ms:
mounted() {
this.buildTextTargetWidth();
},
methods: {
async buildTextTargetWidth() {
await new Promise(r => setTimeout(r, 500));
console.log(document.getElementById('test')); // The element is there
},
}
Now the element is there!
But why I have to wait other 500ms to get the DOM data if the mounted() is calling after the DOM is ready? It makes no sense for me.
By the way: The element typographyTextPreview which I try to call is embedded inside a v-for. Could this be the reason for this behavior? If yes, how could I wait until the v-for is ready?
--EDIT--
I wanted to create this topic as clean and simple as possible. But here I add additional data from the component, for example the entire for loop where the element typographyTextPreview is included:
<div ref="builderLayer" class="vconf-create-builder column" :class="getColumnLength">
<div class="vconf-create-builder-layers" :key="layersKey">
<div v-for="layer of getLayers" class="vconf-create-builder-layer" :key="layer.uid" :style="{ background: 'url(' + getLayerImage(layer) + ') center / contain no-repeat' }">
<template v-for="tile of layer.data">
<!-- <VueDragResize :key="tile.uid" v-if="tile.type=='typography'" :parentLimitation="true" :isActive="true" :x="calculatePositionX(tile)" :y="calculatePositionY(tile)" :w="tile.editBoxWidth" :h="tile.editBoxHeight" #resizestop="updateTypoBoxSize($event, layer, tile)" #dragstop="updateTypoBoxPosition($event, layer, tile)">
<h2 class="typographyTextPreview" :style="{'font-size':calculateFontSize(tile) + 'px', color: tile.fontColor}">{{tile.text ? tile.text : "Please enter an initial text in the settings"}}</h2>
</VueDragResize> -->
<div v-if="tile.type=='typography'" class="container" :key="tile.uid">
<div class="target" :style="buildTextTargetStyle(tile)">
<h2 id="test" class="typographyTextPreview" :style="{color: tile.fontColor}">{{tile.text ? tile.text : "Please enter an initial text in the settings"}}</h2>
</div>
<Moveable
className="moveable"
:target="['.target']"
:keepRatio="true"
:throttleResize="1"
:bounds=' {"left":0,"top":0, "right": clientWidth, "bottom": clientHeight}'
:origin="true"
:snappable="true"
:zoom="1"
:draggable="true"
:scalable="true"
:rotatable="true"
#drag="onDrag($event, tile)"
#scale="onScale($event, tile)"
#rotate="onRotate($event, tile)"
/>
</div>
</template>
</div>
</div>
</div>
From the docs:
A component is considered mounted after:
All of its synchronous child components have been mounted (does not include async components or components inside trees).
Its own DOM tree has been created and inserted into the parent container. Note it only guarantees that the component's DOM tree is in-document if the application's root container is also in-document.
If you need to wait for DOM changes, I suggest you to use the nextTick utility, which waits for the DOM to be updated. More details here

Vuejs - Reference to HTMLElement becomes stale after using it in slot

I'm trying to use Vue on an existing page where there are some controls outside of my control. I wanted to wrap the controls with a Vue component, but all references to those controls are stale after Vue is initialized.
See the following codesandbox for repro: https://codesandbox.io/s/heuristic-fast-405up
The component wrapping the controls is super simply:
Component:
<div id="app">
<slot />
</div>
Page:
<div id="app">
<test-container>
<div id="some-element">Initial Text</div>
</test-container>
</div>
var element = document.getElementById("some-element");
new Vue({
components: {
TestContainer: App
}
}).$mount("#app");
console.log(element);
setTimeout(() => {
console.log("updating element...");
element.innerText = "Updated Text";
console.log(element.innerText);
console.log(document.getElementById("some-element").innerText);
}, 1000);
This will print:
updating element...
Updated Text
Initial Text // should be Updated Text
Is there a way to not break existing references to those controls when rendered in a slot? I want to keep rendering the existing controls, but wrap them with a Stepper component for example.

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.

Nuxt/Vue: How to position custom loading component

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

Custom Vue directive to omit tag but render tag's contents?

I'd like to create a custom Vue directive to omit the tag but render the tag's contents when the directive is true.
So for example, if the data for my vue instance is defined as
data:{
omitIt: true
}
And if the markup looks like this:
<div v-omit="omitIt" class="someClass">
Hello world!
</div>
When omitIt is set to false as it is above, I'd like the following rendered into the dom:
<div class="someClass">
Hello world!
</div>
But when omitIt is true I'd like only the following rendered into the dom:
Hello world!
I initially tried to solve this by doing the following (admittedly not a custom vue directive):
<template v-if="!omitIt">
<div class="someClass">
</template>
Hello world!
<template v-if="!omitIt">
</div>
</template>
The above isn't pretty but I thought perhaps it would work. But alas what gets rendered into the dom when omitIt is false is:
<div class="someClass"></div>
Hello world!
Any suggestions on how to achieve the results I'm looking for?
I thought #Nit's answer was a great and simple one and upvoted it, but it does have one flaw: a slot may not be a root element so the component will fail when the wrapper needs to be omitted. This is because slots can contain more than one element and if the slot does contain more than one, there could end up being more than one root element, which is not allowed.
I have a partial solution that renders just the first element in the slot if the component should not wrap.
Vue.component("wrapper", {
props:{
nowrap: {type: Boolean, default: false}
},
render(h){
// This will *only* render the *first* element contained in
// the default slot if `nowrap` is set. This is because a component
// *must* have a single root element
if (this.nowrap) return this.$slots.default[0]
// Otherwise, wrap the contents in a DIV and render the contents
return h('div', this.$slots.default)
}
})
Here is an example of it working.
console.clear()
Vue.component("wrapper", {
props:{
nowrap: {type: Boolean, default: false}
},
render(h){
// Log a warning if content is being omitted
const omissionMessage = "Wrapper component contains more than one root node with nowrap specified. Only the first node will be rendered."
if (this.$slots.default.length > 1 && this.nowrap)
console.warn(omissionMessage)
// This will *only* render the *first* element contained in
// the default slot if `nowrap` is set. This is because a component
// *must* have a single root element
if (this.nowrap) return this.$slots.default[0]
// Otherwise, wrap the contents in a DIV and render the contents
return h('div', this.$slots.default)
}
})
new Vue({
el: "#app"
})
.someClass{
color: blue
}
<script src="https://unpkg.com/vue#2.4.2"></script>
<div id="app">
<wrapper class="someClass">Hello World</wrapper>
<wrapper nowrap>No wrap, single root</wrapper> <br>
<wrapper nowrap>
No wrap, two roots. Paragraph is ommitted.
<p>Some other content</p>
</wrapper>
</div>
A couple notes: The component will always wrap unless you add nowrap as an attribute. Also, notice the class is added to the wrapped container without specifying it as a prop. This is because Vue automatically renders attributes that aren't specified as props on the root element of a component, unless you tell it not to.
This answer is wrong, slots cannot be used in this manner. Please see Bert's answer instead.
The easiest solution would be to create a wrapper component with slots for this purpose, passing the omitting argument as a prop.
The content distribution part becomes rather straightforward.
In the wrapper component template:
<slot v-if="omitIt"></slot>
<div v-else>
<slot></slot>
</div>
Wherever you want to use the wrapper:
<wrapper v-bind:omitIt="omitIt">
// Content
</wrapper>