Trying to find a Lifecycle hook that worked after switching to a new page VUE - vue.js

I have page transition animation.
I want after transition find on new page element and change style.
My problem I first change the element and then go to a new page where this element is not changed.
Here link on video how its work now https://www.youtube.com/watch?v=VvmVxd-cmNM

I am not sure if I get this right but I'll try to answer anyway
I want after transition find on new page element and change style.
Vue <transition> has events for when you want to know if a transition is done.
You might be able to use #leave or #after-leave.
Docs: https://v2.vuejs.org/v2/guide/transitions.html#JavaScript-Hooks
My problem I first change the element and then go to a new page where this element is not changed.
You are structuring your app incorrectly.
I watched the video and it looks like both pages includes your Nav/Sidebar (the right part) thing in each page.
You are structuring your page like this:
PageA.vue
<div id="page-a">
<!-- this is your left side part -->
<div id="page-a-content"></div>
<!-- this is your right side part -->
<div id="sidebar"></div>
</div>
PageB.vue
<div id="page-b">
<!-- this is your left side part -->
<div id="page-b-content"></div>
<!-- this is your right side part -->
<div id="sidebar"></div>
</div>
App.vue
<div>
<!-- This shows PageA.vue OR PageB.vue -->
<router-view />
</div>
You only have the left-side part changing but not the right-side part.
You should structure it like this:
App.vue
<div>
<!-- This is your left side part. PageA.vue OR PageB.vue -->
<router-view />
<!-- This is your right side part -->
<Sidebar />
</div>
When you change your page, only the <router-view /> will change and not the <Sidebar />

Related

vue passing props to sub component will generate html attributes to wrapper root

So I have this component PageWrapper that has a title props.
<div id="page-wrapper">
<h1>{{title}}</h1>
<slot />
</div>
And I have a wrapper component that forwards most props to its child:
<PageWrapper v-bind="[$attrs]">
<slot name="default"/>
</PageWrapper>
It works as expected, except that my page wrapper root has an extra title html attribute that I don't want to be there.
<div id="page-wrapper" title="My Title">
<h1>My title</h1>
<!-- rest of the page -->
</div>
I have the feeling there is a confusion between html attributes and vue properties when forwarding to sub-component.
I thought this had to do with the difference between $props and $attrs but apparently not as it doesn't work in any case.
Can someone clarify this for me? How can I avoid this html-attributes behavior?
Cheers
you do not want to bind the attributes to the PageWrapper Component.
Instead just bind the title property.
<PageWrapper :title="title">
<slot name="default"/>
</PageWrapper>
EDIT:
Might also try to bind the attributes without the square brackets v-bind="$attrs"

How to properly create a popup component in Vue 3

As part of becoming a better Vue programmer, I am trying to implement a popup similar to Popper with a clean and Vueish architecture. Here is a simple schematic that I came up with:
So basically there is a target component, which is the reference for the popup's position. The popup can be positioned above, below, right and left of the target, therefore I will need to have access to the target element in my popup. Also, the target can be an arbitrary component. It can be a simple button or span, but also something much more complex.
Then there is the popup itself, which will be put into a modal at the end of the body, It contains the actual content. The content again can be an arbitrary component.
I have a working implementation of the popup, but the basic structure seems to be far from perfect. I am using two slots, one for the target element and one for the content.
Here is what I have come up with so far for the template:
<template>
<div ref="targetContainer">
<slot name="target"></slot>
</div>
<teleport to="body">
<div v-show="show" class="modal" ref="modal">
<div ref="popover" class="popover" :style="{top: popoverTop + 'px', left: popoverLeft + 'px'}">
<slot name="content"></slot>
</div>
</div>
</teleport>
</template>
There are several issues with this that I am not really happy with.
Using the popup is not very simple
When using this popup in another component, two <template> tags are rquired. This is ungly and not very intuitive. A very simple use case looks like this:
<modal :show="showPopup" #close="showPopup=false">
<template v-slot:target>
<button #click="showPopup=true"></button>
</template>
<template v-slot:content>
<div>Hello World!</div>
</template>
</modal>
The target is wrapped in another <div>
This is done to get access to the target element, that I need for the layout. In mounted() I am referencing the target element like this:
let targetElement = this.$refs.targetContainer.children[0];
Is this really the best way to do this? I would like to get rid of the wrapping <div> element, which just asks for unintended side effects.
The best solution would be to get rid of one slot and somehow reference the target element in another way because I only need its layout information, it does not have to be rendered inside the popover component.
Can someone point me in the right direction?
Here is my solution, which was inspired by a comment on my question and which I think is worth sharing.
Instead of putting the target element into a slot, I am now passing its ref as a prop, which makes things much cleaner.
The popover component's template now looks like this.
<template>
<teleport to="body">
<div v-show="show" class="modal" ref="modal">
<div ref="popover" class="popover" :style="{top: popoverTop + 'px', left: popoverLeft + 'px'}">
<slot ref="content"></slot>
</div>
</div>
</teleport>
</template>
I has a targetRefprop, so the component can be simply used like this:
<div ref="myTargetElement" #click="isPopupVisible=true">
</div>
<modal :show="isPopupVisible" #close="isPopupVisible=false" targetRef="myTargetElement">
<!-- popup content goes here -->
</modal>
And after mounting I can access the target element like this:
let targetElement = this.$parent.$refs[this.targetRef];
I like this solution a lot. However, ideas, advice or words of caution are still highly welcome.

Vue: toggling between two instances of the same component doesn't update the view

I have a setup in a Vue-powered UI, where the user can toggle the contents of a certain div between several options, and two of those options happen to be instances of the same child component (with different properties passed in).
Everything works fine when displaying any given content page for the first time, or when toggling between two unrelated content pages. However when toggling between the two pages which both use the same child component, the div content doesn't get updated.
In code it looks (greatly simplified) like this:
Parent component
<template>
<div>
<!-- toggle buttons -->
<div class="page-button" #click="page=1">About</div>
<div class="page-button" #click="page=2">Dog List</div>
<div class="page-button" #click="page=3">Cat List</div>
<!-- page content -->
<div v-if="page===1">some plaintext here...</div>
<div v-if="page===2">
<childComponent :state="state" listName="dogs" />
</div>
<div v-if="page===3">
<childComponent :state="state" listName="cats" />
</div>
<!-- rest of file omitted -->
childComponent.vue
<template>
<div>
<template v-for="(item, index) in items">
<div>{{ index }}: {{ item.label }}</div>
<!-- etc.. -->
</template>
</div>
</template>
<script>
module.exports = {
props: ['state', 'listName'],
data: function () {
return {
items: this.state.lists[this.listName],
}
},
}
</script>
In the above, state is a global state object that all components have access to, with state.lists.dogs and state.lists.cats being regular arrays.
When the UI initializes with page set to 2 or 3, everything works correctly - the dog list shows for page 2, and the cat list shows for page 3. Likewise, when I click page 2, then page 1, then page 3, everything is fine. However when toggling back and forth between page 2/3, the vue doesn't re-render the child component.
I assume it's possible to work around this by changing the underlying data structure or by binding the child component differently. But is there a straightforward way to make Vue re-render the component as expected?
I guess what you see is Vue trying to optimize rendering by reusing existing component instance. Add key attribute on your childComponent with different values...
<!-- page content -->
<div v-if="page===1">some plaintext here...</div>
<div v-if="page===2">
<childComponent :state="state" listName="dogs" key="dogs" />
</div>
<div v-if="page===3">
<childComponent :state="state" listName="cats" key="cats" />
</div>
<!-- rest of file omitted -->
Other solution (and much better IMHO) is to make your component "reactive" to prop changes - instead of using props to initialize the data() (which is "one time" thing - data() is executed only once when component is created), use computed
module.exports = {
props: ['state', 'listName'],
computed: {
items() {
return this.state.lists[this.listName]
}
},
}
You can use v-show if you just want to render it before hand. Its more costly but it should work without any issues.
<template>
<div>
<!-- toggle buttons -->
<div class="page-button" #click="page=1">About</div>
<div class="page-button" #click="page=2">Dog List</div>
<div class="page-button" #click="page=3">Cat List</div>
<!-- page content -->
<div v-show="page===1">some plaintext here...</div>
<div v-show="page===2">
<childComponent :state="state" listName="dogs" />
</div>
<div v-show="page===3">
<childComponent :state="state" listName="cats" />
</div>
<!-- rest of file omitted -->

vue.js Mount component to app root

I have a modal.vue component as follows:
<template>
<transition name="modal-transition">
<div class="modal-body" v-if="displayed">
<div class="modal-overlay" #click="displayed = false"></div>
<div class="modal-content">
<slot/>
</div>
</div>
</transition>
</template>
How do I mount this component to the applications root element rather than in place?
For crude inaccurate example:
<body>
<div id="app">
<div class="header"></div>
<div class="nav"></div>
<div class="stage">
<div class="sub-nav"></div>
<div class="content">
<modal :display.sync="display">MY MODAL</modal> <-- Don't mount here...
</div>
</div>
<-- Mount here instead...
</div>
</body>
The current issue is that my sites header and navigation is layered on top of my modal and it's darkened full screen overlay instead of layered behind the modal overlay.
Update for Vue 3
There is now a built in feature called teleport which allows mounting parts of your component template to any DOM element.
The example from the OP would look like something like this
<!-- MyModal.vue -->
<template>
<transition name="modal-transition">
<div class="modal-body" v-if="displayed">
<div class="modal-overlay" #click="displayed = false"></div>
<div class="modal-content">
<slot/>
</div>
</div>
</transition>
</template>
<!-- SomeDeeplyNestedComponent.vue -->
<template>
<teleport to="#app">
<!-- Can still receive props from parent -->
<MyModal :my-prop="foo">
<!-- slot content -->
</MyModal>
</teleport>
</template>
Vue 2
Move the elements own self to the element of applications root may be achieved in two ways, Using a portal as a preferred solution or using an append.
Using a Portal (Preferred Method)
PortalVue is a set of two components that allow you to render a
component's template (or a part of it) anywhere in the document - even
outside the part controlled by your Vue App!
https://portal-vue.linusb.org/
Using an Append (Not best practice)
If adding a portal library is too heavy, using an append is allowed but lightly discouraged officially in the VUE docs.
Typically this particular mount position will satisfy a z-index overlay for your own modal or dialog popup that you require to render over the top of the entire app. You can always substitute this.$root.$el in this example for a different element target using standard getElementBy or querySelector functions.
Here the element is being moved not destroyed and re-added, all reactive functionality will remain in tact.
<script>
export default {
name: 'modal',
...
mounted: function() {
this.$root.$el.append(this.$el);
},
destroyed: function() {
this.$el.parentNode.removeChild(this.$el);
}
}
</script>
On mounted the element is moved inside of where the top level VUE app instance is mounted.
On destroyed removes the placeholder DOM comment for the migrated component from the new parent to prevent orphaned duplication each time the component remounts it's self.
VUE officially states not to destroy an element outside of VUE so this is not to be confused with that statement, here the component has already been destroyed.
This DOM comment duplication will typically happen when for example switching views with vue-router as this mechanism mounts and dismounts all components in a router view each time vue-router view state changes.
This behaviour is a bug cause by vue-router, the object is destroyed properly by VUE render manager but an index reference remains by mistake, using a portal package resolves this issue.
Here is the result:

Creating first view in Vue.js

I'm creating first view in Vuejs like this:
<template>
<p>Welcome to MyWebsite</p>
<p>These terms and conditions outline the rules and regulations for the use of MySite Website.</p> <br />
<span style="text-transform: capitalize;"> MySite</span> is located at:<br />
<address>Adresss , City<br />State - 00000, USA<br />
</address>
</template>
But I always get
Welcome to MyWebsite These terms and conditions outline
the rules and regulations for the use of MySite Website.
MySite is located
at: Adresss , CityState - 00000, USA
Component template should contain exactly one root element. If you are using v-if on multiple elements, use v-else-if to chain them
instead.
why it recommend me to use v-if? I don't understand what should I do there? Regards
Component template should contain exactly one root element.
<template>
<div><!-- ONLY ONE DIRECT CHILD IN THE TEMPLATE -->
<p>Welcome to MyWebsite</p>
<p>These terms and conditions outline the rules and regulations for the use of MySite Website.</p> <br />
<span style="text-transform: capitalize;"> MySite</span> is located at:<br />
<address>Adresss , City<br />State - 00000, USA<br /></address>
</div>
</template>
For similar reasons to why React can only render a single root node Vue.js requires the first thing in the <template> to be the opening of a containing element inside of which the rest of the template will be written. Vue.js is guessing this is what you want, but it's better to make it explicit. Thus, to fix it:
<template>
<div>
<p>Welcome to MyWebsite</p>
<p>These terms and conditions outline the rules and regulations for the use of MySite Website.</p> <br />
<span style="text-transform: capitalize;"> MySite</span> is located at:<br />
<address>Adresss , City<br />State - 00000, USA<br />
</address>
</div>
</template>
The root element refers to your components root div-like element which is <template></template>
So, vue is telling you that below these root components you should only have one element: which means,
<template>
<div>
// put everything here
</div>
</template>
As stated above, you need to encapsulate the content inside a unique block (div, section...).
<template>
<section>
....
</section>
</template>