I have this accordion and a collapse (as a component because I need to loop it). Here's the code:
accordion.vue
<div class="accordion col-lg-8 mx-auto" role="tablist">
<b-card no-body class="mb-1 py-2" v-for="each in questions" :key="each.id">
<Collapses v-bind:each="each"/>
</b-card>
</div>
Collapses.vue
<div>
<b-button #click="isActive = !isActive" role="tab" block v-b-toggle="'accordion-'+each.id">{{ each.question }}
<i class="float-right fa" :class="{ 'fa-plus': !isActive, 'fa-minus': isActive }"></i>
</b-button>
<b-collapse v-bind:id="'accordion-'+each.id" visible accordion="my-accordion" role="tabpanel">
<b-card-body>
<b-card-text>{{ each.answer }}</b-card-text>
</b-card-body>
</b-collapse>
</div>
<script>
export default {
props: ["each"],
data() {
return {
isActive: false
}
}
}
</script>
The accordion works fine except the icons. The accordion only shows one (expanded) collapse at a time. Whenever I click another collapse, the previous collapse closes, but the icon doesn't change (because I did not click it). How do I automatically change the icon whenever the collapse closes?
The b-collapse events include show and hide, which is emitted when the component state changes. Thus, you could use a v-on directive (or # for shortand) to bind an event listener in the template that sets the isActive flag accordingly:
<b-collapse #hide="isActive = false" #show="isActive = true">
Then you could remove the button-click handler as it's already taken care of by the event binding above.
demo
Your data is defined like method, try to do it in more clear way:
{
data: () => ({
isActive: false
})
}
Related
I have following problem. I have multiple b-buttons connected with b-collapse, all are starting visible (so they are open).
Now I need to make them none visible when the toggle from my parent.vue will be triggered - so after clicking on my button in my parent.vue I need to close all, that when I retrigger it and open the complete collapse all collapse in my child.vue should be closed.
How can I achieve that?
Parent.vue
<b-button class="col-5" v-b-toggle="'New' + item.id" variant="danger">
<Child :idParent="item.id"/>
Child.vue
<b-collapse visible :id="'New' + idParent">
<b-button v-b-toggle="Toggle1"></b-button>
<b-collapse visible id="Toggle1"></b-collapse>
<b-button v-b-toggle="Toggle2"></b-button>
<b-collapse visible id="Toggle2"></b-collapse>
<b-button v-b-toggle="Toggle3"></b-button>
<b-collapse visible id="Toggle3"></b-collapse>
</b-collapse>
One way to achieve this is to set a 'visible' boolean in the parent, toggled by the button press, and then pass this to the child to use in the button elements. For example:
Parent.vue
<b-button class="col-5" v-b-toggle="'New' + item.id" variant="danger" #click="!buttons_visible">
<Child :idParent="item.id" :buttons_visible="buttons_visible" />
Child.vue
<b-collapse visible :id="'New' + idParent">
<b-button v-b-toggle="Toggle1"></b-button>
<b-collapse :visible="buttons_visible" id="Toggle1"></b-collapse>
<b-button v-b-toggle="Toggle2"></b-button>
<b-collapse :visible="buttons_visible" id="Toggle2"></b-collapse>
<b-button v-b-toggle="Toggle3"></b-button>
<b-collapse :visible="buttons_visible" id="Toggle3"></b-collapse>
</b-collapse>
Running this piece of Bootstrap HTML and JS code below, I am struggling to not initiate the modal when clicking on the dropdown navi-link (e.g. Mark as Read).
<ul id="notification_items" class="navi navi-hover">
<!--begin::Item-->
<li id="1f7cbbe4-2345-486a-ab66-b05601f033a9" data-notification-id="1f7cbbe4-2345-486a-ab66-b05601f033a9" data-notification-subject="Some Subject" data-notification-message="And some message" class="notification_item cursor-pointer navi-item">
<div class="pr-10 pl-2 navi-link">
<div class="navi-label mx-5"><span class="label label-dot label-lg label-primary mt-n6"></span></div>
<div class="navi-text">
<span class="font-weight-bolder font-size-lg">Some Subject</span><span class="text-muted"> - And some message</span>
<div class="text-muted font-size-sm">3 hours ago</div>
</div>
<div class="notification_actions dropdown dropdown-inline d-none">
<i class="far fa-ellipsis-h"></i>
<div class="dropdown-menu dropdown-menu-sm dropdown-menu-right" style="">
<ul class="navi flex-column navi-hover py-2">
<li class="navi-item"><span data-notification-id="1f7cbbe4-2345-486a-ab66-b05601f033a9" class="notification_mark_as_read navi-link"><span class="navi-icon"><i class="fad fa-comment-check"></i></span><span class="navi-text">Mark as Read</span></span></li>
<div class="dropdown-divider"></div>
<li class="navi-item"><span class="navi-icon"><i class="far fa-trash-alt text-danger"></i></span><span class="navi-text text-danger">Delete</span></li>
</ul>
</div>
</div>
</div>
</li>
<!--end::Item-->
</ul>
And the simple JS code piece to show the modal when clicking on the <li>:
$(document).on("click", ".notification_item", function () {
$('#notification').modal('show');
});
I tried to e.stopPropagation(); when clicking on the .notification_actions Which works initially, but then obviously every other event won't work anymore.
You have to play with the arguments from the event parameter of the click event: target and currentTarget
target will return your DOM element on which the click occurred
initially
currentTarget will always be the DOM element on which your handlers
is attached -> in your case the li.notification_item
The solution is to identify the element the click occured on - target. There are a lot of ways - in your case you can detect if your click occured in the dropdown navi-link (.dropdown-menu) by traversing the DOM up until the root menu (#notification_items):
$(document).on("click", ".notification_item", function (e) {
// you traverse up to the root ul, and looking if you are in a dropdown menu
if ($(e.target).closest('.dropdown-menu', '#notification_items').length > 0) {
// You are in a drop down menu -> do nothing
} else {
// you are somewhere else -> trigger the modal
$('#notification').modal('show');
}
});
JSFiddle
P.S. this codes checks if you are in a dropdown-menu, you can use specific selector if you want to check for a specific dropdown.
I am basically making a windows xp themed portfolio and I am stumped with adding tabs to my modals. I am following a tutorial (https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/Tab_Role) and my issue is that when I call document.querySelectorAll('[role="tab"]') I get nothin. Using querySelector() returns null.
I was thinking that maybe it is because the tab isnt created yet until I open the modal but even then shouldn't the eventlistener check everytime something is added to the DOM? Maybe the scope is wrong but I get some errors when I put it inside the render or anywhere within the class.
Here is my component:
import React, { Component } from "react";
import ReactDOM from "react-dom";
import "xp.css/dist/XP.css";
import Draggable from 'react-draggable';
class AboutMeModal extends Component {
render() {
return (
<Draggable
axis="both"
handle=".tabs"
defaultPosition={{x: 40, y: -80}}
position={null}
grid={[25, 25]}
scale={1}
onStart={this.handleStart}
onDrag={this.handleDrag}
onStop={this.handleStop}>
<section className="tabs" style={{width:'calc(99%)'}}>
<menu role="tablist" aria-label="Sample Tabs">
<button id="tab-1" role="tab" aria-selected="true" aria-controls="tab-A" tabIndex="0">Tab A</button>
<button id="tab-2" role="tab" aria-selected="false" aria-controls="tab-B" tabIndex="-1">Tab B</button>
<button id="tab-3" role="tab" aria-selected="false" aria-controls="tab-C" tabIndex="-1">Tab C</button>
</menu>
<article role="tabpanel" id="tab-A" aria-labelledby="tab-1">
<h3>Tab Content</h3>
<p>
You create the tabs, you would use a <code>menu role="tablist"</code> element then for the tab titles you use a <code>button</code> with the <code>aria-controls</code> parameter set to match the relative <code>role="tabpanel"</code>'s element.
</p>
<p>
Read more at MDN Web docs - ARIA: tab role
</p>
</article>
<article role="tabpanel" id="tab-B" aria-labelledby="tab-2" hidden>
<h3>More...</h3>
<p>This tab contains a GroupBox</p>
</article>
<article role="tabpanel" id="tab-C" aria-labelledby="tab-3" hidden>
<h3>Tab 3</h3>
<p>Lorem Ipsum Dolor Sit</p>
</article>
</section>
</Draggable>
);
}
};
export default AboutMeModal;
window.addEventListener("DOMContentLoaded", () => {
console.log("event listener added");
const tabs = document.querySelectorAll('[role="tab"]'); //I never get the list of
//tabs back
const tabList = document.querySelector('[role="tablist"]');
// Add a click event handler to each tab
tabs.forEach(tab => {
console.log("here"); //my code never reaches here!
tab.addEventListener("click", changeTabs);
});
});
function changeTabs(e) {
console.log("change tabs");
const target = e.target;
const parent = target.parentNode;
const grandparent = parent.parentNode;
// Remove all current selected tabs
parent
.querySelectorAll('[aria-selected="true"]')
.forEach(t => t.setAttribute("aria-selected", false));
// Set this tab as selected
target.setAttribute("aria-selected", true);
// Hide all tab panels
grandparent
.querySelectorAll('[role="tabpanel"]')
.forEach(p => p.setAttribute("hidden", true));
// Show the selected panel
grandparent.parentNode
.querySelector(`#${target.getAttribute("aria-controls")}`)
.removeAttribute("hidden");
}
DOMContentLoaded is fired only once when the basic structure of the HTML is known to the browser. It's never called again after the initial load. You shall hook your events directly on the button tabs:
<button id="tab-1" onClick={changeTabs} role="tab" aria-selected="true" aria-controls="tab-A" tabIndex="0">Tab A</button>
<button id="tab-2" onClick={changeTabs} role="tab" aria-selected="false" aria-controls="tab-B" tabIndex="-1">Tab B</button>
<button id="tab-3" onClick={changeTabs} role="tab" aria-selected="false" aria-controls="tab-C" tabIndex="-1">Tab C</button>
I using tooltip like in exemple Toggle Tooltip:
<template>
<div class="text-center">
<div>
<b-button id="tooltip-button-1" variant="primary">I have a tooltip</b-button>
</div>
<div class="mt-3">
<b-button #click="show = !show">Toggle Tooltip</b-button>
</div>
<b-tooltip :show.sync="show" target="tooltip-button-1" placement="top">
Hello <strong>World!</strong>
</b-tooltip>
</div>
</template>
<script>
export default {
data: {
show: true
}
}
</script>
But I don’t need it to open on the hover.
Can anyone help to figure it out?
In the tooltip component there's a prop called triggers in which you could specify the event that could trigger the tooltip :
<b-tooltip :show.sync="show" target="tooltip-button-1" triggers="click" placement="top">
Hello <strong>World!</strong>
</b-tooltip>
I've created three simple buttons that will trigger three different bootstrap modal dialog. The modal dialogs are "Add Product", "Edit Product" and "Delete Product". Both the Add and Edit modal dialogs contain a form with two input elements, whereas the Delete modal dialog contains a simple text. I realise that my code becomes very messy and hard to maintain. Hence, I have the following question:
1) How do I reuse the modal dialog, instead of creating 3 separate dialogs?
2) How do I know which modal dialog has been triggered?
Update: I've developed a soultion where I will include conditional statements such as v-if, v-else-if and v-else to keep track of which button the user click. However, I still feel that there is a better solution to this. Can anyone help/advice me?
Below is my current code:
<template>
<div>
<b-button v-b-modal.product class="px-4" variant="primary" #click="addCalled()">Add</b-button>
<b-button v-b-modal.product class="px-4" variant="primary" #click="editCalled()">Edit</b-button>
<b-button v-b-modal.product class="px-4" variant="primary" #click="deleteCalled()">Delete</b-button>
<!-- Modal Dialog for Add Product -->
<b-modal id="product" title="Add Product">
<div v-if="addDialog">
<form #submit.stop.prevent="submitAdd">
<b-form-group id="nameValue" label-cols-sm="3" label="Name" label-for="input-horizontal">
<b-form-input id="nameValue"></b-form-input>
</b-form-group>
</form>
<b-form-group id="quantity" label-cols-sm="3" label="Quantity" label-for="input-horizontal">
<b-form-input id="quantity"></b-form-input>
</b-form-group>
</div>
<div v-else-if="editDialog">
<form #submit.stop.prevent="submitEdit">
<b-form-group id="nameValue" label-cols-sm="3" label="Name" label-for="input-horizontal">
<b-form-input id="nameValue" :value="productName"></b-form-input>
</b-form-group>
</form>
<b-form-group id="quantity" label-cols-sm="3" label="Quantity" label-for="input-horizontal">
<b-form-input id="quantity" :value="productQuantity">5</b-form-input>
</b-form-group>
</div>
<div v-else>
<p class="my-4">Are You Sure you want to delete product?</p>
</div>
</b-modal>
</div>
</template>
<script>
export default {
data() {
return {
productName: "T-Shirt",
productQuantity: 10,
addDialog: false,
editDialog: false,
deleteDialog: false
};
},
methods: {
addCalled() {
this.addDialog = true;
},
editCalled() {
this.editDialog = true;
this.addDialog = false;
this.deleteDialog = false;
},
deleteCalled() {
this.deleteDialog = true;
this.addDialog = false;
this.editDialog = false;
}
}
};
</script>
<style>
</style>
As already mentionned, I would have use slots and dynamic component rendering to accomplish what you're trying to do in a cleaner way.
See snippet below (I didn't make them modals as such but the idea is the same).
This way, you can have a generic modal component that deals with the shared logic or styles and as many modalContentsub-components as needed that are injected via the dedicated slot.
Vue.component('modal', {
template: `
<div>
<h1>Shared elements between modals go here</h1>
<slot name="content"/>
</div>
`
});
Vue.component('modalA', {
template: `
<div>
<h1>I am modal A</h1>
</div>
`
});
Vue.component('modalB', {
template: `
<div>
<h1>I am modal B</h1>
</div>
`
});
Vue.component('modalC', {
template: `
<div>
<h1>I am modal C</h1>
</div>
`
});
new Vue({
el: "#app",
data: {
modals: ['modalA', 'modalB', 'modalC'],
activeModal: null,
},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<button v-for="modal in modals" #click="activeModal = modal"> Open {{ modal }} </button>
<modal>
<template slot="content">
<component :is="activeModal"></component>
</template>
</modal>
</div>
Update
Now, You might think how will you close your modal and let the parent component know about it.
On click of button trigger closeModal for that
Create a method - closeModal and inside commonModal component and emit an event.
closeModal() {
this.$emit('close-modal')
}
Now this will emit a custom event which can be listen by the consuming component.
So in you parent component just use this custom event like following and close your modal
<main class="foo">
<commonModal v-show="isVisible" :data="data" #close- modal="isVisible = false"/>
<!-- Your further code -->
</main>
So as per your question
A - How do I reuse the modal dialog, instead of creating 3 separate dialogs
Make a separate modal component, let say - commonModal.vue.
Now in your commonModal.vue, accept single prop, let say data: {}.
Now in the html section of commonModal
<div class="modal">
<!-- Use your received data here which get received from parent -->
<your modal code />
</div>
Now import the commonModal to the consuming/parent component. Create data property in the parent component, let say - isVisible: false and a computed property for the data you want to show in modal let say modalContent.
Now use it like this
<main class="foo">
<commonModal v-show="isVisible" :data="data" />
<!-- Your further code -->
</main>
The above will help you re-use modal and you just need to send the data from parent component.
Now second question will also get solved here How do I know which modal dialog has been triggered?
Just verify isVisible property to check if modal is open or not. If isVisible = false then your modal is not visible and vice-versa