How to emit event to another component that is not a parent in vue.js? - vue.js

So the first component in the example below simply just imports other components and then renders them in the template. Among the components being imported, I have a Stages component and a StageExecutionTimes component. What I'm trying to do is when a user clicks on an icon in the Stage component, I want to then hide the StageExecutionTimes component. My understanding is that I will need to use $emit from the Stages component to send the event to the parent which will then hide the StageExecutionsComponent. I am trying to set showgraph as a boolean which is then used as a prop between the components. I am then trying to change the value of showgraph via $emit.
The code below does not seems to be affecting or changing the value of showgraph in the main component
main component that imports and renders subcomponents
<template>
<div id="vue-main">
<NavBar></NavBar>
<transition name="fade">
<div :v-bind="showgraph"><StageExecutionTimes></StageExecutionTimes></div>
</transition>
<transition name="fade">
<Stages></Stages>
</transition>
<transition name="fade">
<Overview></Overview>
</transition>
<Footer></Footer>
</div>
</template>
<script>
import NavBar from "../components/NavBar.vue";
import Stages from "../components/execution_details/Stages.vue";
import Binaries from "../components/execution_details/Binaries.vue";
import StageExecutionTimes from "../components/graphs/StageExecutionTimes.vue";
import Overview from "../components/execution_details/Overview.vue";
export default {
name: "execution_details",
data() {
return {
loading: true,
showgraph: true
};
},
components: {
NavBar,
Stages,
StageExecutionTimes,
Overview,
},
../components/execution_details/Stages.vue";
<template>
<div class="stages">
<template v-if="job_execs.length > 0">
<h3>Stages</h3>
<img src="#/assets/charticon.png">
<transition name="fade" appear mode="out-in">
<table>
<tbody>
<template v-for="item in job_execs">
<tr>
<td
v-for="stage_execution in item.stage_execution"
:class="stage_execution.status.name"
:title="stage_execution.exec_node.name"
:key="stage_execution.stage.name"
>
<br />
{{ stage_execution.duration | durationReadable }}
<br />
{{ stage_execution.status.name }}
</td>
</tr>
</template>
</tbody>
</table>
</transition>
</template>
</div>
</template>
<script>
import { calculateDuration } from "../../helpers/time.js";
import { liveDuration } from "../../helpers/time.js";
import moment from "moment";
export default {
name: "Stages",
props: ["showgraph"],
data() {
return {
job_execs: []
};
},
How can I hide StageExecutionTimes component when charticon is clicked in the Stages component?
Also, can I accomplish this WITHOUT even touching StageExecutionTimes component? It seems like I could just pass the event from Stages to the main component and then from there just hide StageExecutionTimes.

if you have many components to share datas,you need a vuex store.
you click the then you change the datas in the store, and in your you use v-if="isShow"
#click="hideOtherComponent"
hideOtherComponent(){
this.store.commit('HIDE_COMPONENT');
}
computed: {
isShow() {
return store.state.StageExecutionTimesShow
}
}
<component v-if="isSHow">
`
vuex

Have the components a common high level parent? If the answer is yes, you can $emit the change to the parent, and pass to the other child the data trought a Prop.

Related

How can I wrap every v-if in my Vue code in a transition?

The task of having to write
<transition name="fade">
<div v-if="condition">
</div>
</transition>
Is manual labour. Is there any shortcut way of wrapping every v-if with a transition?
You can create a custom Vue component:
// AppTransition.vue
<template>
<transition name='fade'>
<div v-if='show'>
<slot />
</div>
</transition>
</template>
<script>
export default {
props: {
show: Boolean
}
}
</script>
and then use it as follows:
<template>
<AppTransition :show='condition'>
<div>Hello, world</div>
</AppTransition>
</template>
<script>
// You can avoid importing it everywhere by making it a global component, see https://vuejs.org/guide/components/registration.html
import AppTransition from './AppTransition'
export default {
components: { AppTransition }
}
</script>

How Can I Call Template in Same Component on Vue

My problem is, there are a few templates on a component. And I want to call on same component. For example:
<template>
<div>
[I WANT TO USE FIRST TEMPLATE]
</div>
<template>First</template> //First Template
<template>Second</template> //Second Template
</template>
Ihsan Fajar Ramadhan's answer is one way of doing it. But, there is another better way of conditional rendering of components like below:
<template>
<div>
<button v-on:click="setSelected('comp1')">Comp1</button>
<button v-on:click="setSelected('comp2')">Comp2</button>
<component :is="selected"></component>
</div>
</template>
<script>
import comp1 from './comp1.vue';
import comp2 from './comp2.vue';
export default {
components : {
'comp1' : comp1,
'comp2' : comp2
},
data(){
return {
selected : 'comp1'
},
methods: {
setSelected(tab){
this.selected = tab;
}
}
}
</script>
if you want to render them conditionally, you can use v-if directive. for the example:
<template>
<div>
[I WANT TO USE FIRST TEMPLATE]
</div>
<template v-if="fistCondition">First</template> //First Template
<template v-else>Second</template> //Second Template
</template>
or
<template>
<div>
[I WANT TO USE FIRST TEMPLATE]
</div>
<template v-if="fistCondition">First</template> //First Template
<template v-else-if="secondCondition">Second</template> //Second Template
</template>

Vue - display/create component from a function

One thing that I have been struggling to figure out how to do better is modals. Currently, I am registering the modal component on each Vue that needs it. However, this feels rather sloppy, as I am having to register the component several times. Even using mix-ins just does not feel like an elegant solution. What would be optimal to be able to do is to mimic JavaScript's alert() method on the Vue instance. For example, be able to call this.ShowModal(Header, Body)
However, from my understanding, there is no way to accomplish this
Take for example my Modal example. You could have a modal template like this:
<script type="text/x-template" id="modal-template">
<transition name="modal">
<div class="modal-mask">
<div class="modal-wrapper">
<div class="modal-container">
<div class="modal-header">
<slot name="header">
default header
</slot>
</div>
<div class="modal-body">
<slot>
</slot>
</div>
<div class="modal-footer">
<slot name="footer">
default footer
<button class="modal-default-button" #click="$emit('close')">
OK
</button>
</slot>
</div>
</div>
</div>
</div>
</transition>
</script>
Then you would have to reference the component over and over again like this
<template>
<button #click="displayModal">Display the Modal Alert</button>
<modal v-if="showModal" #close="showModal = false">
<h3 slot="header"> This is a good header </h3>
<p>
Look at me I am the body! You have seen me {{displayCount}} times!
</p>
</modal>
</template>
<script>
components: {modal},
data: {
showModal: false,
displayCount: 0
},
methods: {
displayModal(){
this.displayCount++
this.showModal = true;
}
}
</script>
If you wanted to reuse the component for several messages from within the parent you would then have to add several more variables to store things such as the header and body. You could put some of the logic into a mixin but you would still have to have the clutter of adding the modal component and possibly the mixin.
This brings us to the question. Is there a way to create a function in the Vue instance that would allow for us to dynamically create a Modal component and fill in the slots with arguments passed to the function? e.g. this.ShowModal("This is a good header", "Look at me I am the body!")
Use Vue.extend() create a "modal" constructor and create a instance,you can mount it to DOM dynamically by $mount or other ways
In Modal example:
modal.vue:
<template>
<div>
{{message}} //your modal content
</div>
</template>
<script>
export default {
name: 'modal',
data(){
return {
message: '',
}
},
methods:{
/************/
close () {
/****this.$destroy()****/
}
}
}
</script>
modal.js:
import myModal from 'modal.vue'
import Vue from 'vue'
const modalConstructor = Vue.extend(myModal)
const modal = (options,DOM)=>{
const modalInstance = new modalConstructor({data:options})
modalInstance.vm = modalInstance.$mount() //get vm
const dom = DOM || document.body // default DOM is body
dom.appendChild(modalInstance.vm.$el) // mount to DOM
return modalInstance.vm
}
export default modal
now you can create a Modal component by a function like this:
import showModal from 'modal.js'
showModal({message:"..."})

Vue.js dynamic layout renderless component layout with multiple slots

I am trying to build a dynamic layout for my application. I have two different layouts, one being DefaultLayout.vue:
<template>
<div>
<main>
<slot/>
</main>
</div>
</template>
and a second one being LayoutWithFooter.vue, with two slots:
<template>
<div>
<main>
<slot/>
</main>
<footer>
<slot name="footer"/>
</footer>
</div>
</template>
My renderless component to handle the dynamic layout looks like this:
<script>
import Vue from 'vue';
import DefaultLayout from './DefaultLayout';
import LayoutWithFooter from './LayoutWithFooter';
export default {
props: {
name: {
type: String,
required: true
}
},
created(){
this.registerComponent("DefaultLayout", DefaultLayout);
this.registerComponent("LayoutWithFooter", LayoutWithFooter);
this.$parent.$emit('update:layout', this.name);
},
methods: {
registerComponent(name, component) {
if(!Vue.options.components[name]) {
Vue.component(name, component);
}
}
},
render() {
return this.$slots.default[0];
},
}
</script>
All of this works fine for the DefaultLayout.vue but when I want to use the LayoutWithFooter.vue, it cannot handle the two slots inside it. Here's an example usage:
<template>
<layout name="LayoutWithFooter">
<div>
<div>some content</div>
<div slot="footer">content for the footer slot</div>
</div>
</layout>
</template>
Problem now is, that the "content for the footer slot" does not get rendered inside of the footer slot of the LayoutWithFooter.vue.
First of all I want you to pay an attention to defining slots level in your example. You provided this code:
<template>
<layout name="LayoutWithFooter">
<div>
<div>some content</div>
<div slot="footer">content for the footer slot</div>
</div>
</layout>
</template>
But actually your div slot="footer" does not refer to footer slot of LayoutWithFooter.vue component. It because of anyway the very first child refers to default slot. And as a result it looks like:
"You want to set content for default slot and inside this default slot you tried to set content for footer slot" - but it's two different scopes.
The right options would look like on the next example:
<template>
<layout name="LayoutWithFooter">
<!-- default slot content -->
<div>
<div>some content</div>
</div>
<!-- footer slot content -->
<div slot="footer">content for the footer slot</div>
</layout>
</template>
I prepared some example based on code and structure you've provided. There you are able to switch layouts and check out how it works and use different components slot in one layout.
Check it here:
https://codesandbox.io/s/sad-fog-zr39m
P.S. Maybe some point are not totally clear, please reply on my answer and I will try to explain more and/or provide you with more links and sources.

Components and Sub components

I'm new to Vue.js and I'm having a bit of trouble using components with sub-components. I have the following .vue files
app.vue
<template>
<section>
<menu></menu>
<h1>Create Your MIA</h1>
<div id="board"></div>
<slider>
<skin></skin>
</slider>
</section>
</template>
slider.vue
<template>
<div id="slider-panel">
<h3>{{* heading}}</h3>
<div class="slider">
<slot>
Some content
</slot>
</div>
</div>
</template>
<script>
import skin from "./skin";
export default {
components: {
skin: skin
}
};
</script>
skin.vue
<template>
<div v-for="colour in colours">
<div :style="{ backgroundColor: colour }">
<img src="../assets/images/MIA.png"/>
</div>
</div>
</template>
<script>
export default {
data() {
return {
heading: "Choose Skin Tone"
};
}
};
</script>
I'm trying to load the skin sub component into the component. Everything works well except for the skin sub component as it doesn't get compiled. I do not get any compile or vue related errors though. I also wanted to be able to have several instances of the slider component like this
app.vue
<template>
<section>
<menu></menu>
<h1>Create Your MIA</h1>
<div id="board"></div>
<slider>
<skin></skin>
</slider>
<slider>
<foo></foo>
</slider>
<slider>
<bar></bar>
</slider>
</section>
</template>
I'm not sure what I am doing wrong.
I'm not 100% sure of what you want to achieve here, but to compile a component inside a component, you need to add the child component inside the parent's template, like this:
Slider.vue (I've simplified the structure):
<template>
<div id="slider-panel">
<h3>{{* heading}}</h3>
<skin></skin>
</div>
</template>
<script>
import skin from './skin'
export default {
components : {
'skin': skin
}
}
</script>
App.vue:
<template>
<section>
<menu></menu>
<h1>Create Your MIA</h1>
<div id="board"></div>
<slider></slider>
</section>
</template>
Actually, if you add skin in the app's template inside of adding it in the slider component template, it gets included (and rendered) assuming that its scope is app, not slider. In order to add skin inside slider scope, it needs to be added to slider's template. Check this: https://vuejs.org/guide/components.html#Compilation-Scope
Some other things:
Use a hyphen-separated name for the components, with at least 2 words: <custom-slider> instead of <slider>, for example, following the Web Components API (otherwise it might collide with current or upcoming web components).
Slots are complicated to grasp, so read this carefully: https://vuejs.org/guide/components.html#Content-Distribution-with-Slots
Good luck!
Update:
If you want the slider component to be content agnostic and be able to insert anything you want inside it, you have two options (that I can think of):
Remove all the logic from the slider component and make skin a descendant from app. Then use slots in the slider component, as follows:
Slider.vue:
<template>
<div id="slider-panel">
<h3>{{* heading}}</h3>
<slot></slot>
</div>
</template>
<script>
export default {}
</script>
App.vue:
<template>
<section>
<menu></menu>
<h1>Create Your MIA</h1>
<div id="board"></div>
<slider>
<skin></skin>
</slider>
</section>
</template>
<script>
import skin from './skin'
export default {
skin: skin
}
</script>
If you know that the slider will always have a closed set of components inside, you can use dynamic components: https://vuejs.org/guide/components.html#Dynamic-Components
After some research I found this which refers to a is= attribute that will transclude the sub-component template
so in app.vue
<slider-component>
<div is="skin-component" v-for="colour in colours"></div>
</slider-component>
and then add child components