Bootstrap 5 Dropdown menu broken when using Modal with Nuxt - vue.js

Using Nuxt (vueJs) and Bootstrap 5, I have a dropdown menu in a NavBar :
<nav class="navbar navbar-expand-lg navbar-dark bg-primary pb-xl-5 pt-xl-5">
<div class="container-fluid">
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav me-xl-5">
<li class="nav-item dropdown me-2 ms-2">
<a
class="nav-link dropdown-toggle text-uppercase text-white"
href="#"
id="navbarDropdown"
role="button"
data-bs-toggle="dropdown"
aria-expanded="false"
>Select language</a>
<ul class="dropdown-menu" aria-labelledby="navbarDropdown">
<li><a class="dropdown-item text-uppercase" href="#">EN</a></li>
<li><a class="dropdown-item text-uppercase" href="#">ES</a></li>
<li><a class="dropdown-item text-uppercase" href="#">FR</a></li>
</ul>
</li>
</ul>
</div>
</div>
</nav>
Everything works fine until I try to use Bootstrap's Modal programmatically :
<script>
import { Modal as bModal } from 'bootstrap';
export default {
...
mounted() {
this.modal = new bModal(document.getElementById('modal-name'));
}
...
};
</script>
The modal works fine but the dropdown menu is broken. As soon as I remove the use of the modal it works again.
I'm not sure how I should be using bootstrapjs :(
Here is the config of bootstrap in nuxt :
import { defineNuxtConfig } from '#nuxt/bridge'
export default defineNuxtConfig({
...
modules: [
'#nuxtjs/axios',
'#nuxtjs/auth-next',
'#nuxtjs/i18n',
'#nuxtjs/style-resources',
'#nuxtjs/fontawesome'
],
fontawesome: {
proIcons: {
light: true
}
},
buildModules: [
'#nuxtjs/fontawesome',
],
styleResources: {
scss: [
'#/assets/scss/_variables.scss',
'#/assets/scss/_utilities.scss'
]
},
plugins: [
{src:'~/static/js/bootstrap.bundle.min.js', mode:'client'}
],
...
});

for a dropdown menu, you need to import Dropdown as well as for a modal window
main.js
import { Modal, Dropdown } from "bootstrap";
window.Modal = Modal;
window.Dropdown = Dropdown;
modal.vue
mounted() {
modal = new Modal(document.getElementById("modal"));
modal.show();
}
menu.vue
mounted() {
let dropdownElementList = [].slice.call(
document.querySelectorAll(".dropdown-toggle")
);
let dropdownList = dropdownElementList.map(function (dropdownToggleEl) {
dropDown = new Dropdown(dropdownToggleEl);
});
}

Related

Vue3 is cutting properties from custom Elements used in App

i want to include an external library in my Vue3 app. But when i try to inlcude them in my app, but some properties the element uses get cutted by Vue. Maybe someone can explain me this behaviour and how to fix this.
The attribute i am pointing on is "secondary"
vite.config.ts:
...
plugins: [
vue({
template: {
compilerOptions: {
// treat all tags with a dash as custom elements
isCustomElement: (tag) => {
return tag.includes("-");
},
},
},
}),
],
...
index.html
<div class="container">
<atom-button secondary>Test</atom-button>
<my-vue-comp></my-vue-comp>
</div>
in my component:
<template>
<div ref="root">
<atom-button secondary>Test</atom-button>
</div>
</template>
output in DOM:
<div class="container">
<atom-button secondary="" role="button" tabindex="0" type="button" aria-disabled="false">Test</atom-button>
<my-vue-comp>
<div>
<atom-button role="button" tabindex="0" type="button" aria-disabled="false">Test</atom-button>
</div>
</my-vue-comp>
</div>

Scroll to section from child component in Vue.js

I am trying to understand how to emit an event from a child component to the parent that scrolls to the correct section via a ref or id. So for example the navigation menu is a child component with links. when I click a link, the corresponding section in the parent scrolls into view. I am coming from React so I am a bit thrown off. How do I pass parameters up to scroll to correct ref or id? basically the way anchorlinks act.
Here is what I got so far.. I am not sure I should be using links , I might need buttons since i'm not navigating pages.
Navigation.vue (Child)
<template>
<div class="navigation">
<img class="logo" alt="Vue logo" src="../assets/logo.png" />
<nav>
<ul>
<li>
<a #click="goSection" href="/">Contact</a>
</li>
<li>
Projects
</li>
<li>
Skills
</li>
<li>
Education
</li>
</ul>
</nav>
</div>
</template>
<script>
export default {
name: "Navigation",
//I want to emit an event in the parent where the page section scrolls in to view//
methods: {
goSection() {
this.$emit("go-section", this.value);
},
},
};
</script>
App.vue (Parent)
<template>
<div id="app">
<Navigation #go-section="goToSection" />
<section class="section" ref="projects">
<p>Projects</p>
</section>
<section class="section" ref="skills">
<p>Skills</p>
</section>
<section class="section" ref="contact">
<p>Contacts</p>
</section>
</div>
</template>
<script>
import Navigation from "./components/Navigation.vue";
export default {
name: "App",
components: {
Navigation,
},
methods: {
goToSection(event, value) {
var element = this.$refs[(event, value)];
var top = element.offsetTop;
window.scrollTo(0, top);
},
},
};
</script>
You can use a tag but prevent default link behavior, and pass ref to your goSection method :
Vue.component('navigation', {
template: `
<div class="navigation">
<img class="logo" alt="Vue logo" src="../assets/logo.png" />
<nav>
<ul>
<li>
<a #click.prevent="goSection('contact')" href="/">Contact</a>
</li>
<li>
Projects
</li>
<li>
Skills
</li>
<li>
Education
</li>
</ul>
</nav>
</div>
`,
methods: {
goSection(val) {
this.$emit("go-section", val);
},
},
})
new Vue({
el: '#demo',
methods: {
goToSection(value) {
const top = this.$refs[(value)].offsetTop;
window.scrollTo({
top: top,
left: 0,
behavior: 'smooth'
});
},
},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="demo">
<navigation #go-section="goToSection"></navigation>
<section class="section" ref="projects">
<p>Projects</p>
</section>
<section class="section" ref="skills">
<p>Skills</p><p>Skills</p><p>Skills</p><p>Skills</p><p>Skills</p><p>Skills</p><p>Skills</p><p>Skills</p><p>Skills</p><p>Skills</p><p>Skills</p><p>Skills</p><p>Skills</p><p>Skills</p><p>Skills</p><p>Skills</p><p>Skills</p>
</section>
<section class="section" ref="contact">
<p>Contacts</p>
</section>
</div>
You can emit an event with parameter (section ref for example) in the child component:
<a #click="$emit('go-section', 'contact')" href="/">Contact</a>
<a #click="$emit('go-section', 'projects')" href="/">Projects</a>
And then use this parameter in the parent's listener method
// template
<Navigation #go-section="goToSection" />
// js
goToSection(sectionRef) {
var element = this.$refs[sectionRef];
var top = element.offsetTop;
window.scrollTo(0, top);
},

Use event #change and #click to get data pull-and-drop in vuejs?

Template:
<div class="row">
<div class="col-lg-12 mt-2">
<draggable
class="list-group"
tag="ul"
v-model="imgList.top"
v-bind="dragOptions"
>
<transition-group type="transition">
<li
class="list-group-item" style="align-items: center;display: flex;padding: 12px 15px; font-size: 13px"
v-for="record in imgList.top"
:key="record._id"
#click="onClickValue"
<!-- #change="onChangeValue" -->
>
<div class="col-lg-12">
<img class="w-100" :src="getImageURL(record.image)" alt />
</div>
</li>
</transition-group>
</draggable>
</div>
</div>
<script>
import draggable from "vuedraggable";
export default {
components: {
draggable
},
data() {
return {
imgList: {
top: [
{_id: '12354356444433', image: '09jgg24.jpg'},
{_id: '12354356442211', image: '09jaef2.jpg'},
]
}
}
},
methods: {
onClickValue() {
console.log('ok') // No results received
},
//onChangeValue() {
// console.log('ok') // No results received
//},
}
}
</script>
I am doing drag-and-drop in vuejs. I hold the image and drag and drop it.. Now I want when I drag an image to catch the #click or #change event. I tried putting #click and #change in my code. But when I drag and drop nothing appears? Please show me to catch that event. Thank you

Vue.js 2.6.12 with Bootstrap 5.1.0 - Issue with manual component initiation

Following up on my old thread Vue.js (2.x) with Bootstrap 5
I keep having issues when creating a bootstrap component wrapper in Vue.
The idea is to create a wrapper for each Bootstrap component that I want to use (not all of them).
The issue happened when I tried to create a bootstrap.Dropdown wrapper. The dropdown menu is shown properly when clicked, but it didn't auto-close when clicking outside (somewhere else).
<template>
<div ref="el" class="dropdown">
<button
class="btn btn-primary dropdown-toggle"
type="button"
data-bs-toggle="dropdown"
aria-expanded="false"
>
Select...
</button>
<ul class="dropdown-menu">
<slot></slot>
</ul>
</div>
</template>
<script>
import { Dropdown } from 'bootstrap';
export default {
name: 'BootstrapDropdown',
data() {
return {
dropdown: null,
};
},
mounted() {
const { el } = this.$refs;
this.dropdown = new Dropdown(el, {
autoClose: true,
});
['show', 'shown', 'hide', 'hidden'].forEach((e) => {
el.addEventListener(`${e}.bs.dropdown`, () => {
this.$emit(e);
});
});
},
};
</script>
Any ideas are welcomed. Thanks in advance.
I think it's not working because the Dropdown instance needs to be set on the trigger element, not the container. Change the "el" ref to the trigger button...
<template>
<div class="dropdown">
<button
ref="el"
class="btn btn-primary dropdown-toggle"
type="button"
data-bs-toggle="dropdown"
aria-expanded="false"
>
Select...
</button>
<ul class="dropdown-menu">
<slot></slot>
</ul>
</div>
</template>
Demo

Laravel Vuejs2 missing param for named route

I am having a hard time trying to isolate the console error.
My code is as follows:
myside.blade.php has:
<div class="nav-container">
<ul class="nav nav-icons justify-content-center" role="tablist">
<li class="nav-item">
<div>
<router-link class="nav-link text-primary" :to="{ name: 'property', params: { customerId: {{ $property_data[0]->listingId }} }}" #click.native="isVisible = !isVisible">
<i class="nc-icon nc-key-25"></i>
<br> Property
</router-link>
</div>
</li>
<li class="nav-item">
<router-link class="nav-link text-primary" :to="{ name: 'booking' }" #click.native="isVisible = !isVisible">
<i class="nc-icon nc-chart-pie-36"></i>
<br> Bookings
</router-link>
</li>
<li class="nav-item">
<a class="nav-link text-primary" href="" role="tab">
<i class="nc-icon nc-preferences-circle-rotate"></i>
<br> Performance
</a>
</li>
<li class="nav-item">
<a class="nav-link text-primary" href="" role="tab">
<i class="nc-icon nc-money-coins"></i>
<br> Revenue
</a>
</li>
<li class="nav-item">
<a class="nav-link text-primary" href="" role="tab">
<i class="nc-icon nc-layers-3"></i>
<br> Integration
</a>
</li>
</ul>
</div>
<property-page :customer-Id="{{ $property_data[0]->listingId }}" v-if="isVisible"></property-page>
<router-view></router-view>
My routes.js file:
import VueRouter from 'vue-router';
let routes = [
{
path: '/property/:customerId',
name: 'property',
component: require('./components/PropertyPage'),
props: true
},
{
path: '/booking',
name: 'booking',
component: require('./components/BookingPage')
}
];
export default new VueRouter({
routes,
linkActiveClass: 'nav-link text-success'
});
my app.js file:
import Vue from 'vue';
import VueRouter from 'vue-router';
Vue.use(VueRouter);
import router from './routes';
import PropertyPage from './components/PropertyPage.vue';
import BookingPage from './components/BookingPage.vue';
new Vue({
el: '#root',
router,
data: {
NavPrimaryClass: 'nav-link text-primary',
NavClass: 'nav-link',
isVisible: true
},
components: {
'property-page': PropertyPage,
'booking-page': BookingPage
}
})
my PropertyPage.vue file:
<template>
<div>
<div class="ajax-loader">
<img src="/loader/ajax-loader.gif" width="300px" v-if="loading" />
</div>
<div v-if="propertyDataCheck">
</div>
</div>
</template>
<script>
import moment from 'moment';
import axios from 'axios';
export default {
props: {
customerId: {
type: Integer,
required: true,
default: 0
}
},
data() {
return {
propertyData: [],
loading: false
}
},
computed: {
propertyDataCheck () {
return this.propertyData.length;
}
},
mounted() {
this.loading = true;
axios.get('/ajax/propertydata/' + this.customerId)
.then(function(response) {
this.propertyData = response.data;
this.loading = false;
}.bind(this))
.catch(function() {
this.loading = false;
}.bind(this));
}
}
</script>
<style>
.ajax-loader {
position: absolute;
left: 40%;
top: 15%;
margin-left: -32px; /* -1 * image width / 2 */
margin-top: -32px; /* -1 * image height / 2 */
}
</style>
The end result, a console error which I have spent hours trying to work out where it is coming from.
Error:
[vue-router] missing param for named route "property": Expected "customer" to be defined
I needed a component to be loaded upon page load (outside of vue-router) which is why I have <property-page :customer-Id="{{ $property_data[0]->listingId }}" v-if="isVisible"></property-page> tags and is bound to a method which will switch upon a router-link click (in case you were wondering).
My understanding of this error is the variable for the first router-link which is generated from a Laravel model DB query {{ $property_data[0]->listingId }} should be checked within a v-if wrapped around the vue-router? I have also done this to no avail.
Any help would be much appreciated!
Believe it or not, you need to use strict kebab-case (all lower case!) html-properties in vue-templates. Change customer-Id to customer-id
<property-page :customer-Id=... // does not get mapped to this.customerId
<property-page :customer-id=... // does get mapped to this.customerId, yay!