BootstrapVue - Bootstrap CSS is being added to my Vue app multiple times - vue.js

I'm using BootstrapVue and have set it up as per the docs. What I'm seeing though is that for every component in my Vue app that uses a BootstrapVue component, I'm getting the Bootstrap embedded in my rendered HTML. In this case, I have 27 instances of the same stylesheet being added.
I can't see where the issue is.
Here's my main.js file
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import { BRow, BCol, BContainer } from 'bootstrap-vue'
import {InlineSvgPlugin} from "vue-inline-svg/dist/vue-inline-svg";
import VueSvgIconPolyFill from "#yzfe/vue-svgicon-polyfill";
Vue.config.productionTip = false
Vue.use(VueSvgIconPolyFill);
Vue.use(BCol, BRow, BContainer)
Vue.use(require('vue-moment'))
Vue.use(InlineSvgPlugin)
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
And here's an example of a Vue component using BootstrapVue
<template>
<section class="latest-news">
<b-container>
<b-row class="latest-news__title-bar">
<b-col class="d-flex align-items-center" :sm="12" :md="10">
<h2>
{{blockTitle}}
<a
href="#"
title="Collapse this section"
:class="`collapse-control ` + (isCollapsed ? 'collapse-control--collapsed' : '')"
#click.prevent="isCollapsed = !isCollapsed"
v-html="(isCollapsed) ? 'Show' : 'Hide'"
></a>
</h2>
</b-col>
<b-col class="d-flex justify-space-around" v-if="showSearchBar">
<ContentSearchBar title="Search news"/>
</b-col>
<b-col class="d-flex justify-content-end">
<Toolbar controls="news" :display-sorter="false" v-if="!isCollapsed && news.length"/>
</b-col>
</b-row>
<b-row v-if="!isCollapsed">
<b-col
cols="12"
:sm="smCols"
:md="mdCols"
v-for="(item, index) in news"
:key="`news` + index"
:class="((index < numberNewsItems) || !numberNewsItems) ? `latest-news__col` : ``"
>
<NewsItem
v-if="(index < numberNewsItems) || numberNewsItems === null"
:show-excerpt="showExcerpt"
:news="item"
:item-index="index"
:format="newsListFormat"
/>
</b-col>
</b-row>
<b-row v-if="news && news.length === 0 && isSearch && !isCollapsed">
<b-col cols="12">There are no news item results for your search term "{{$route.params.query}}"</b-col>
</b-row>
</b-container>
</section>
</template>
<script>
import NewsItem from "./NewsItem";
import ContentSearchBar from "./ContentSearchBar";
import Toolbar from "./Toolbar";
import store from '../store';
import {mapGetters} from 'vuex';
import NewsService from '../services/NewsService'
export default {
name: "LatestNews",
store: store,
props: {
showExcerpt: {
default: true,
},
showSearchBar: {
default: false,
},
numberNewsItems: {
default: null
},
'isSearch': {
default: false
},
},
data() {
return {
news: [],
isCollapsed: false
}
},
mounted() {
if (this.isSearch) {
this.searchNews()
} else {
this.getNews()
}
},
methods: {
async getNews() {
const response = await NewsService.all()
this.news = response.data
},
async searchNews() {
let query = this.$route.params.query;
const response = await NewsService.search(query);
this.news = response.data.results
}
},
components: {Toolbar, ContentSearchBar, NewsItem},
computed: {
blockTitle() {
if (this.isSearch) {
return 'News search results for "' + this.$route.params.query + '"'
} else {
return 'Latest News'
}
},
...mapGetters([
'newsListFormat'
]),
smCols() {
if (this.newsListFormat === 'list') {
return '12'
} else {
return '6'
}
},
mdCols() {
if (this.newsListFormat === 'list') {
return '12'
} else {
return '3'
}
},
}
}
</script>
<style lang="scss">
.latest-news {
&__col {
margin-bottom: 24px;
}
&__title-bar {
margin-bottom: 20px;
h2 {
margin-bottom: 0;
}
}
}
</style>
This is how Chrome dev tools shows the HTML when yarn serveed

This has been resolved :)
The issue was caused by including the bootstrap scss files in an scss file that was #imported into Vue's main.js so that Bootstrap's mixins could be used on single file component's styles
Removing the Bootstrap scss imports and importing the bootstrap css files in the main.js file fixed the issue.

Related

VUE.JS 3 Changing boolean value of one sibling component from another

I have two components - component A and component B that are siblings.
I need to change the boolean value inside of Component-A from the Watcher in Component-B.
Component A code:
<template>
<div></div>
</template>
<script>
export default {
data() {
return {
editIsClicked: false,
}
}
}
</script>
Component B code:
<template>
<v-pagination
v-model="currentPage"
:length="lastPage"
:total-visible="8"
></v-pagination>
</template>
<script>
export default {
props: ["store", "collection"],
watch: {
currentPage(newVal) {
this.paginatePage(newVal);
// NEED TO TOGGLE VALUE HERE - when i switch between pages
},
},
},
};
</script>
The Vue Documentation proposes communicating between Vue Components using props and events in the following way
*--------- Vue Component -------*
some data => | -> props -> logic -> event -> | => other components
*-------------------------------*
It's also important to understand how v-model works with components in Vue v3 (Component v-model).
const { createApp } = Vue;
const myComponent = {
props: ['modelValue'],
emits: ['update:modelValue'],
data() {
return {
childValue: this.modelValue
}
},
watch: {
childValue(newVal) {
this.$emit('update:modelValue', newVal)
}
},
template: '<label>Child Value:</label> {{childValue}} <input type="checkbox" v-model="childValue" />'
}
const App = {
components: {
myComponent
},
data() {
return {
parentValue: false
}
}
}
const app = createApp(App)
app.mount('#app')
<div id="app">
Parent Value: {{parentValue}}<br />
<my-component v-model="parentValue"/>
</div>
<script src="https://unpkg.com/vue#3/dist/vue.global.prod.js"></script>
I have made a new playground. Hope it helps you now to understand the logic.
You can store data in the main Vue App instance or use a Pinia store for it.
But I would suggest you to start without Pinia to make your app simpler. Using Pinia will make your App much more complicated and your knowledge of Vue seems to be not solid enough for that.
const { createApp } = Vue;
const myComponentA = {
props: ['editIsClicked', 'currentPage'],
template: '#my-component-a'
}
const myComponentB = {
emits: ['editIsClicked'],
data() {
return {
currentPage: 1,
}
},
watch: {
currentPage(newVal) {
this.$emit('editIsClicked', newVal)
}
},
template: '#my-component-b'
}
const App = {
components: {
myComponentA, myComponentB
},
data() {
return {
editIsClicked: false,
currentPage: 1
}
},
methods: {
setEditIsClicked(val) {
this.editIsClicked = true;
this.currentPage = val;
}
}
}
const app = createApp(App)
app.mount('#app')
#app { line-height: 2; }
.comp-a { background-color: #f8f9e0; }
.comp-b { background-color: #d9eba7; }
<div id="app">
<my-component-a :edit-is-clicked="editIsClicked" :current-page="currentPage"></my-component-a>
<my-component-b #edit-is-clicked="setEditIsClicked"></my-component-b>
</div>
<script src="https://unpkg.com/vue#3/dist/vue.global.prod.js"></script>
<script type="text/x-template" id="my-component-a">
<div class="comp-a">
My Component A: <br />editIsClicked: <b>{{editIsClicked}}</b><br/>
currentPage: <b>{{currentPage}}</b><br/>
</div>
</script>
<script type="text/x-template" id="my-component-b">
<div class="comp-b">
My Component B: <br />
<label>CurrentPage:</label> <input type="number" v-model="currentPage" />
</div>
</script>

erro while using bootstrap-vue in vue3 project

I am working on bootstrap-vue but I cannot add the functionalities of bootstrap. the code of main.js is
import { createApp } from 'vue'
import App from './App.vue'
import "bootstrap/dist/css/bootstrap.css"
import"bootstrap-vue/dist/bootstrap-vue.css"
createApp(App).mount('#app')
and the code of Helloworld.vue file is
<template>
<div>
<h1>hello from helloworld</h1>
<h5>Pressed and un-pressed state</h5>
<b-button :pressed="true" variant="success">Always Pressed</b-button>
<b-button :pressed="false" variant="success">Not Pressed</b-button>
<h5 class="mt-3">Toggleable Button</h5>
<b-button :v-bind="myToggle" variant="primary">Toggle Me</b-button>
<p>Pressed State: <strong>{{ myToggle }}</strong></p>
<h5>In a button group</h5>
<b-button-group size="sm">
<b-button
v-for="(btn, idx) in buttons"
:key="idx"
:v-bind="btn.state"
variant="primary"
>
{{ btn.caption }}
</b-button>
</b-button-group>
<p>Pressed States: <strong>{{ btnStates }}</strong></p>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
props: {
msg: String
},
data() {
return {
myToggle: false,
buttons: [
{ caption: 'Toggle 1', state: true },
{ caption: 'Toggle 2', state: false },
{ caption: 'Toggle 3', state: true },
{ caption: 'Toggle 4', state: false }
]
}
},
computed: {
btnStates() {
return this.buttons.map(btn => btn.state)
}
}
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h3 {
margin: 40px 0 0;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
</style>
what I do to implement the bootstrap-vue in this project
In your main.js file you are missing bootstrap-vue import. Try the below for vue3, it worked for me. Please note: create a const for app.
import { createApp } from 'vue'
import BootstrapVue3 from 'bootstrap-vue-3'
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'
import App from './App.vue'
const app = createApp(App)
app.use(BootstrapVue3)
app.mount('#app')

Vue.js 2 data not displayed in the template

I'm using vue.js 2 / vue-cli with axios. I'm facing a problem with the display of my posts (in the wall). I've made my axios request, have gotten my data (in the console), I've written my template and ... nothing was displayed ... I really appreciate your help
my template :
<template>
<v-layout column>
<v-flex xs4>
<panel title="The Wall">
<p v-for="post in posts" :key="post.index">{{ post.title }} - {{ post.content }}</p>
</panel>
</v-flex>
</v-layout>
</template>
my script :
<script>
import Panel from '../components/Panel'
import PostService from "../services/PostService.js";
export default {
components: {
Panel
},
data() {
return {
posts: null
}
},
async mounted() {
this.posts = (await PostService.getAllPosts()).data;
}
}
</script>
Add a data property called cidItem for example and bind it to your props as follows
<template>
<div id="app" class="container" :cid="cidItem">
<Images :cid="cidItem" />
<Video :cid="cidItem" />
<TextType :cid="cidItem" />
<Card :cid="cidItem" />
</div>
</template>
<script>
import axios from 'axios';
import Images from "./components/Images";
import Video from "./components/Video";
import TextType from "./components/TextType";
import Card from "./components/Card";
export default {
name: 'app',
props: ["cid"],
components: {
Images,
Video,
TextType,
Card
},
mounted() {
axios({method: "GET", "url": this.contentDeliveryUrl}).then(result => {
// eslint-disable-next-line
this.content = amp.inlineContent(result.data)[0];
console.log(this.content)
}, error => {
console.error(error);
});
},
data() {
return {
contentDeliveryUrl: 'https://c1.adis.ws/cms/content/query?fullBodyObject=true&query=%7B%22sys.iri%22%3A%22http%3A%2F%2Fcontent.cms.amplience.com%2F${this.cid}%22%7D&scope=tree&store=testing',
content: [],
cidItem:'7e4301de-9c6e-4fab-9e68-3031b94d662d'
}
}
}
</script>
Since your component have the same structure i recommend to use mixins, create a file named myMixins.js and add the following code inside it :
const myMixins = {
props:['cid'],
mounted() {
axios({
method: "GET",
"url": this.contentDeliveryUrl
}).then(result => {
// eslint-disable-next-line
this.content = amp.inlineContent(result.data)[0];
console.log(this.content)
}, error => {
console.error(error);
});
},
data() {
return {
contentDeliveryUrl: 'https://c1.adis.ws/cms/content/query?fullBodyObject=true&query=%7B%22sys.iri%22%3A%22http%3A%2F%2Fcontent.cms.amplience.com%2F${this.cid}%22%7D&scope=tree&store=testing',
content: []
}
}
}
export default mixins;
and inside each component add this :
import myMixins from './myMixins'
export default{
....
mixins: [myMixin]
}

passing data in vuejs using props

I'm doing image upload in vuejs. And I separate the upload file, use props to listen
In user.vue
<template>
<a-form
:form="form"
#submit.prevent="uploadFile"
>
<a-tabs v-model="activeKey" tabPosition="right">=
<image-upload
:option="{
title: 'Upload',
placeholder: 'File upload'
}"
/>
</a-tabs>
</a-form>
</template>
<script>
import { Tabs, Form } from "ant-design-vue";
Vue.use(Tabs);
Vue.use(Form);
export default {
components: {
image-upload: () =>
import(
/* webpackChunkName: "js/chunks/photo-platform.chunk" */ "./upload/index.vue"
),
},
methods: {
uploadFile() {
this.form.validateFields((err, values) => {
console.log(values);
});
}
}
}
</script>
In upload/index.vue
<template>
<b-row>
<b-col md="3">
<b-form-group>
<label>{{option.title}}</label>
<b-form-file
accept="image/*"
placeholder= {{ option.placeholder }}
drop-placeholder="Drop file here..."
name="titleImage"
v-model="titleImage"
#change="changeTitle"
multiple
/>
</b-form-group>
</b-col>
</b-row>
</template>
<script>
import {
BCard,
BRow,
BCol,
BForm,
} from "bootstrap-vue";
export default {
props: {
option: Object
},
components: {
BFormGroup,
BRow,
BCol,
BCard,
},
data() {
return {
titleImage: [],
}
},
methods: {
changeTitle() {
////
}
}
}
</script>
I am using props in vuejs. Now I want when I select multiple images in upload/index.vue, and press submit form uploadFile in user.vue, I will get the data of the image I just selected in. Please give me your opinion. I'm new to vuejs so it's really hard for me. Thanks

VueJS display dynamic modal component

I have posts and replys s.t. replies belong to posts via the attribute reply.posts_id.
I am attempting to show the reply form as a modal for the user to enter a reply. However, I want to create a generic Modal component that I can use everywhere with content that is specified in another component built for a specific context.
Reply to post is the first place I woul like this to work.
Currently, the Vuex correctly returns Modal visible:true when the reply button is clicked, but the modal does not render and I get the error message showing that the Modal component is not found:
Unknown custom element: <ModalReplyForm> - did you register the component correctly? For recursive components, make sure to provide the "name" option.
I am using vuex to manage the visibility of the modal. Here are the relevant files:
store.js:
import Vue from 'vue'
import Vuex from 'vuex'
import axios from 'axios'
...
Vue.use(Vuex)
export default new Vuex.Store({
state: {
status: '',
...
modalVisible: false,
modalComponent: null
},
mutations: {
...
showModal(state, componentName) {
console.log('showing the modal')
state.modalVisible = true;
state.modalComponent = componentName;
},
hideModal(state) {
console.log('hiding the modal')
state.modalVisible = false;
}
},
actions: {
...
}
},
getters: {
isAuthenticated: state => !!state.user,
authStatus: state => state.status,
user: state => state.user,
token: state => state.token,
posts: state => {
return state.posts;
}
...
}
})
App.vue
<template>
<div id="app">
<app-modal></app-modal>
<NavigationBar />
<div class="container mt-20">
<router-view />
</div>
<vue-snotify></vue-snotify>
</div>
</template>
<script>
import AppModal from '#/components/global/AppModal';
import NavigationBar from '#/components/layout/NavigationBar'
export default {
name: "App",
components: {
AppModal,
NavigationBar
}
};
</script>
<style>
body {
background-color: #f7f7f7;
}
.is-danger {
color: #9f3a38;
}
</style>
Post.vue (houses the button to call the reply modal):
<template>
<div class="row ui dividing header news">
<!-- Label -->
<div class="m-1 col-md-2 ui image justify-content-center align-self-center">
<img v-if="post.avatar_url" :src="post.avatar_url" class="mini rounded"/>
<v-gravatar v-else :email="post.email" class="mini thumbnail rounded image rounded-circle z-depth-1-half"/>
</div>
<!-- Excerpt -->
<div class="col-md-9 excerpt">
...
<!-- Feed footer -->
<div class="feed-footer row">
<div class="small"> {{ post.created_at | timeAgo }}</div>
<button type="button" flat color="green" #click="showModal('ModalReplyForm')">
<i class="fa fa-reply" ></i>
...
<div v-show="postOwner(post)" class="">
<button type="button" flat color="grey" #click="deletePost(post.id)">
<i class="fa fa-trash " ></i>
</button>
</div>
</div>
</div>
</div>
</template>
<script>
import { mapMutations } from 'vuex';
import PostsService from '../../services/PostsService'
import RepliesService from '../../services/RepliesService'
import Replies from '#/components/Reply/Replies'
import ReplyForm from '#/components/Reply/ReplyForm'
export default {
name: "Post",
props: {
post: {
type: Object,
required: true
}
},
components: {
Replies,
ReplyForm
},
computed: {
me() {
return this.$store.getters.user
}
},
methods: {
...mapMutations(['showModal']),
...
}
};
</script>
AppModal.vue - generic Modal component
<template>
<div class="c-appModal">
<div class="c-appModal__overlay" v-if="visible"></div>
<div class="c-appModal__content" v-if="visible" #click.self="hideModal"></div>
<div class="c-appModal__innerContent">
<component :is="component"></component>
</div>
</div>
</template>
<script>
import Vue from 'vue';
import { mapState, mapMutations } from 'vuex';
export default {
name: 'AppModal',
data() {
return {
component: null
}
},
computed: {
...mapState({
visible: 'modalVisible',
modalComponent: 'modalComponent'
}),
},
methods: {
...mapMutations(['hideModal'])
},
watch: {
modalComponent(componentName) {
if (!componentName) return;
Vue.component(componentName, () => import(`#/components/modals/${componentName}`));
this.component = componentName;
}
},
created() {
const escapeHandler = (e) => {
if (e.key === 'Escape' && this.visible) {
this.hideModal();
}
};
document.addEventListener('keydown', escapeHandler);
this.$once('hook:destroyed', () => {
document.removeEventListener('keydown', escapeHandler);
});
},
};
</script>
ModalReplyForm - specific reply modal content
<template>
<div>
<div class="c-modalReply">
<div>
<label for="reply">Your comment</label>
<div class="field">
<textarea name="reply" v-model="reply" rows="2" placeholder="Compose reply"></textarea>
</div>
</div>
<button class="c-modalReply__cancel" #click="hideModal">Cancel</button>
<button class="c-modalReply__post" :disabled="!isFormValid" #click="createReply">Reply</button>
</div>
</div>
</template>
<script>
import RepliesService from '#/services/RepliesService'
import { mapMutations } from 'vuex';
export default {
name: "ModalReplyForm",
// props: {
// post: {
// type: Object,
// required: true
// }
// },
data() {
return {
reply: ""
};
},
computed: {
isFormValid() {
return !!this.reply;
},
currentGroup() {
return this.$store.getters.currentPost;
}
},
methods: {
...mapMutations([
'hideModal'
]),
async createReply () {
let result = await RepliesService.addReply({
reply: {
body: this.reply,
postId: this.post.id
}
});
this.$emit("reply-created");
this.hideModal();
}
}
};
</script>
Unknown custom element: - did you register the
component correctly? For recursive components, make sure to provide
the "name" option.
This message says that you never imported/defined ModalReplyForm, which you have not.
In my own generic modal, I ended up having to import all the components that might appear within the modal itself.
If you add a:
import ModalReportForm from ...
and a:
components: {
ModalReplyForm
}
to AppModal.vue, the modal should then do what you expect.