Bootstrap-Vue Modal: How to open different modals in an V-For rendered list? - vue.js

I am using Bootstrap-Vue Modals to open an "Edit" modal when clicking the "Edit" button that is attached to each item I have rendered in a list from v-for rendered list.
Each time, however, when I click on the edit button, it opens all of the modals, stacked on top of eachother with the last element in the list being the top modal.
How can I specify it to only open the modal/information for the item that is clicked to be edited?
//Parent Component
<div class="dataList">
<div v-bind:key="item.id" v-for="item in this.$store.getters.data">
<Child v-bind:item="item"></Child>
</div>
</div>
//Child Component
<div>
{{this.item.name}}
{{this.item.details}}
{{this.item.completedBy}}
{{this.item.status}}
<button v-b-modal.modal-1>Edit</button>
<button v-on:click="deleteItem">Delete</button>
<div>
<b-modal id="modal-1" title="BootstrapVue">
<form #submit="editItem">
<input v-model="name">
<input v-model="details">
<input v-model="completedBy">
<select v-model="status">
<option>Fail</option>
<option>Warn</option>
<option>Pass</option>
</select><br>
<input type="submit" value="Submit" #click="$bvModal.hide('modal-1')">
</form>
</b-modal>
</div>
</div>
Now each modal shows the correct information (like the proper name, details, status, etc), but I just need it only the specific modal.
I imagine it has something to do with the 'v-b-modal.modal-1' but I'm not sure how to dynamically set the id of each modal...is there a way to easily set each modal id to match the item.id?
Here is the documentation for Bootstrap-Vue Modals, but I wasn't to find what I needed.

I'd recommend moving the <b-modal> out of your v-for.
This way you only have one.
Then you instead set the user/item you want to edit to a variable, and utilize that in your modal to show the data for the selected user.
Example
new Vue({
el: "#app",
data() {
return {
selectedUser: null,
items: [{
name: "Name 1",
status: "Fail",
details: "Details 1"
},
{
name: "Name 2",
status: "Warn",
details: "Details 2"
}
]
}
},
methods: {
editUser(user) {
this.selectedUser = user;
this.$bvModal.show("edit-modal");
}
}
})
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap#4.5.3/dist/css/bootstrap.min.css" />
<link href="https://unpkg.com/bootstrap-vue#2.21.2/dist/bootstrap-vue.css" rel="stylesheet" />
<script src="//unpkg.com/vue#2.6.12/dist/vue.min.js"></script>
<script src="//unpkg.com/bootstrap-vue#2.21.2/dist/bootstrap-vue.min.js"></script>
<div id="app" class="p-4">
<div v-for="item in items" class="mt-3">
{{item.name}} {{item.details}} {{item.status}}
<b-btn variant="primary" #click="editUser(item)">Edit</b-btn>
<b-btn variant="danger">Delete</b-btn>
</div>
<b-modal id="edit-modal" title="Edit User">
<form v-if="selectedUser">
<input v-model="selectedUser.name">
<input v-model="selectedUser.details">
<input v-model="selectedUser.completedBy">
<select v-model="selectedUser.status">
<option>Fail</option>
<option>Warn</option>
<option>Pass</option>
</select><br>
<input type="submit" value="Submit">
</form>
</b-modal>
</div>
If you want to keep the modal inside your v-for, you will have to give it a unique id. If you item has one i would recommend using that, otherwise you can use the index from the v-for.
Simplified example
<div v-for="item in items">
<button v-b-modal:[`edit-modal-${item.id}`]>Edit Item</button>
<b-modal :id="`edit-modal-${item.id}`">
<!-- Modal Content-->
</b-modal>
</div>

One of the most quick way but maybe not the best is Conditional Rendering the modals.
You can toggle the active modal number/index when the Edit is clicked by setting a prop/data/var as activeModal: 1 or n.
Then at the section where you iterate modals you can use v-if directive for modal like: <v-modal v-if="activeModal"> that'd be one way to ignore/not render other modals but would cost re-rendering on each Edit click.
Best/Final solution would be to vue-portal the content to a single universal modal.

Related

Vue.js 3 and Modal with DaisyUI (Tailwind CSS)

I'm tinkering with DaisyUI within Vue.js 3 (porting an existing Vue+Bootstrap application to Tailwind CSS). I liked the idea that DaisyUI doesn't have JS wizardry going on behind the scenes, yet there seems to be some CSS voodoo magic that is doing things more complicated than they need to be (or at least this is my impression).
From the DaisyUI examples, here's the modal I'm trying to integrate:
<input type="checkbox" id="my-modal" class="modal-toggle"/>
<div class="modal">
<div class="modal-box">
<h3 class="font-bold text-lg">Congratulations random Internet user!</h3>
<p class="py-4">You've been selected for a chance to get one year of subscription to use Wikipedia for free!</p>
<div class="modal-action">
<label for="my-modal" class="btn">Yay!</label>
</div>
</div>
</div>
no javascript, yet the problem is that the modal will come and go according to some obscure logic under the hood that tests the value of the my-modal input checkbox at the top. That's not what I want. I want my modal to come and go based on my v-show="showModal" vue3 reactive logic!
Daisy doesn't seem to make that possible. Or at least not easily. What am I missing?
The modal is controller by the input field, so to open or close the modal just toggle the value of that input:
<template>
<!-- The button to open payment modal -->
<label class="btn" #click="openPaymentModal">open modal</label>
<!-- Put this part before </body> tag -->
<input type="checkbox" v-model="paymentModalInput" id="payment-modal" class="modal-toggle" />
<div class="modal modal-bottom sm:modal-middle">
<div class="modal-box">
<h3 class="font-bold text-lg">Congratulations random Internet user!</h3>
<p class="py-4">You've been selected for a chance to get one year of subscription to use Wikipedia for free!</p>
<div class="modal-action">
<label class="btn" #click="closePaymentModal">Yay!</label>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
const paymentModalInput = ref()
const openPaymentModal = () => {
paymentModalInput.value = true
}
const closePaymentModal = () => {
paymentModalInput.value = false
}
</script>

adding component on add button

i am a absolute beginner in vuejs,i have a feature of adding dynamic input fields on click of a button it will keep on adding rows and keeping in mind the counter should be incrementing also so that i can validate on backend, this is my code so far
<div id="settlement_container" class="container-fluid mt-4">
<div class="card rounded-0 shadow-lg">
<div class="card-body p-0">
<div class="card-header px-2">
<div class="row wow fadeIn">
<div class="col-5">
<h3>Add Store Status</h3>
</div>
</div>
</div>
<form class="custom-form-group" action="{{url('stores/addStoreStatusDB')}}" method="POST">
<div class="form-group col-6">
<label for="exampleInputEmail1">Tax</label>
<input type="text" class="form-control" id="exampleInputEmail1" aria-describedby="emailHelp" name="tax" placeholder="Tax" required>
</div>
<div class="display-inline">
<div class="form-group col-md-6">
<button #click="addstatus" class="btn btn-primary">Add Rows</button>
</div>
</div>
<div class="display-inline">
<div class="form-group col-md-6">
<button type="submit" class="btn btn-primary">Update Tax</button>
</div>
</div>
<dynamic-rows/>
</form>
</div>
</div>
</div>
{{-- Main layout --}}
#push('script')
<script src="{{ asset('js/app_vue.js') }}" ></script>
<script>
Vue.component('dynamic-rows',{
//accept data inside template
props:['counter'],
//accept data inside template
template:"<label for='exampleInputEmail1'>counter</label>"
});
const app = new Vue({
el: '#settlement_container',
data: {
counter:0
},
component:['dynamic-rows'],
methods:{
addstatus:function(e){
appendDiv=""
e.preventDefault();
alert("inside");
}
}
});
</script>
now i can do this in jquery in 5 minutes , but as i am beginner in vuejs i cant developer the sense of it of how to do it, i have a component and i want to repeat the component every time the button is clicked,
here is the fiddle! fiddle
OK, so a lot going on here and I think it may be easier to break down some of the points in isolation for you to play with and learn.
To add inputs, I think it makes more sense to have the values being in an array. Using Vue, you can iterate through that array to let each array element have its own <input/> while also simply adding another array element to add a new input:
<template>
<div>
<div v-for="(tax, index) in taxes" :key="index">
<input v-model="taxes[index]" />
</div>
<button type="number" #click="add">Add</button>
<p>Count: {{taxes.length}}</p>
</div>
</template>
<script>
export default {
data(): {
return {
taxes: [0]
}
},
methods: {
add() {
this.taxes.push(0);
}
}
});
</script>
Now with regards to the counter, I don't know what you mean validate on the backend. You could add a watcher on the taxes array and process changes there? Watchers are used sparingly, with computed properties being much preferred, but they may make sense if you need to be sending data to the backend instead of into the DOM.
The counter prop you registered in your code is not really going to work for the pattern I showed. Generally, props are for parent components to pass data to child components. The preferred pattern when sending data from child to parent is to use $emit. Read more here.

Don't see div with v-if, regardless of the value in the data

I have problem with displaying component with v-if.
In one component i have <div v-if="seen">...</div>.
In another component I have <button v-on:click="seen = !seen">...</button>.
In "var vue = nev Vue({...})" file, in data: I have seen: true and this is not working.
I found "solution" which works: example
and I tried this "function version" of data in my code, but it doesn't work too :/
Here is my code:
Main File
var vue = new Vue({
el: '#app',
components: {
Navigation,
Home,
Footer,
Login
},
data: function () {
return {
seen: true
}
},
template: "<div><navigation></navigation><login></login><home></home><Footer></Footer></div>"
})
template that I can't see
<div v-if="seen" id="loginbox">
<form>
<input type="text" placeholder="login" class="input is-rounded"/>
<input type="password" placeholder="password" class="input is-rounded"/>
</form>
</div>
button template
<div class="navbar-menu">
<div class="navbar-start"></div>
<div class="navbar-end">
<p class="nav-item">
<a class="nav-link" v-on:click="seen = !seen">Login</a>
</p>
<p class="nav-item">
<a class="nav-link">Register</a>
</p>
</div>
</div>
I expect that when I click on button, "loginbox" template will be shown.
EDIT:
I did it in half way. I used props (used export default...) in in template that I cannot seen. It not work properly, becouse now I can change value of "seen" only with button which is in this template. I'd like change value of it by button which is in another template.
You should somehow share data between components.
You can do it many ways, but for this case i suggest to use event handling https://v2.vuejs.org/v2/guide/events.html#ad
Edited your sandbox with example - https://codesandbox.io/s/mzq0r2w88j

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>