What is the proper way to use multiple heavy same VueJS components on a page? - vue.js

I am trying to create custom input as VueJS component. It will have <input type="text"> field and button. This component must implement such behavior: you can type text with autocomplete or press button which opens modal dialog with records from database and then select one. Something like this:
<div class="input-group">
<input type="text" class="form-control" placeholder="Search for...">
<span class="input-group-btn">
<button class="btn btn-default" type="button" #click="openModal">Choose</button>
</span>
</div>
The modal dialog will contain complicated logic and a lot of HTML code. I think I will put modal dialog in other component.
After all my custom input component will be used on page in table rows like:
<tr v-for="item in items">
<td><input-component :item="item"><input-component></td>
</tr>
The table may contain 10-30 rows and that is a question - should I exclude heavy modal dialog code from my custom input component or it is fine in VueJS to have such many tens duplications in DOM?
What variant should I choose:
1) exclude modal dialog, place it once in top of page and call it from custom input components
<body>
<modal-component></modal-component>
<table><tbody>
<tr v-for="item in items">
<td><input-component :item="item"><input-component></td>
</tr>
</tbody></table>
</body>
2) include modal dialog and have tens of its duplicated code in DOM
<body>
<table><tbody>
<tr v-for="item in items">
<td><input-component :item="item"><input-component></td><!--now contains <modal-component></modal-component>-->
</tr>
</tbody></table>
</body>

Use a dynamic component and bind the component type to a reactive property.
Vue.component('customer-dialog', {
template: '<p>Customer Dialog</p>'
})
Vue.component('supplier-dialog', {
template: '<p>Supplier Dialog</p>'
})
var app = new Vue({
el: '#app',
data: {
dialog: null // initial dialog to use
},
methods: {
showDialog: function(d) {
this.dialog = d
// additional dialog initialization code
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.10/vue.js"></script>
<div id="app">
<component :is="dialog"></component>
<button #click="showDialog('customer-dialog')">Customers</button>
<button #click="showDialog('supplier-dialog')">Suppliers</button>
</div>
If you want to keep the switched-out dialogs in memory so that you can preserve their state or avoid re-rendering, you can wrap the dynamic component in a element.
<keep-alive><component :is="dialog"></component></keep-alive>

Just use one modal dialog for the whole page.
Fill in the dialog with relevant data from an array that will look like this
var dialogs = [
{ name: 'john', surname: 'snow' },
{ name: undefined, surname: undefined },
...
]
var currentDialog = 4
var dialogData = dialogs[currentDialog]

Related

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

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.

How to show using vue static data into modal?

I have this couple of 'cards' with this content
<div class='card'>
<span>title one </span>
<button #click='open = !open'>show</button>
</div>
<div class='card'>
<span>title two </span>
<button #click='open = !open'>show</button>
</div>
Where in the same component I show this modal. What i need to show inside of it is if I click the first button, show title one, if I click button 2,show title 2 and so on...
What will be the best approach to do this task? (show in modal the card content)
First a couple of things:
ooen might be a typo, did you mean open?
=! looks like a single operator but it actually means open = !open in this context. Put a space between the = and ! to make it clear what it means.
If you want to control the visibility of two sections independently then you will need two separate data properties (open1 and open2). Use v-if or v-show to control the visibility.
new Vue({
el: '#app',
data: {
open1: false,
open2: false,
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div class="card">
<span v-if="open1">title 1</span>
<button #click="open1 = !open1">toggle</button>
</div>
<div class="card">
<span v-if="open2">title 2</span>
<button #click="open2 = !open2">toggle</button>
</div>
</div>
If you will have lots of these, then wrap the functionality into a separate component so you don't have to define open1, open2, ... and so on.
Vue.component('card', {
template: `
<div class="card">
<span v-if="open"><slot/></span>
<button #click="open = !open">toggle</button>
</div>`,
data() {
return {
open: false
}
}
})
new Vue({
el: '#app'
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<card>title 1</card>
<card>title 2</card>
</div>
I found the solution.
Create the cards data into a array of objects in the data instance
Use a v-for to loop through the cards
Create a method that capture the card clicked, create a projectSelected value in data
<button #click="showModal = !showModal,open(project)">Open</button>
captureProject(card) {
this.projectSelected = project;
},
Show the selected project inside the modal
{{ projectSelected .title }}

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

What is the proper way to add same component everytime a button is clicked?

I am creating a form which needs to have a button and when clicked a new component which is only an input field would be added.
For example I have only one field to put in my address, but my adress is very long so I need 2 additional input fields. I click a button twice and two more components with the input appear.
What is a proper way to achieve this functionality?
Here is a simple solution to your question.
https://jsfiddle.net/RiddhiParekh/usd57zkb/6/
Template =>
<div id="vue-instance">
<button #click="addInput">Click to add input</button>
<br>
Address:
<div v-for="i in count">
<input type="text">
</div>
</div>
Script =>
var vm = new Vue({
el: '#vue-instance',
data: {
count:1
},
methods:{
addInput(){
this.count = this.count+1
}
}
});

Presentation Component in Vue2

I want to display all my forms and info pages in floating sidebox.
I don't want to copy and paste the floating sidebox html to all the places. So I want to create a component which acts as container to my forms or info pages.
This is the sample code for form.
<div class="floating-sidebox">
<div class="sidebox-header">
<div class="sidebox-center">
<h3 class="title">{{ title }}</h3>
</div>
</div>
<div class="sidebox-content">
<div class="sidebox-center">
<!-- This is the actual content. Above container code is common for all forms. -->
<vue-form-generator :schema="schema" :model="model" :options="{}"></vue-form-generator>
</div>
</div>
<div class="floating-sidebox-close" #click="cancel"></div>
</div>
<div class="floating-sidebox-overlay"></div>
In above code, I uses vue-form-generator to generate the form. The floating-sidebox elements are common for all forms and info pages. I want to abstract it by Presentational component.
How could I do it Vue2?
Define a component that wraps all your "floating-sidebox" components. You can access the "floating-sideboxes" via this.$children and use their title etc. as navigation placeholder. Since $children is an array you can easily represent the currently visible entity with and index
...
data: function() {
sideboxes: [],
currentIndex: null
},
...
mounted: function() {
this.sideboxes = this.$children;
this.currentIndex= this.$children.length > 0 ? 0 : null;
},
...
computed: {
current: function() {
return this.currentIndex ? this.sideboxes[this.currentIndex] : null;
}
}
You can then bind in the template of the wrapping view
<ul>
<li v-for="(sidebox, index) in sideboxes" #click="currentIndex = index"><!-- bind to prop like title here --></li>
</ul>
<div>
<!-- binding against current -->
</div>
JSfiddle with component https://jsfiddle.net/ptk5ostr/3/