I have offers cards list rendering thru the loop. Every 3rd col (bootstrap) elements i add row div. Now i need to add another col element (banner block) for every 6th element. For render some thing like that:
How i can implement that?
My code now
<div class="row" v-for="i in Math.ceil(offers.length / 3)">
<div class="col-xl-4 col-lg-4 col-md-6 col-sm-12 col-12" v-for="offer in offers.slice((i-1)*3, i*3)">
<h2>{{offer.name}}</h2>
<h2>{{offer.desc}}</h2>
</div>
</div>
for loop:
<div class="mycol" v-for="(offer,ind) in offers">
<template v-if="ind % 5 == 0">
<h2>banner</banner>
</template>
<template v-else>
<h2>{{offer.name}}</h2>
<h2>{{offer.desc}}</h2>
</template>
</div>
for new line for every third col you can use css
.mycol:nth-child(3n+1){
clear:left;
}
I would recommend you do less programming in the view and more in the view model. Create a computed that splits up your data into series of offers and banners, and also into rows, then use that computed in a straightforward way.
const chunk = (arr, size) =>
arr
.reduce((acc, _, i) =>
(i % size) ?
acc :
[...acc, arr.slice(i, i + size)], []);
new Vue({
el: '#app',
data: {
offers: []
},
computed: {
rows() {
const withBanners = chunk(this.offers, 5).map((arr) => [...arr, {name: 'banner', type: 'Banner'}]).reduce((a, b) => a.concat(b), []);
return chunk(withBanners, 3);
}
},
mounted() {
setTimeout(() => {
this.offers = [{
name: 'offer'
},
{
name: 'offer'
},
{
name: 'offer'
},
{
name: 'offer'
},
{
name: 'offer'
},
{
name: 'offer'
},
{
name: 'offer'
},
{
name: 'offer'
},
{
name: 'offer'
},
{
name: 'offer'
},
{
name: 'offer'
}
];
}, 500);
}
});
#app {
display: grid;
}
.row {
display: grid;
grid-gap: 2rem;
grid-template-columns: repeat(3, auto);
justify-content: left;
}
.box {
width: 8rem;
height: 8rem;
}
.banner {
background-color: #f9c;
}
.offer {
background-color: #99f;
}
<script src="https://unpkg.com/vue#latest/dist/vue.js"></script>
<div id="app">
<div class="row" v-for="row in rows">
<div class="col-xl-4 col-lg-4 col-md-6 col-sm-12 col-12" v-for="item in row">
<div v-if="item.type === 'Banner'" class="banner box">
<h2>{{item.name}}</h2>
</div>
<div v-else class="offer box">
<h2>{{item.name}}</h2>
</div>
</div>
</div>
</div>
This should do exactly what you want.. I had to manipulate the data some because Vue's templating language is not designed to handle the logic for this kind of use case
HTML
<div id="app">
<div v-for="items in rows" class="row">
<div v-for="item in items" class="col-xl-4 col-lg-4 col-md-6 col-sm-12 col-12">{{item}}</div>
</div>
</div>
SCRIPT
created () {
while (this.items.length > 0) {
const howMany = (this.rows.length % 3 === 0) ? 3 : 2
const row = this.items.splice(0, howMany)
if (howMany === 2) row.push('banner')
this.rows.push(row)
}
},
https://jsfiddle.net/jamesharrington/k6c0rgL3/17/
I assume that you want to add a banner every 6 elemtns, but you want to show the 6th. I would handle this on my data object, inserting the banner inside it. It is easier. You could split your array on this way.
let firstPart = myData.slice(0,5)
let lastPart = myData.slice(5,)
let newData = [...firstPart, banner, ...lastPart]
Now, you just need to do this every 6 elements.
I recommend to use flex if it is possible.
So the code will look like: http://jsfiddle.net/n89dbo37/
new Vue({
el: '#app',
data() {
return {
items: _.times(20, i => ({type: 'offer'})),
};
},
computed: {
itemsWithBanners() {
let result = [];
this.items.forEach((item, idx) => {
if (idx && idx % 5 === 0) {
result.push({type: 'banner'});
}
result.push(item);
});
return result;
},
},
});
Thanks for everyone, i took Roy J solution, rebuild for my case and get result. My code:
<template>
<div class="section-space80 results-col" >
<div class="container" >
<div class="row">
<div class="col-md-12">
<div class="wrapper-content bg-white pinside40">
<div class="row" v-for="row in rows">
<div v-for="offer in row" class="col-xl-4 col-lg-4 col-md-6 col-sm-12 col-12">
<div class="lender-listing" v-if="offer.type && offer.type === 'Banner'">
<div class="lender-head">
Banner
</div>
</div>
<div class="lender-listing" v-if="offer.mfoName">
<div class="lender-head">
<div class="lender-logo">Offer</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
const chunk = (arr, size) =>
arr
.reduce((acc, _, i) =>
(i % size) ?
acc :
[...acc, arr.slice(i, i + size)], []);
import axios from 'axios'
export default {
data() {
return {
showOffers: true,
loanOffers: [],
isVisible: false,
loadMore: true,
offset: 0,
rows: ''
}
},
methods: {
getOffersList: function () {
let dataElements = this
dataElements.loading = true
axios.get('/api/v1/getUserOffers')
.then(function (response) {
dataElements.loanOffers = response.data
const withBanners = chunk(dataElements.loanOffers, 5).map((arr) => [...arr, {name: 'banner', type: 'Banner'}]).reduce((a, b) => a.concat(b));
dataElements.rows = chunk(withBanners, 3);
})
},
},
beforeMount(){
this.getOffersList()
}
}
</script>
I offer to use template and loop over it.
Then inside you check v-if="i%6" --> your article v-else --> yuor ad.
Related
This is my first project and I am trying to build a product filter. I created search and category filter which is working properly, but I am not able to create filter for rating.
This is my code for reference:
<div class="heading">
<h6 class="mb-3">Filter By Area</h6>
</div>
<div class="form-check" v-for="filter in filters" :key="filter">
<input class="form-check-input" type="radio" name="flexRadioDefault" id="flexRadioDefault1"
#click="() => filterAccommodations(filter)">
<label class="form-check-label area-filter-each" for="flexRadioDefault1">
{{ filter }}
</label>
</div>
<div class="heading">
<h6 class="mb-3 mt-3">Filter By Rating</h6>
</div>
<div class="form-check" v-for="starRating in starRatings" :key="starRating">
<input class="form-check-input" type="radio" name="flexRadioDefault" id="flexRadioDefault1"
#click="() => starAccommodations(filter)">
<label class="form-check-label area-filter-each" for="flexRadioDefault1">
{{ starRating }}
</label>
</div>
the search for area works perfectly and i have no issues. even the search bar works as good.
script
const filters = [
"All",
"Dagana",
"Lhuentse",
"Mongar",
"Pemagatshel",
"Tashiyangtse",
"Trashigang",
"Zhemgang",
];
const starRatings = [
"All",
"1",
"2",
"3",
"4",
"5",
];
export default {
name: "SidebarFilter",
props: ["filterAccommodations", "searchAccommodations" ,'filteredAccommodations','starAccommodations'],
data() {
return {
filters,
starRatings
};
},
};
these are on my components that i have build for the search filter.
and it meets with the content from another component at a parent file, which i call is model for my self reference.
Below is the Parent template
<div class="container">
<div class="row">
<div class="col-md-3 col-lg-3 col-xl-3 col-sm-12 col-xs-12">
<SidebarFilter :filterAccommodations="filterAccommodations"
:searchAccommodations="searchAccommodations"
:starAccommodations="starAccommodations"
/>
</div>
<div class="col-md-9 col-lg-9 col-xl-9 col-sm-12 col-xs-12">
<AccommodationElements :accommodations="accommodations" />
</div>
</div>
</div>
I am writing some functions to actually make the query on the json data. This is my script:
import ACCOMMODATION_DATA from '../Accommodation_DATA.json'
methods: {
filterAccommodations(filter) {
this.resetAccommodations();
if (filter === "All") {
this.accommodations = ACCOMMODATION_DATA;
} else {
this.accommodations = ACCOMMODATION_DATA.filter(accommodation => {
return accommodation.location_id.toLowerCase().includes(filter.toLowerCase());
});
}
},
starAccommodations(filter) {
this.resetAccommodations();
if (filter === "All") {
this.accommodations = ACCOMMODATION_DATA;
} else {
this.accommodations = ACCOMMODATION_DATA.filter(accommodation => {
return accommodation.star_rate.toLowerCase().includes(filter.toLowerCase());
});
}
},
searchAccommodations(search) {
this.resetAccommodations();
this.accommodations = ACCOMMODATION_DATA.filter(accommodation => {
return accommodation.title.toLowerCase().includes(search.toLowerCase());
});
},
resetAccommodations() {
this.accommodations = ACCOMMODATION_DATA;
}
}
JSON file sample:
{
"id": 100,
"title": "Onofredo Walkden",
"content": "Salivary operation NEC",
"image_id": "http://dummyimage.com/512x517.png/cc0000/ffffff",
"banner_image_id": "http://dummyimage.com/x.png/ff4444/ffffff",
"location_id": "Tashiyangtse",
"address": "9 Briar Crest Hill",
"map_lat": 40.5845053,
"map_lng": -8.0854006,
"is_featured": true,
"star_rate": 4,
"date_created": "6/22/2021",
"price": 19433.22
}
You can maybe try it like this:
<template>
<div>
{{ filterAccommodations }}
</div>
</template>
data() {
return {
title: "",
location: "",
starRate: 5,
};
},
computed: {
filterAccommodations() {
return ACCOMMODATION_DATA.filter((acc) => {
return (
acc.location_id.toLowerCase().includes(this.location.toLowerCase()) &&
acc.title.toLowerCase().includes(this.title.toLowerCase()) &&
acc.star_rate === this.starRate
);
});
},
}
In your UI you will need 3 input fields, where you can set your title, location and star rating.
isGridView: true,
isListView: true,
methods: {
switchView: function() {
this.isGridView = !this.isGridView;
},
switchData: function () {
this.isListView = !this.isListView;
}
<div class="product-grid1">item1</div>
<div class="product-grid2">item2</div>
<div class="product-grid3">item3</div>
<div class="product-list1">item1</div>
<div class="product-list2">item2</div>
<div class="product-list3">item3</div>
<div id="app-gridview">
<div>
<button class="button" v-on:click="switchView()"></button>
<button class="button" v-on:click="switchData()"></button>
</div>
<div v-bind:class="[ isGridView ? 'grid-wrapper' : 'list-wrapper' ]">
<div class="grid-row" v-if="isGridView">
<div class="grid-header" v-for="name in gridData.columns">{{ name }}</div>
</div>
<!-- GridView structure -->
<div v-if="isGridView" class="grid-row" v-for="row in gridData.data">
<div class="list-row-item" v-for="name in gridData.columns">
<div>{{ row[name] }}</div>
</div>
</div>
<!-- ListView structure -->
<div v-if="!isGridView" class="list-row" v-for="row in gridData.data">
<img v-bind:src="row.ImagePath" class="list-image" />
<div class="list-property">
<div class="list-row-item" v-for="name in gridData.columns">
<div class="list-property-name">{{ name }}</div>
<div>{{ row[name] }}</div>
</div>
</div>
</div>
</div>
I tried to implement the list and the grid view, Where I need to toggle between each one. For that i have taken isGrid and isList set to true, And from vue side i am trying to place ternary operator, And switch between each other.
Can you please help me on toggle from the list and grid view.
When you create a component whose view can be changed, I suggest that you use the container-presentational component pattern. Really easy to keep track, and it's a breeze to add a new "view" of the data.
// this is the grid view
// this is a presentational component:
// only displays what is passed through props
Vue.component("GridView", {
props: ["users"],
computed: {
headers() {
if (!this.users.length) return []
return Object.keys(this.users[0])
},
},
template: `
<table>
<thead>
<tr>
<th
v-for="header in headers"
:key="header"
>
{{ header }}
</th>
</tr>
</thead>
<tbody>
<tr
v-for="user in users"
:key="user.id"
>
<td
v-for="(val, key) in user"
:key="user.id + '-' + key"
>
{{ val }}
</td>
</tr>
</tbody>
</table>
`
})
// this is the list view
// this is a presentational component:
// only displays what is passed through props
Vue.component("ListView", {
props: ["users"],
template: `
<ol>
<li
v-for="user in users"
:key="user.id"
>
<div
v-for="(val, key) in user"
:key="user.id + '-' + key"
>
{{ key }}: {{ val }}
</div>
</li>
</ol>
`
})
// this component handles the data:
// fetching, mapping, transforming, etc.
// this is a renderless component
Vue.component("DataContainer", {
data() {
return {
users: []
}
},
mounted() {
this.fetchUsers()
},
methods: {
async fetchUsers() {
try {
const response = await fetch('https://jsonplaceholder.typicode.com/users')
const json = await response.json()
this.users = json.map(({
id,
name,
username,
email
}) => ({
id,
name,
username,
email
}))
} catch (err) {
console.error(err)
}
}
},
render(h) {
// renders nothing, just provides the data
// by passing it through "users"
return this.$scopedSlots.default({
users: this.users,
})
},
})
// the Vue instance
new Vue({
el: "#app",
data() {
return {
layout: "list-view",
}
},
methods: {
switchView() {
this.layout = this.layout === "list-view" ? "grid-view" : "list-view"
}
},
template: `
<div>
<button
#click="switchView"
>
SWITCH VIEW
</button>
<data-container>
<template
#default="{ users }"
>
<component
:is="layout"
v-bind="{ users }"
/>
</template>
</data-container>
</div>
`,
})
table {
border-collapse: collapse;
}
table,
tr,
th,
td {
border: 1px solid black;
}
td,
th {
padding: 4px 8px;
}
th {
background-color: rgba(0, 0, 0, 0.3);
}
<script src="https://cdn.jsdelivr.net/npm/vue#2.6.12/dist/vue.js"></script>
<div id="app"></div>
I understand that this is a bit further away from correcting a v-if condition-handling - but this setup would help you create flexible, extensible & maintainable solution.
I this is the cleanest solution
<template>
<button #click="toggleList">switch render view</button>
<component
:is="currentComponent"
:columns="gridData.columns"
:items="gridData.data"
/>
</template>
<script>
import gridComponent from "./your-grid-component.vue";
import listComponent from "./your-list-component.vue";
export default {
components: {
gridComponent,
listComponent,
},
data() {
return {
listType: "grid", //grid/list
gridData: {
columns: [],
data: [],
},
};
},
methods: {
toggleList() {
this.listType = this.listType === "grid" ? "list" : "grid";
},
},
computed: {
currentComponent() {
return this.listType === "grid" ? "gridComponent" : "listComponent";
},
},
};
</script>
I'm working on a filtering feature in a Vue application. I'm new to Vue, and I have the filter semi-working. It successfully allows me to select an asset type from the dropdown, and will filter the results accordingly. But what's not working is the clearFilters method.
My goal is to reset the assetType to an empty string and the filterResults array to empty, and my thought was that since checking the length of filterResults, when I clear it it would return to displaying the entire un-filtered array.
What am I doing wrong? Any information would be greatly appreciated.
<template>
<div ref="content">
<div class="container pt-3 text-center">
<div class="filter-container">
<div class="btn-group">
<button
v-ripple="'rgba(255, 255, 255, .2)'"
#click="showAssetType = !showAssetType"
class="btn btn-secondary dropdown-toggle"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
style="max-height: 40px;">
{{assetType ? assetType : 'Asset Type'}}
</button>
<div class="dropdown-menu" :style="'display:' + (showAssetType ? 'block' : 'none') + ';'">
<a class="dropdown-item" #click.prevent="setAssetFilter('all')" href="#!">All Assets</a>
<a class="dropdown-item" #click.prevent="setAssetFilter('USD'); filterByAssetType()" href="#!">USD</a>
<a class="dropdown-item" #click.prevent="setAssetFilter('GBP'); filterByAssetType()" href="#!">GBP</a>
<a class="dropdown-item" #click.prevent="setAssetFilter('CAD'); filterByAssetType()" href="#!">CAD</a>
</div>
</div>
</div>
</div>
<div id="data-table" v-if="filterResults.length > 0">
<div v-for="(transaction, index) in filterResults" :key="index">
<global-history-item>
<template v-slot:header>
<h1 class="date">{{formatDate(transaction.date)}}</h1>
<div class="transaction-header-wrapper">
<p class="transaction-text">{{formatString(transaction.tx_type)}} Transaction</p>
<p class="transaction-text" style="text-align: right">{{transaction.coin_type}}</p>
</div>
</template>
<template v-slot:content>
<global-transaction :transaction='transaction' #updateClassType="updateClassType" />
</template>
</global-history-item>
</div>
</div>
<div id="data-table">
<div v-for="(item, index) in totalHistory" :key="index">
<div v-if="item.tx_type">
<global-history-item>
<template v-slot:header>
<h1 class="date">{{formatDate(item.date)}}</h1>
<div class="transaction-header-wrapper">
<p class="transaction-text">{{formatString(item.tx_type)}} Transaction</p>
</div>
</template>
<template v-slot:content>
<global-transaction :transaction='item' #updateClassType="updateClassType" />
</template>
</global-history-item>
</div>
<div v-else-if="item.invoice_id">
<global-history-item>
<template v-slot:header>
<h1 class="date">{{formatDate(item.date)}}</h1>
<div class="invoice-header-wrapper">
<p class="invoice-text">Invoice Created</p>
<p class="invoice-text">Invoice #{{item.invoice_id}}</p>
</div>
</template>
<template v-slot:content>
<global-invoice :invoice='item' />
</template>
</global-history-item>
</div>
<div v-else>
<global-history-item>
<template v-slot:header>
<h1 class="date">{{formatDate(item.date)}}</h1>
<div class="invoice-header-wrapper">
<p class="invoice-text">Login Event</p>
<br />
</div>
</template>
<template v-slot:content>
<global-account-activity :message='"A successful login to your account was made"' />
</template>
</global-history-item>
</div>
</div>
</div>
</div>
</template>
<script>
... imports removed for brevity
export default {
name: 'Global',
components: { },
props: ['totalHistory', 'printFormat'],
mixins: ['formatDate', 'formatMenuLabel'],
data () {
return {
showAssetType: false,
showClassType: false,
activityType: '',
assetType: '',
filterResults: [],
printMode: false
}
},
methods: {
setAssetFilter (value) {
this.showAssetType = false
this.assetType = value
},
formatString (str) {
const firstLetter = str.charAt(0)
const remainder = str.slice(1)
return firstLetter.toUpperCase() + remainder
},
updateClassType (transactionRecord) {
this.$store.dispatch('updateTransactionType', transactionRecord)
},
updateTransaction (transactionRecord) {
console.log('in updateTransaction', transactionRecord)
this.$store.dispatch('updateTransactionNote', transactionRecord)
},
filterByAssetType () {
const selectedCurrency = this.assetType
if (this.assetType === 'all') {
this.clearFilters()
} else {
this.filterResults = this.totalHistory.filter(function (trans) {
return trans.currency === selectedCurrency
})
}
},
clearFilters () {
return (this.assetType = '') && (this.filterResults = [])
}
}
}
</script>
So if I am not mistaking, you only want the method clearFilters, to work? If so, try:
clearFilters () {
this.assetType = ''
this.filterResults = []
}
The logical AND operator (&&) is not to chain expressions. It’s to do an expression if the first expression is truthy.
First expression && second expression, example
const questionAnswered = true
console.log(questionAnswered && "Hooray!")
// will log "Hooray!" (Expression 2)
If you set questionAnswered to false, it will log false (expression 1)
I agree with Jens rewrite of clearFilters(). The original looked odd to me.
I had started creating a sample component to demonstrate possibly simplifying the filtering process when there were no answers. Since I have finished it and it works, I am posting it.
ClearFilters.vue
<template>
<div class="clear-filters">
<div class="row">
<div class="col-md-6">
<table class="table table-bordered">
<thead>
<tr>
<th>NAME</th>
<th>CURRENCY</th>
</tr>
</thead>
<tbody>
<tr v-for="asset in filteredAssets" :key="asset.id">
<td>{{ asset.name }}</td>
<td>{{ asset.currency }}</td>
</tr>
</tbody>
</table>
</div>
<div class="col-md-6">
<div class="form-group">
<label class="criteria-label">FILTER CRITERIA:</label>
<select class="form-control" v-model="currentCriteria">
<option v-for="(criteria, index) in criteriaOptions" :key="index" :value="criteria">{{ criteria }}</option>
</select>
</div>
<button type="button" class="btn btn-secondary" #click="resetFilter">Reset</button>
</div>
</div>
</div>
</template>
<script>
import assets from './clear-filters-data.js';
export default {
data() {
return {
assets: assets,
criteriaOptions: [
'ALL', 'USD', 'GBP', 'CAD'
],
currentCriteria: 'ALL'
}
},
computed: {
filteredAssets() {
if (this.currentCriteria === 'ALL') {
return this.assets;
}
else {
return this.assets.filter( asset => asset.currency === this.currentCriteria);
}
}
},
methods: {
resetFilter() {
this.currentCriteria = 'ALL';
}
}
}
</script>
<style scoped>
.criteria-label {
font-weight: bold;
}
</style>
Test data:
const assets = [
{
id: 1,
name: 'asset1',
currency: 'USD'
},
{
id: 2,
name: 'asset2',
currency: 'USD'
},
{
id: 3,
name: 'asset3',
currency: 'USD'
},
{
id: 4,
name: 'asset4',
currency: 'GBP'
},
{
id: 5,
name: 'asset5',
currency: 'GBP'
},
{
id: 6,
name: 'asset6',
currency: 'GBP'
},
{
id: 7,
name: 'asset7',
currency: 'CAD'
},
{
id: 8,
name: 'asset8',
currency: 'CAD'
},
{
id: 9,
name: 'asset9',
currency: 'CAD'
},
]
export default assets;
dynamically created fields
Hi, the image above shows dynamically created fields I'm using. You can add or delete the fields with the button at the right.
My goal is to
store data that are input to these dynamically created fields into a list
bind the data / display the data at a confirmation page before submitting
The expected result is that when I key in data to these fields, it will display on the confirmation page before submitting the form
I've tried using v-model, but this only works for normal fields. Here's there code for what I have now
<template>
<div>
<div v-for="(line, index) in lines" :key="index" class="row">
<div class="col-lg-6">
<div class="row">
<div class="col-2">
<q-select
v-model="line.countryCode"
label="Country Code"
:options="countryPhoneCodes"
/>
</div>
<div class="col-10">
<q-input
v-model="line.number"
label="Phone Number"
placeholder="5551234567"
type="tel"
value=""
/>
</div>
</div>
</div>
<div class="col-lg-4">
<q-select
v-model="line.phoneUsageType"
label="Type of Usage"
:options="phoneUsageTypes"
/>
</div>
<div class="col-lg-2">
<div class="block float-right">
<q-btn round #click="removeLine(index)" icon="delete" />
<q-btn round v-if="index + 1 === lines.length" #click="addLine" icon="playlist-plus" />
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'PhoneNumberLine',
data () {
return {
lines: [],
blockRemoval: true,
phoneUsageTypes: [
{
label: 'Home', value: 'home'
}, {
label: 'Work', value: 'work'
}, {
label: 'Mobile', value: 'mobile'
}, {
label: 'Fax', value: 'fax'
}
],
countryPhoneCodes: [
{
label: '+90',
value: '+90'
}, {
label: '+1',
value: '+1'
}
]
}
},
watch: {
lines () {
this.blockRemoval = this.lines.length <= 1
}
},
methods: {
addLine () {
let checkEmptyLines = this.lines.filter(line => line.number === null)
if (checkEmptyLines.length >= 1 && this.lines.length > 0) {
return
}
this.lines.push({
countryCode: null,
number: null,
phoneUsageType: null
})
},
removeLine (lineId) {
if (!this.blockRemoval) {
this.lines.splice(lineId, 1)
}
}
},
mounted () {
this.addLine()
}
}
</script>
This should be similar to what you are trying to achieve. tweak it to fit your needs. good luck.
const makeLine = (n = 0) => ({
number: n
});
new Vue({
name: "App",
template: `
<div>
<div class="line" v-for="(line, idx) in lines" :key="idx">
<input v-model="line.number" />
<button #click="addLine" v-if="lines.length - 1 === idx">+</button>
<button #click="removeLine(idx)" v-if="lines.length > 1">-</button>
</div>
</div>
`,
data() {
return {
lines: [
makeLine(),
],
}
},
methods: {
addLine() {
this.lines.push(makeLine(this.lines.length));
},
removeLine(idx) {
this.lines = this.lines.filter((_, id) => id !== idx);
}
}
}).$mount("#app");
#app {
display: flex;
flex-direction: column;
}
.line {
display: flex;
}
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="app"></div>
I'm currently working on a page for our company's new website. In this design we have a blog page which displays our blog entries. I request these blogs with Cockpit CMS & axios in my NuxtJs project that makes it a object.
At this moment I'm struggling with the following: In the design the blogs are displayed in rows of 3 items, and after that 2 items. I'm using the BULMA framework and to display my columns the correct way I need to wrap the columns like follow:
<div class="columns">
<div class="column"></div>
<div class="column"></div>
<div class="column"></div>
</div>
<div class="columns">
<div class="column"></div>
<div class="column"></div>
</div>
<div class="columns">
<div class="column"></div>
<div class="column"></div>
<div class="column"></div>
</div>
A short version of my code can be found here:
https://jsfiddle.net/06o5nvkd/
At this moment I have a component which gets my blogs through a prop called 'blogs'. Me and my colleague can't find a good working method to split the blogs array into chunks of 2 & 3 to display them correctly in a wrapping columns div.
Is anyone here able to help us out? Any more information that's needed is welcome.
Use a computed property to chunk your blogs array on the fly and use that array with the chunks to create the columns.
Computed properties detect addtions to the blogs array automatically (when using this.blogs inside), so fetching some new blog entries will be no problem.
new Vue({
el: "#app",
data: {
blogs: [
{ title: "Learn JavaScript" },
{ title: "Learn Vue" },
{ title: "Play around in JSFiddle" },
{ title: "Build something awesome" },
{ title: "a" },
{ title: "b" },
{ title: "c" },
{ title: "d" },
{ title: "e" },
{ title: "f" },
]
},
computed: {
blogsChunked() {
let blogs = this.blogs;
let chunkSize = 2;
let blogsChunked = [];
while (blogs.length > 0) {
let chunk = blogs.slice(0, chunkSize);
blogs = blogs.slice(chunkSize);
blogsChunked.push(chunk);
chunkSize == 2 ? chunkSize++ : chunkSize--;
}
return blogsChunked;
}
}
})
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
}
div.column {
color: white;
background-color: grey;
margin: 5px;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.2/css/bulma.min.css" rel="stylesheet"/>
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.17/dist/vue.js"></script>
<div id="app">
<section class="blogs has-padding-bottom-xxl">
<div class="container">
<div class="columns is-mobile" v-for="(chunk, index) in blogsChunked" :key="index">
<div class="column" v-for="(blog, key) in chunk" :key="key">
{{ blog.title }}
</div>
</div>
</div>
</section>
</div>
One solution is to have nested arrays & v-for loops.
<div id="app">
<section class="blogs has-padding-bottom-xxl">
<div class="container">
<div class="columns" v-for="(blogs, key) in blogsArr" :key="key">
<div class="column" v-for="(blog, index) in blogs" :key="index">
{{ blog.title }}
</div>
</div>
</div>
</section>
</div>
new Vue({
el: "#app",
data: {
blogsArr: [
[{ title: "Learn JavaScript" },
{ title: "Learn Vue" }],
[{ title: "Play around in JSFiddle" },
{ title: "Build something awesome" }]
]
}
})
https://jsfiddle.net/3vqydakw/