Vuejs load component via v-if and use v-show afterwards - vuejs2

I want to load a component for the first time via v-if condition but once its rendered I want to toggle its visiblity via v-show, is there any preferred way of achieving it?

How about emitting an event from the component when it's first created / mounted and listening for that to toggle the v-show.
For example
<my-component v-if="componentLoad" v-show="componentLoaded && componentToggle"
#load="componentLoaded = true" />
data: {
componentLoad: false,
componentLoaded: false,
componentToggle: true,
}
and in the component
mounted () {
this.$emit('load')
}
Then you just need to flip componentLoad to initially create and mount the component and then alter componentToggle to show / hide it.

Related

How to open and close a modal within a child component using a prop, vuejs?

I currently have the following child component
<Edit
#fetchInfo="fetchInfo"
:agencyData="agency"
:dialogEdit.sync="dialogEdit"
></Edit>
Which basically contains a modal
initially it is false, so as not to show the modal:
data(){
return {
dialogEdit: false
}
}
by a method I do:
open(){
this.dialogEdit = true;
}
In my <Edit></Edit> component I have:
<el-dialog title="Editar" :visible.sync="dialogEdit" width="60%">
</el-dialog>
and received with prop:
props: ["dialogEdit"],
But then when closing the modal from the child component I receive an error
[Vue warn]: Avoid mutating a prop directly since the value will be
overwritten whenever the parent component re-renders. Instead, use a
data or computed property based on the prop's value. Prop being
mutated: "dialogEdit"
First of all, apparently you are using Element UI.
But, as the error suggests, you are modifying dialogEdit directly. When closing the element ui modal by clicking the X, dialogEdit becomes false. You could solve this using computed property, like suggested for example in this answer.
Since you are using Element UI you also have another possibility to solve this. The dialog has the event before-close which is fired before the modal is closed. There you can emit the new boolean value for dialogEdit to the parent. So keep the :dialogEdit.sync="dialogEdit" in child tag and add the before-close to the dialog and a function to handle, where you emit the new false value:
<el-dialog title="Editar" :before-close="handleClose" ....>
JS:
methods: {
handleClose() {
this.$emit('update:dialogEdit', false);
}
},
If you have some button in your modal to close the modal, you can add the same function there:
<el-button type="primary" #click="handleClose">Close</el-button>
SANDBOX
quite hard to understand your question. you should elaborate more.
is all this in the same file? if in that case you no need to create a props as there's already dialogEdit in the data() section.
props value is never redefined so if this is the cases, just remove the props.
if that's not solved your problem please update your question with better explaination because i just see one file.

How to get div in nested child component during mounted - Vue.js

I have problem whit scrolling to the element while opening the page. It's like scrolling to an anchor. I'm sending div id via props to the nested child component. On mounted I call a method scrollToSection where I have logic with scrolling.
props: {
section: String
},
mounted() {
this.scrollToSection();
},
methods: {
scrollToSection () {
if (this.section != null) {
const options = {
force: true,
easing: 'ease-in',
};
VueScrollTo.scrollTo(`#${this.section}`, 100, options);
}
}
}
In that point child component dosen't render the DOM and document.getElementById(this.section) return null. But in parent component I have an access to that div.
Do you have any idea how to resolve that issue? Method nextTick dosen't work.
Had the same problem, where I needed to access an image in a nested component, I had to wait till the image is loaded within the nested component, by adding a #load event listener that emit a loaded event to the parent, so I knew it was ready and perform my actions.
If nextTick isn't working, you can do the same, just emit a ready event on the mounted hook of the child component and when it's ready, perform the scroll.
//Child component
mounted(){
this.$emit('ready');
}
_
//Parent component
...
<child-component #ready="scrollToSection()" />
...
Or in alternative, if you have access to the div you want to scroll to, you can use pure javascript
MyDiv.scrollIntoView({ block: "start", behavior: 'smooth' });

Cannot get DOM events on component in host component

I have a Vue component that contains a list of objects named lines. I build a table from those lines using different components based on the line type. This works perfectly. Here's a stripped down version of the component:
<template>
<table>
<tr v-for="line in lines"
:key="line.key"
:is="componentForType[line.eventType] || 'LogLine'"
v-bind="line"
/>
</table>
</template>
<script>
export default {
name: 'DebugLog',
components: {
LogLine,
FormattedLogLine,
UserDebug,
Limits
},
data () {
return {
lines: [],
selectedKey: null,
componentForType: {
'USER_DEBUG' : 'UserDebug',
'LIMIT_USAGE_FOR_NS' : 'Limits',
'EXCEPTION_THROWN' : 'FormattedLogLine',
'FATAL_ERROR' : 'FormattedLogLine'
}
}
},
mounted() {
// code that loads this.lines
}
}
</script>
Now I want to be able to click any row of the table, and have the row become "selected", meaning that I want store line.key in this.selectedKey and use CSS to render that line differently. But I can't get the events working. Here's the updated <template>; nothing else is changed:
<template>
<table>
<tr v-for="line in lines"
:key="line.key"
:is="componentForType[line.eventType] || 'LogLine'"
v-bind="line"
:class="{selected: line.key == selectedKey}"
#click.capture="selectedKey = line.key"
/>
</table>
</template>
I've added the last 2 properties on the tr element - a dynamic class binding and a click event handler to set this.selectedKey to the active line's key. But it isn't working. I replaced the #click handler code with console.log(line.key) and nothing is logged, which tells me that my #click handler is never firing. I originally wrote it with out the .capture modifier, but tried adding the modifier when the original didn't work.
Is vue.js stopping propagation from the child component to the parent? Can I not bind the click event on the tr since it :is another vue component? Or is there something else going on? The examples I've found in the docs are much simpler and I'm not sure they correspond to my situation. The various child components are not binding any click events. I'd prefer to handle the event entirely in the parent as shown, since I will have a number of types of child component, and I don't want to have to implement click handlers in each.
Update: Looking at my child components, I note that each contains a tr tag that must effectively replace the tr in the parent template. For example, my most basic component is LogLine, shown here:
<template>
<tr>
<td>{{timeStamp}}</td>
<td>{{eventType}}</td>
<td>{{lineNumber}}</td>
<td>{{lineData}}</td>
</tr>
</template>
<script>
export default {
name: 'LogLine',
props: ['timeStamp', 'eventType', 'lineData', 'lineNumber'],
data: function () {
return {}
}
}
</script>
So I'm guessing that the binding in the parent isn't actually binding on the tr in the DOM; it's just binding on the Vue component, listening for a click event to be sent from the child with $emit; and that each child component will need to bind #click on its tr and emit it to the parent. Assuming I'm right, is there any shortcut I can use from the parent template to have vue forward the DOM events? Any other option I'm missing besides binding click in every child component?
Piggy-backing off of Jacob's answer here. Since you're essentially attaching an event listener to a dynamic component it expects a custom click event. So you have two options here:
Listen for the native DOM click event within that component (by attaching a click event listener to a normal DOM element within the component) and emit a custom click event to the parent.
Use the .native modifier to listen for the native DOM click event instead of a custom one directly in the parent.
Since you are using an :is prop, it's considered a dynamic Vue component, not a DOM element.
Events listener on a Vue component won't be passed down to its DOM element by default. You have to do it manually by going into the component template and add v-on="$listeners".
demo: https://jsfiddle.net/jacobgoh101/am59ojwx/7/
e.g. <div v-on="$listeners"> ... </div>
#Jacob Goh's use of v-on="$listeners" is simple and allows forwarding of all DOM events in one action, but I wanted to document an approach I tried on my own for completeness. I will be switching to Jacob's solution in my component. I am now using Husam's .native modifier in the parent as it is more suitable to my particular use case.
I was able to make my component work by editing each child component, capturing the click event and re-emitting it. For example:
<template>
<tr #click="$emit('click')">
<td>{{timeStamp}}</td>
<td>{{eventType}}</td>
<td>{{lineNumber}}</td>
<td>{{lineData}}</td>
</tr>
</template>
<script>
export default {
name: 'LogLine',
props: ['timeStamp', 'eventType', 'lineData', 'lineNumber'],
data: function () {
return {}
}
}
</script>

is it correct global component communication in vue?

i make modal popup components myPopup.vue for global.
and import that in App.vue and main.js
i use this for global, define some object Vue.prototype
make about popup method in Vue.prototype
like, "show" or "hide", any other.
but i think this is maybe anti pattern..
i want to find more best practice.
in App.vue
<div id="app>
<my-popup-component></my-popup-conponent>
<content></content>
</div>
main.js
...
Vue.prototype.$bus = new Vue(); // global event bus
Vue.prototype.$popup = {
show(params) {
Vue.prototype.$bus.$emit('showPopup', params);
},
hide() {
Vue.prototype.$bus.$emit('hidePopup');
}
}
Vue.component('my-popup-component', { ... });
...
myPopup.vue
....
export default {
...
created() {
this.$bus.$on('showPopup', this.myShow);
this.$bus.$on('hidePopup', this.myHide);
}
...
need-popup-component.vue
methods: {
showPopup() {
this.$popup.show({
title: 'title',
content: 'content',
callback: this.okcallback
});
}
}
It seems to be works well, but i don't know is this correct.
Is there any other way?
I was very surprised while reading your solution, but if you feel it simple and working, why not?
I would do this:
Add a boolean property in the state (or any data needed for showing popup), reflecting the display of the popup
use mapState in App.vue to bring the reactive boolean in the component
use v-if or show in App.vue template, on the popup declaration
create a 'showPopup' mutation that take a boolean and update the state accordingly
call the mutation from anywhere, anytime I needed to show/hide the popup
That will follow the vue pattern. Anything in state, ui components reflect the state, mutations mutates the state.
Your solution works, ok, but it doesn't follow vue framework, for exemple vue debug tools will be useless in your case. I consider better to have the minimum of number of patterns in one app, for maintenance, giving it to other people and so on.
You somehow try to create global component, which you might want to consume in your different projects.
Here is how I think I would do this -
How do I reuse the modal dialog, instead of creating 3 separate dialogs
Make a separate modal component, let say - commonModal.vue.
Now in your commonModal.vue, accept single prop, let say data: {}.
Now in the html section of commonModal
<div class="modal">
<!-- Use your received data here which get received from parent -->
<your modal code />
</div>
Now import the commonModal to the consuming/parent component. Create data property in the parent component, let say - isVisible: false and a computed property for the data you want to show in modal let say modalContent.
Now use it like this
<main class="foo">
<commonModal v-show="isVisible" :data="data" />
<!-- Your further code -->
</main>
The above will help you re-use modal and you just need to send the data from parent component.
How do I know which modal dialog has been triggered?
Just verify isVisible property to check if modal is open or not. If isVisible = false then your modal is not visible and vice-versa
How my global dialog component will inform it's parent component about its current state
Now, You might think how will you close your modal and let the parent component know about it.
On click of button trigger closeModal for that
Create a method - closeModal and inside commonModal component and emit an event.
closeModal() {
this.$emit('close-modal')
}
Now this will emit a custom event which can be listen by the consuming component.
So in you parent component just use this custom event like following and close your modal
<main class="foo">
<commonModal v-show="isVisible" :data="data" #close- modal="isVisible = false"/>
<!-- Your further code -->
</main>

Vue: Keep the same message until component is completely hidden

I have following Vue component.
When the button is clicked, it shows my-popup that shows message depending on the world flag.
When my-popup is closed, it switches world flag true/false.
Also, on closing, my-popup fades out and takes 2~3 seconds to completely dissapear.
Problem is, right after the onOK() method is fired, I can see the message on the closing my-popup changes as well.
(ex. If current message is hello world, it is changed to hello universe when fading out)
Is there any way to switch world flag and still hold the same message until the popup is closed?
<template>
<button #click="popupShown = true">hello</button>
<my-popup :value="popupShown", title="HELLO" #ok="onOK()">hello {{world ? 'world' : 'universe'}}</mypopup>
</template>
<script>
export default {
data () {
return {
popupShown: false,
world: true
}
},
methods: {
onOK () {
this.world = !world
this.popupShown: false
}
}
}
</script>
(Assuming that you are using Vue Transition in my-popup component)
What I notice about Vue transition is that, on a transition for hiding something, using
v-if : reactive values would stop updating
v-show : reactive values would continue updating
demo: https://codepen.io/jacobgoh101/pen/BxqGgB
Therefore, if you are using v-show in the transition in my-popup, simply changing it to v-if should solve your problem.