How can we show multiple items with Bootstrap-Vue Carousel? - vuejs2

I think boostrap-vue carousel not so detailed. For this reason i can't reach to nice solution.
I wanna just show 3 items (like in image) in my app and i didnt find the solution and i searched other package and there was no solution for me.
All i want to do like this;
data() {
return {
slide: 0,
sliding: null
};
},
methods: {
onSlideStart(slide) {
this.sliding = true;
},
onSlideEnd(slide) {
this.sliding = false;
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div>
<b-carousel id="carousel-1" v-model="slide" :interval="0" controls indicators background="white" img-width="650" img-height="480" #sliding-start="onSlideStart" #sliding-end="onSlideEnd">
<b-carousel-slide caption="First slide" text="Nulla vitae elit libero, a pharetra augue mollis interdum." img-src="https://picsum.photos/1024/480/?image=52"></b-carousel-slide>
<b-carousel-slide img-src="https://picsum.photos/1024/480/?image=54">
<h1>Hello world!</h1>
</b-carousel-slide>
<b-carousel-slide img-src="https://picsum.photos/1024/480/?image=58"></b-carousel-slide>
<b-carousel-slide>
<img slot="img" class="d-block img-fluid w-100" width="1024" height="480" src="https://picsum.photos/1024/480/?image=55" alt="image slot">
</b-carousel-slide>
</b-carousel>
<p class="mt-4">
Slide #: {{ slide }}<br> Sliding: {{ sliding }}
</p>
</div>
If you have any other library suggestion i would appreciate.
Thanks for help.

BootstrapVue <b-carousel> is designed to only show a single slide at a time (as is the standard bootstrap V4.x carousel component). There are never more than 2 slides visible at once (during the slide or fade transition, all other slides are hidden. CSS transforms are used to make the appearance of the slides moving)
You would need to either find a 3rd party component that supports multiple slides showing, or generate your own custom component.

I'm not familiar with this specific component, but this is from its documentation:
<!-- Slide with blank fluid image to maintain slide aspect ratio -->
<b-carousel-slide caption="Blank Image" img-blank img-alt="Blank image">
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse eros felis, tincidunt
a tincidunt eget, convallis vel est. Ut pellentesque ut lacus vel interdum.
</p>
</b-carousel-slide>
I would try using a blank image as the default and inserting whatever other images/content you want as children of the slide:
<!-- Slide with blank fluid image to maintain slide aspect ratio -->
<b-carousel-slide caption="Blank Image" img-blank img-alt="Blank image">
<img class="my-img" src="img1.jpg"/>
<img class="my-img" src="img2.jpg"/>
<img class="my-img" src="img3.jpg"/>
</b-carousel-slide>
And use absolute positioning or flexbox to display them the way you want.

I did the same thing using b-card-group. Because b-carousel does not support showing multiple items.
In template area
<div>
<!-- carousel area -->
<b-card-group deck class="mb-0">
<b-card v-for="(item,index) in currentPageCards" :key="index" class="mr-0">
<!-- card content -->
</b-card>
</b-card-group>
<!-- pagination area -->
<div class="pagination" v-if="cards.length>cardsPerPage">
<div class="index" v-for="i in pageCount" :key="i" #click="next(i)" :class={active:currentPage(i)}></div>
</div>
</div>
In script area
data() {
return {
cards: [{
//Data in the card as objects
},{},{},{},{}],
paginatedCards:[],
pageCount:0,
cardsPerPage:4,
currentPageIndex:0
}
},
computed: {
currentPageCards(){
this.createPages();
return this.paginatedCards[this.currentPageIndex];
}
},
methods:{
currentPage(i){
return i-1===this.currentPageIndex;
},
createPages() {
let cardsLength = this.cards.length;
let fullPagesCount = Math.floor(cardsLength/this.cardsPerPage);
if(cardsLength>this.cardsPerPage) {
this.pageCount = 0;
for (let i = 0; i < fullPagesCount * this.cardsPerPage; i += this.cardsPerPage) {
this.paginatedCards[this.pageCount] = this.cards.slice(i,i + this.cardsPerPage);
this.pageCount++;
}
this.paginatedCards[this.pageCount] = this.cards.slice(cardsLength-this.cardsPerPage,cardsLength);
this.pageCount = this.pageCount+1;
} else {
this.paginatedCards[0] = this.cards;
}
},
next(i){
this.currentPageIndex=i-1;
}
}
In style area
.pagination{
display:flex;
align-items: center;
justify-content: center;
padding:10px;
}
.index{
margin-left:10px;
width:10px;
height:10px;
background:#000000
}
.active{
width:15px;
height:15px;
}
It shows like as below
Try this one. Thank you!

Related

Is it possible to change content of layout thought props?

I have that layout:
<template>
...navigation for all pages..
<h1>content for X??</h1>
<slot />
...footer for all pages...
</template>
For pages/index.vue:
<template>
index is here ok :)
</template>
and pages/about.vue:
<template>
about is here ok :)
</template>
I wish I could render h1 in layout for different pages and not print same text for all.
I know I can move h1 from layout to pages and edit each page.
But there are a lot of content between h1 and slot and I just want to change text.
There would be many ways of achieving what you are asking but the simplest would be the usage of named slots.
In the vue documentation (please take some time digging into it, the amount of deftail and explanation available is very appreciable) :
The <slot> element has a special attribute, name, which can be used to assign a unique ID to different slots so you can determine where content should be rendered.
The process of using named slots in the nuxt layouts is documented in the nuxt layout documentation.
You could create a TitleLayout.vue component in the layouts folder of your nuxt project that would look like this:
<!-- layouts/TitleLayout.vue -->
<template>
<div>
<nav>
<nuxt-link to="/"> index </nuxt-link>
<nuxt-link to="about"> about </nuxt-link>
</nav>
<main>
<h1>
<slot name="heading">
a default title
</slot>
</h1>
<slot/>
</main>
</div>
</template>
and that you could use it like this in your pages/About.vue (or in any other page)
<!-- pages/About.vue -->
<template>
<div>
<NuxtLayout name="with-title">
<template #heading> Hello from the about title </template>
Lorem ipsum dolor sit amet consectetur adipisicing elit. Quibusdam eius
rem, sit repellendus reiciendis explicabo? Dolorum totam molestias
adipisci cupiditate distinctio, libero dolorem iusto, quidem esse
excepturi voluptatem repellendus facere.
</NuxtLayout>
</div>
</template>
<script setup>
definePageMeta({
layout: false,
});
</script>

Carousel component from bootstrap-vue3 not working

I am creating Vue app with vue3 and using the bootstrap-vue3 package for Frontend.
I followed Bootstrap-Vue docs and copied the following code but Carousel doesn't work.
Of course, I can use another package for Carousel, but almost works already was done with this package. So I am looking for a solution to use this package.
Kindly let me know please if you folks how to fix this issue.
<template>
<div>
<b-carousel
id="carousel-1"
v-model="slide"
:interval="4000"
controls
indicators
background="#ababab"
img-width="1024"
img-height="480"
style="text-shadow: 1px 1px 2px #333;"
#sliding-start="onSlideStart"
#sliding-end="onSlideEnd"
>
<!-- Text slides with image -->
<b-carousel-slide
caption="First slide"
text="Nulla vitae elit libero, a pharetra augue mollis interdum."
img-src="https://picsum.photos/1024/480/?image=52"
></b-carousel-slide>
<!-- Slides with custom text -->
<b-carousel-slide img-src="https://picsum.photos/1024/480/?image=54">
<h1>Hello world!</h1>
</b-carousel-slide>
<!-- Slides with image only -->
<b-carousel-slide img-src="https://picsum.photos/1024/480/?image=58"></b-carousel-slide>
<!-- Slides with img slot -->
<!-- Note the classes .d-block and .img-fluid to prevent browser default image alignment -->
<b-carousel-slide>
<template #img>
<img
class="d-block img-fluid w-100"
width="1024"
height="480"
src="https://picsum.photos/1024/480/?image=55"
alt="image slot"
>
</template>
</b-carousel-slide>
<!-- Slide with blank fluid image to maintain slide aspect ratio -->
<b-carousel-slide caption="Blank Image" img-blank img-alt="Blank image">
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse eros felis, tincidunt
a tincidunt eget, convallis vel est. Ut pellentesque ut lacus vel interdum.
</p>
</b-carousel-slide>
</b-carousel>
<p class="mt-4">
Slide #: {{ slide }}<br>
Sliding: {{ sliding }}
</p>
</div>
</template>
<script>
export default {
data() {
return {
slide: 0,
sliding: null
}
},
methods: {
onSlideStart(slide) {
this.sliding = true
},
onSlideEnd(slide) {
this.sliding = false
}
}
}
</script>
You need a active prop in any one of the b-carousel-slide tag to display the slide.
It is something not mentioned in the documentation.

Quasar: Drawer changes to automatically Mini-mode

I am using Quasar, and I would like the drawer changes into mini mode automatically whenever the browser gets smaller by users. For now, it's always opened even I narrows the browser.
It's what I tried below:
<q-drawer
v-model="drawer"
show-if-above
:mini="!drawer || miniState"
#click.capture="drawerClick"
:width="220"
:breakpoint="500"
bordered
:content-style="{ backgroundColor: '#f5f7f9' }"
>
<q-scroll-area class="fit">
<q-list padding>
<q-btn
v-if="!miniState"
flat
left
#click="miniState = !miniState"
class="logo-btn"
>
<img
src="~assets/os_logo.png"
width="144px"
height="24px"
contain
/>
</q-btn>
<q-btn v-else flat left #click="miniState = !miniState">
<img src="~assets/os_logo_no_text.png" width="24px" contain />
</q-btn>
<!-- MENU -->
<q-expansion-item
default-opened
v-for="(menu, index) in menus"
:style="index === 0 && 'margin-top: 27px'"
:icon="menu.icon"
:label="menu.title"
:key="menu.id"
:expand-icon="menu.subMenus.length === 0 ? 'none' : ''"
header-class="header-bg text-black"
expand-icon-class="text-gray"
>
<q-expansion-item
v-for="(sub, index) in menu.subMenus"
:key="index"
:label="sub.title"
expand-icon="none"
class="sub-content"
:to="{ name: sub.link }"
/>
</q-expansion-item>
</q-list>
</q-scroll-area>
</q-drawer>
And the script code below :
<script lang="ts">
import Vue from 'vue';
export default Vue.extend({
// name: 'ComponentName'
data() {
return {
drawer: false,
miniState: false,
},
computed: { // <-- I tried this one..
miniState() {
if (window.innerWidth < 600) {
return (this.miniState = true);
}
}
},
});
</script>
I have also checked this answers here : How to default navigation drawer to closed on mobile and open on desktop?
But this is not really what I am aiming for since the mode of drawer gets stuck and not changes responsively. Any suggestions?
In your code you have made several mistakes;
You are repeating your data property miniState as a computed property. The miniState property inside the data function will override the computed property; thus it will always be false
You are not returning a value from your computed property; instead you are only assigning.
Still; window properties such as innerWidth is not reactive and watchable in Vue. Please correct me if I am wrong. Thus, watching window.innerWidth will not trigger every time the window is resized.
Since you are using Quasar, you can make use of the the screen plugin which comes with Quasar. You dont have to install anything, the screen plugin is installed by default. Here is the link to the docs.
I have put below a very minimal example of the code with the functionality you required. This is not the code you have put in your post above. I extracted the drawer and the content from the Quasar documentation here
Now the drawer will automatically go into mini mode when the screen size is below 500 px; this is of course configurable.
Also; on a side note, if you are only starting with Vue and Quasar, Vue is now upgraded to Vue3 which comes with the composition api. Quasar is also being upgraded to version 2 which supports Vue3.
Follow the comments below and you will understand the code!
new Vue({
el: '#q-app',
data: function() {
return {
drawer: true,
// using a property to track when to show the mini drawer: this way is easy to maintain.
switchToMini: 500
}
},
computed: {
// use ministate as a computed property
miniState: function() {
// use the screen plugin of Quasar -> this is very handy
return this.$q.screen.width < this.switchToMini
// you can do better and compare agains Quasars default breakpoints; following code checks whether the current screen size is 'sm'. You can comapare against 'xs', 'sm', 'md', 'lg' and 'xl'
// return this.$q.screen.name === "sm"
}
}
})
<!-- quasar and vue includes -->
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/#quasar/extras/material-icons/material-icons.css">
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/quasar/dist/quasar.min.css">
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script src="https://cdn.jsdelivr.net/npm/quasar/dist/quasar.umd.min.js"></script>
<!-- end of includes -->
<!-- app -->
<div id="q-app">
<template>
<div class="q-pa-md">
<q-layout view="hHh Lpr lff" container style="height: 300px" class="shadow-2 rounded-borders">
<q-header elevated class="bg-black">
<q-toolbar>
<q-toolbar-title>Header</q-toolbar-title>
</q-toolbar>
</q-header>
<q-drawer
v-model="drawer"
show-if-above
:mini="miniState"
:breakpoint="200"
:width="200"
bordered
class="bg-grey-3"
>
<q-scroll-area class="fit">
<q-list padding>
<q-item clickable v-ripple>
<q-item-section avatar>
<q-icon name="send" />
</q-item-section>
<q-item-section>
Send
</q-item-section>
</q-item>
</q-list>
</q-scroll-area>
</q-drawer>
<q-page-container>
<q-page padding>
<p>
Lorem ipsum dolor sit amet consectetur adipisicing elit. Fugit nihil praesentium molestias a adipisci, dolore vitae odit, quidem consequatur optio voluptates asperiores pariatur eos numquam rerum delectus commodi perferendis voluptate?
</p>
</q-page>
</q-page-container>
</q-layout>
</div>
</template>
</div>

How to determine which q-expansion-item is open?

I have a q-list with several q-expansion-items as children. The q-expansion-items are assigned to a list, so there is only one open at any given time.
<q-list padding>
<q-expansion-item
id="explore"
group="somegroup"
icon="explore"
label="Explore"
default-opened
#show="switchMode">
<q-card id="explore-card">
<q-card-section id="explore-card-section">
Item one text
</q-card-section>
</q-card>
</q-expansion-item>
<q-expansion-item
id="identity"
group="somegroup"
icon="perm_identity"
label="Identity"
#show="switchMode">
<q-card>
<q-card-section>
Item two text.
</q-card-section>
</q-card>
</q-expansion-item>
</q-list>
I would like to run a Vue method when any q-expansion-item is opened and determine which specific item was opened.
I've tried assigning an id to each q-expansion-item, but need some clunky code to access the ID within the #show event: `
new Vue({
el: '#q-app',
data: function () {
return {}
},
methods: {
switchMode: function (event) {
console.log(event.target.parentElement.parentElement.parentElement.parentElement.id)
}
},
// ...etc
})
I've also tried using the to property to change the URL fragment, but that doesn't update the URL when the item is expanded (it only updates the URL fragment when clicking on the item title).
How can I determine which q-expansion-item in a group is open at any given time?
You can achieve this without using id's and events. You can use v-model instead.
Example -
<q-expansion-item
group="somegroup"
icon="explore" v-model="selected_model['First']"
label="First"
header-class="text-primary"
>
<q-card>
<q-card-section>
Lorem ipsum dolor sit amet, consectetur adipisicing elit.
</q-card-section>
</q-card>
</q-expansion-item>
<q-separator />
<q-expansion-item
group="somegroup"
v-model="selected_model['Second']"
icon="perm_identity"
label="Second"
header-class="text-teal"
>
<q-card>
<q-card-section>
Lorem ipsum dolor sit amet, consectetur adipisicing elit.
</q-card-section>
</q-card>
</q-expansion-item>
data -
data(){
return{
selected_model:{}
}
}
Result-
{ "First": true, "Second": false, "Third": false, "Fourth": false }
Codepen - https://codepen.io/Pratik__007/pen/poymOqm
and if you want First to open default then you can set "First":true in selected_model.

How to pass imageUrl as props?

Here is my component. It takes a props imageUrl which is String referring either an image from a URL or a reference to a local asset from the assets folder
<template>
<div class="flex" style="height: 155px;align-items: center">
<div class="avatar" style="height: 130px;width: 130px">
<img :src="require(`imageUrl`)" height="130" width="130" style="border-radius: 50%"/>
</div>
<div class="flex flex-column" style="margin-left: 31px">
<div class="flex" style="font-weight: bold">
{{fullName}}
</div>
<div class="flex" style="">
{{title}}
</div>
<div style="height: 20px"></div>
<div class="flex" style="text-align: start">
{{content}}
</div>
</div>
</div>
</template>
<script>
export default {
name: "ReviewTile",
props: {
imageUrl: String,
fullName: String,
title: String,
content: String
}
}
</script>
<style scoped>
</style>
I use it like this:
<ReviewTile
image-url="../assets/Eugene.png"
full-name="Eugene B.
"
title="UI/UX Designer."
content=" Christabel is one of the business world’s truly great deal makers and strategists, easily on par with
the best of the strategy consultants and designers I have worked with at SOS-HGIC and Kleio. She is also
a world-class human being."
></ReviewTile>
<div style="background-color: #b7b7b7;height: 1px; margin: 33px 0px"></div>
<ReviewTile
image-url="../assets/headshot_black1.png"
full-name="Gilliam D."
title="Web designer/Developer"
content="Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo."
></ReviewTile>
But images are not loading.
There is a strange JS bug that breaks proper image loading (sry, I forgot what it was, but I found the solution on stackoverflow a while ago).
What helps is breaking the image request up like this:
:src="require('#/assets/' + imageName + '')"
So you only have your image name in the prop and load it without the curly brackets. Now you also don't have to worry about the correct path. The # will find the correct folder.
If anyone can explain the technicalities of this better or finds the other thread with the solution, please feel free to expand on my answer.
Edit: First part of the explanation: Webpack can't work with only a variable as path, because then it would have to go over potentially thousands of files. So you have to have the #/assets/ part as text. Explained more nicely here: Vue.js dynamic image src with webpack require() not working
Couldn't find yet why the curly brackets don't work.
If all your images are in the same folder your can just pass the file name as props:
<ReviewTile
image-url="Eugene.png"
...
></ReviewTile>
<ReviewTile
image-url="headshot_black1.png"
...
></ReviewTile>
Then in the ReviewTitle component, require the imageUrl with the assets path:
<div class="avatar">
<img :src="require(`../assets/${imageUrl}`)" />
</div>
Note:
If all the images have the same extension .png you can even just write the file name like image-url="Eugene" and in the component: <img :src="require(`../assets/${imageUrl}.png`)" />
if you are sending the path of the image in the url, then you can use the props directly,
but make sure you are providing the right path to the image.
<img :src="imageUrl" height="130" width="130" style="border-radius: 50%"/>
also change your prop name in the other component
// Wrong
<ReviewTile
image-url="../assets/Eugene.png"
full-name="Eugene B.
// correct one would be something like this
<ReviewTile
:imageUrl="../assets/Eugene.png"
full-name="Eugene B.
For those using Nuxt.js for the Vue apps - there is simple solution using static folder instead of assets.
The key difference between these two folders is that static is not compiled by Webpack - therefore you are free to use variables as you please.
See the docs:
https://nuxtjs.org/docs/2.x/directory-structure/static