Vuejs Display certain div when radio button is checked - vue.js

How can I display divA when radio button A is checked and display divB when radio button B is checked?
<b-form-radio value="A">A</b-form-radio>
<b-form-radio value="B">B</b-form-radio>
<!-- divA -->
<div>
...
</div>
<!-- divB -->
<div>
...
</div>

Assuming your custom radio control (b-form-radio) supports v-model, you can do this:
<b-form-radio v-model="selected" value="A">A</b-form-radio>
<b-form-radio v-model="selected" value="B">B</b-form-radio>
<!-- divA -->
<div v-show="selected === 'A'">
...
</div>
<!-- divB -->
<div v-show="selected === 'B'">
...
</div>
Additionally, in your component data function, you need to make selected as a reactive property:
{
data() {
return {
// Initially selected will be null
// to hide both the div
selected: null
}
}
}
In summary, it is the combination of v-show directive together with v-model. Also, you can use v-if instead of v-show if you do not want other div to be rendered at all on the page.

Related

Vuejs Unknown custom element (Imported component error)

I basically import 2-3 components in a parent component and based on the button clicks I simply hide and show some components. In every component I have Back and Next buttons, when they clicked, I show different components in different situations. Here's a simple example of one my components;
<template>
<div>
<transition name="fade" appear>
<div class="justify-center">
<form #submit.prevent="submitFormTest" v-if="!back && !configuration">
Bunch of code here is irrelevant with my question...
<!-- Buttons -->
<div class="text-center mt-4">
<button #click="back = true" class="btn-w-orange mr-2" type="button">Back</button>
<button #click="nextButton" class="btn-w-orange" type="button">Next</button>
</div>
</form>
<modalOpMode v-if="back"></modalOpMode>
<modalConnection v-if="configuration && !back"></modalConnection >
</div>
</transition>
</div>
</template>
and here is my script;
<script>
import modalConnection from "./modalConnection";
import modalOpMode from "./modalOpMode "
export default {
data(){
return {
configuration: false,
back: false
}
},
components: {
modalOpMode,
modalConnection,
},
methods: {
nextButton(){
this.configuration = true;
},
},
}
</script>
I statically imported two other components to display when back and next button is clicked. Everything is okay when I go for Next button for ALL MY COMPONENTS. (There are other components that I get the same error.). But the problem occurs when I click the back button. When Next is clicked, the form is hidden and modalConnection is visible as it should but when Back is clicked, I only see a blank page because the modalOpMode gives me the following error;
[Vue warn]: Unknown custom element: <modalOpMode> - did you register the component correctly? For recursive components, make sure to provide the "name" option.
I'm pretty sure that I import the component in the correct way, the path, the component name, the usage, everything is normal because I do that with the other one modalConnection and that one works all good. If you want to check my modalOpMode component, here it is;
<template>
<transition name="fade" appear>
<div>
<form #submit.prevent="submitFormTest" v-if="configuration == ''">
There are four radio buttons that bound with v-model to change the value of configuration variable
</form>
<!-- Modal Comp1-->
<modalComp1 v-if="configuration == 'comp1'"></modalComp1>
<!-- Modal Comp2 -->
<modalComp2 v-if="configuration == 'comp2'"></modalComp2>
<!-- Modal Comp3-->
<modalComp3 v-if="configuration == 'comp3'"></modalComp3>
<!-- Modal Comp4 -->
<modalComp4 v-if="configuration == 'comp4'"></modalComp4 >
</div>
</transition>
</template>
In here I imported 4 other components and bound them to a variable called configuration. When the variable is equal to something like comp1 or comp2 as above, I simply display the relevant component and hide the other content.
As I said before, when I click the next button and show the components, it's all good, but I can't go back, it gives me the above error. What am I doing wrong or what am I missing here? Thanks in advance
In Components you have modalOperatingMode instead of modalOpMode

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.

Vue dynamic ref show dropdown

I am using VueJS and want to use right click event to display the dropdown list. But I have multiple dropdown so need to use dynamic ref. How can I show the dropdown that I want when right click?
<div class="info" #contextmenu="handler($event)">
...
<b-dropdown size="sm" text="…" variant="transparent" no-caret :ref="`dropdown-${id}`" :item1="this.item1" :item2="this.item2">
<b-dropdown-item #click="showDetails(item1, item2)">Send</b-dropdown-item>
</b-dropdown>
</div>
handler(e) {
this.$ref.dropdown.show();
e.preventDefault();
}
You can send another parameter to the handler function that will be the value of the $ref you want to open:
<div class="info" #contextmenu="handler($event, id)">
...
<b-dropdown size="sm" text="…" variant="transparent" no-caret :ref="`dropdown-${id}`" :item1="this.item1" :item2="this.item2">
<b-dropdown-item #click="showDetails(item1, item2)">Send</b-dropdown-item>
</b-dropdown>
</div>
handler(e, id) {
this.$ref[`dropdown-${id}`].show();
e.preventDefault();
}
By the way you can use .prevent modifier instead of using the e.preventDefault:
<div class="info" #contextmenu.prevent="handler(id)">
// your methods
handler(id) {
this.$ref[`dropdown-${id}`].show();
}
You could use e.target to get the element (button) you just clicked and after that use a switch statement to determine which dropdown to show.

Vue: toggling between two instances of the same component doesn't update the view

I have a setup in a Vue-powered UI, where the user can toggle the contents of a certain div between several options, and two of those options happen to be instances of the same child component (with different properties passed in).
Everything works fine when displaying any given content page for the first time, or when toggling between two unrelated content pages. However when toggling between the two pages which both use the same child component, the div content doesn't get updated.
In code it looks (greatly simplified) like this:
Parent component
<template>
<div>
<!-- toggle buttons -->
<div class="page-button" #click="page=1">About</div>
<div class="page-button" #click="page=2">Dog List</div>
<div class="page-button" #click="page=3">Cat List</div>
<!-- page content -->
<div v-if="page===1">some plaintext here...</div>
<div v-if="page===2">
<childComponent :state="state" listName="dogs" />
</div>
<div v-if="page===3">
<childComponent :state="state" listName="cats" />
</div>
<!-- rest of file omitted -->
childComponent.vue
<template>
<div>
<template v-for="(item, index) in items">
<div>{{ index }}: {{ item.label }}</div>
<!-- etc.. -->
</template>
</div>
</template>
<script>
module.exports = {
props: ['state', 'listName'],
data: function () {
return {
items: this.state.lists[this.listName],
}
},
}
</script>
In the above, state is a global state object that all components have access to, with state.lists.dogs and state.lists.cats being regular arrays.
When the UI initializes with page set to 2 or 3, everything works correctly - the dog list shows for page 2, and the cat list shows for page 3. Likewise, when I click page 2, then page 1, then page 3, everything is fine. However when toggling back and forth between page 2/3, the vue doesn't re-render the child component.
I assume it's possible to work around this by changing the underlying data structure or by binding the child component differently. But is there a straightforward way to make Vue re-render the component as expected?
I guess what you see is Vue trying to optimize rendering by reusing existing component instance. Add key attribute on your childComponent with different values...
<!-- page content -->
<div v-if="page===1">some plaintext here...</div>
<div v-if="page===2">
<childComponent :state="state" listName="dogs" key="dogs" />
</div>
<div v-if="page===3">
<childComponent :state="state" listName="cats" key="cats" />
</div>
<!-- rest of file omitted -->
Other solution (and much better IMHO) is to make your component "reactive" to prop changes - instead of using props to initialize the data() (which is "one time" thing - data() is executed only once when component is created), use computed
module.exports = {
props: ['state', 'listName'],
computed: {
items() {
return this.state.lists[this.listName]
}
},
}
You can use v-show if you just want to render it before hand. Its more costly but it should work without any issues.
<template>
<div>
<!-- toggle buttons -->
<div class="page-button" #click="page=1">About</div>
<div class="page-button" #click="page=2">Dog List</div>
<div class="page-button" #click="page=3">Cat List</div>
<!-- page content -->
<div v-show="page===1">some plaintext here...</div>
<div v-show="page===2">
<childComponent :state="state" listName="dogs" />
</div>
<div v-show="page===3">
<childComponent :state="state" listName="cats" />
</div>
<!-- rest of file omitted -->

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/