Passing an event between sibling components - vue.js

I have a Parent component that hosts 2 children. Child A (FileLoader) is a file uploader component. When he successfully uploads something, I want to send an event to Child B (FileViewer) that a file upload succeeded.
I'm new to Vue3 and Vue in general. What is the correct / simple way to send the event info in this way?
In Child A, I already have
this.$emit('new-file-loaded');
but, I'm not sure what to do in my parent. Currently, the parent just holds the 2 components:
<div class="row">
<div class="col">
<FileLoader/>
</div>
</div>
<div class="row">
<div class="col">
<FileViewer />
</div>
</div>
I think in my parent, I want somethign like
<FileTable #new-file-uploaded="....something..."/>, but I'm not quite sure if that's the right approach.

You can have the state of whether the file is loaded or not stored in the parent component. The parent component can update a prop in the sibling component that needs that information when the file upload is complete.
So you can have something like this:
<div class="row">
<div class="col">
<FileLoader #new-file-uploaded="updateFileIsLoaded"/>
</div>
</div>
<div class="row">
<div class="col">
<FileViewer :fileIsLoaded=fileIsLoaded/>
</div>
</div>
Then in the parent component:
data() {
return {
fileIsLoaded: false
}
},
methods: {
updateFileIsLoaded() {
this.fileIsLoaded = true
}
}
When the fileIsLoaded variable changes in the parent component, it will cause the FileViewer component to re-render because its prop changed.

Related

The Bootstrap-vue3 table does not add buttons to each row of data

I'm trying to add a button to the bootstrap table using Vue3, but I ran into the following problem.
The logic of the button is stored in the parent component, and I plan to $emit on the logic from there. But in order for the button to delete a certain row, for example, I need to pass a special ID, I can't pass it to the ID, because I don't understand how to iterate through the data array in the bootstrap table correctly.
Maybe there is a soft and correct way to solve this problem?
ChildComponent.vue
<template>
<div v-if="data.length !== 0">
<div class="card inline" >
<div>
<b-table striped hover :items="data">
<button #click="$emit('remove', hereIneedTheID)">Remove</button>
</b-table>
</div>
</div>
</div>
<div class="card center" v-else>
<h4>No data available in the list</h4>
<button class="btn" #click="$emit('load')">Load list of data</button>
</div>
</template>
<script>
export default {
name: "AppDataList",
emits: ['load', 'remove'],
props: ['data']
}
</script>

Vue - passing v-for index from parent to child component

I've done the research but can't find a good answer. My parent and child component code is below. How do I pass the index for the v-for loop in the parent to the child component for use there? I want to hide any of the gauges past #4 for mobile screens, but show all of them on a desktop.
Parent:
<div class="col-md-8 col-xs-6">
<div class="row flex-nowrap">
<data-block
v-for="(gauge, index) in device.gauges"
:metric="gauge.metric"
:value="gauge.value"
:unit="gauge.unit"
:alarm="gauge.alarm"
:idx="index">
</data-block>
</div>
</div>
Child:
app.component('data-block', {
props: ['alarm', 'metric','value','unit','idx'],
template: `<div v-bind:class="'col card px-0 text-center border' + ((alarm) ? ' border-danger':' border-success') + ((idx > 3) ? ' d-none d-md-block':'')">\
<div class="card-header p-1">{{metric}}</div>\
<div class="card-body p-1 align-middle">\
<h1 class=" text-center display-1 font-weight-normal text-nowrap">{{value}}</h1>\
</div>\
<div class="card-footer p-1">{{unit}}</div>\
</div>`,
created: ()=> console.log(this.idx) //yields "undefined"
})
You're passing the idx prop correctly, but Instead of checking its value inside created hook, try displaying it in the template instead, to make sure it's not an issue with timing (it might not be defined when the child component is created):
<div>{{idx}}</div>
Also, to make the code easier to read and write, I would suggest you to move the static classes to class attribute and the dynamic classes to v-bind:class attribute, and also make it multiline:
template: `
<div
class="col card px-0 text-center border"
:class="{
'd-none d-md-block': idx > 3,
'border-danger': alarm,
'border-success': !alarm
}"
>
...
`

VueJS Modal component inside component

I have a component like this:
<test></test>
I declare this as follows:
Vue.component('test', {
data: {
showModal: true
},
methods: {
displayComponentModalDialog: function() {
this.showModal = true;
}
},
template: `<button #click='displayComponentModalDialog'>test</button>`
});
The <test></test> component is then placed somewhere inside the <div id="#app"> wrapper.
var app = new Vue({
router,
el: '#app',
// etc.
})
Now, I want to display another component inside the test component. So in this case I want a dialog to appear after I click the button in test component. I am not able to achieve this.
What I did is adding a new component:
Vue.component('dialog', {
template: '#dialog-template'
});
And then the following code, although I do not know for sure where to put it.
<!-- template for the modal component -->
<script type="text/x-template" id="dialog-template">
<transition name="dialog">
<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 name="body">
default body
</slot>
</div>
<div class="modal-footer">
<slot name="footer">
<button class="btn btn-primary" #click="$emit('close')">
OK
</button>
</slot>
</div>
</div>
</div>
</div>
</transition>
</script>
<!-- use the modal component, pass in the prop -->
<dialog v-if="showModal" #close="showModal = false">
<h3 slot="header">header</h3>
<p slot="body">
test
</p>
</dialog>
I tried putting this code inside the <test></test> but doesn't work. If I put it inside the template attribute in the component structure, it complains about only one root element.
So it is clear I miss some basic conception how this actually works in VueJS. Someone can help me clarify? Thanks.
As far as I can see your component indeed doesn't have a root tag. Templates have to have a root tag.
This is NOT a valid Vue template:
<div>
<h1>Stuff</h1>
</div>
<h2>Other stuff</h2>
This IS a valid Vue template:
<div>
<div>
<h1>Stuff</h1>
</div>
<h2>Other stuff</h2>
</div>
Note that in the second version we have a single root element for the template, a <div>, whereas in the first one we do not.
You have both a <script></script> and a <dialog></dialog> in your component template.
if you want to add another component in your test component . you can use slot on it.
You can refer to this documentation: https://v2.vuejs.org/v2/guide/components-slots.html
Example:
//component 1
<template>
<div id="modal">
// do something for your modal here.
<slot></slot> // reserve area for your another html or component.
</div>
</template>
// component 2
<template>
<your-modal-component>
<another-component>
</your-modal-component>
</template>

Vue.js: Loading template (or div) when user clicks button?

So I currently have a template sitting in a ".vue" file like so:
<template>
<div id="dataAttachToMe"></div>
</template>
I don't want this to load, unless a user clicks a button, something like
<button #click="loadTheTemplateAbove">See Data</button>
I've tried using this example:https://v2.vuejs.org/v2/guide/conditional.html#Controlling-Reusable-Elements-with-key. But it says something like "Component template should contain exactly one root element" in the error message.
I need more than a show/hide here I think, something that can initiate the template dynamically.
<template>
<div id="data">
<button #click="loadTemplate">Load the template</button>
<div v-if="buttonClicked">
<div id="dataAttachedToThisDiv"></div>
</div>
</div>
</template>
The error you are getting, means that there is more than one root element inside <template></template> tag.
It is required in Vue.js (and other template based frameworks/libraries) to have only one root element.
This will NOT work:
<template>
<div id="dataAttachToMe"></div>
<button #click="loadTheTemplateAbove">See Data</button>
</template>
This will work:
<template>
<div id="someRootDiv">
<div id="dataAttachToMe">Some data</div>
<button #click="loadTheTemplateAbove">See Data</button>
</div>
</template>
Here is a code example (App.vue) of what you are trying to achieve:
Basic idea: we have to create a variable, that will be changed upon button click. We add v-if directive that depends on that variable and will handle element's visibility.
Welcome to StackOverflow. When you get the error Component template should contain exactly one root element it means that you can only have one root element in your template. You can fix that error by wrapping everything in a blank div like so
<template>
<div>
<template v-if="loginType === 'username'">
<label>Username</label>
<input placeholder="Enter your username">
</template>
<template v-else>
<label>Email</label>
<input placeholder="Enter your email address">
</template>
</div>
</template>
Please edit your post and place you <script> tag. Conditional Rendering requires a data field of a boolean that you can place in your if statement on your template
<template>
<div>
<div v-if="show">{{message}}</div>
<div v-if="#show">Not Showing when show is set to false</div>
<button v-on:click="show = true">Show</button>
</div>
</template>
<script>
module.exports {
data: function () {
message: 'Hello Vue!',
show: false
}
}
</script>

VueJS: Is It Possible to Automatically Include One Component in Another Component

I would like to automatically include the contents of one component in a named slot of another component. I.e., something like this:
Vue.component('comp-one', {
template: `
<div class="comps>
<div class="comp-two">
<slot name="compTwo"></slot>
</div>
<div class="comp-one">
<slot></slot>
</div>
</div>
`
})
Vue.component('comp-two', {
slot: 'compTwo',
template: `
<div class="sub-comp-two">
<!-- content goes here -->
<slot></slot>
</div>
`
})
The idea is that if someone uses <comp-two>Some content</comp-two>, it will automatically get added to the slot compTwo in comp-one. Is there a way to do this?
NOTE: I made up slot: 'compTwo'. Just trying to illustrate what I'd like to accomplish.