Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

V-if inside v-for - display list of items in two columns

Tags:

css

vue.js

vuejs2

I'm sorry for posting this, - because I can see that many questions similar to this one has been asked several time. Here's the ones that came closes to helping me - and why they didn't:

  • This one is because the calculation shouldn't be done in the rendering, but rather in the methods/computed section. That doesn't help me.
  • This one is using two different templates, writing the v-if on the template-tag. This would seem foolish in my case, since the two templates would be 98% identical.
  • This Medium-article addresses a problem very very close to mine. However, - it's a filtering of users in his case (which is solved by computed properties), and not an if-clause that inserts a snippet of code at a certain iteration (which is what I think I'm looking for).

The problem

I have a list of items, being pulled from an API, - so the amount will change. I want them displayed in two columns as such:

-----------------
| Item1   Item5 |
| Item2   Item6 |
| Item3   Item7 |
| Item4         |
-----------------

I'm looping through them using a v-for loop.

My attempts

  1. Using pure CSS with display: flex

But that can only do this:

-----------------
| Item1   Item2 |
| Item3   Item4 |
| Item5   Item6 |
| Item7         |
-----------------
  1. Using CSS with column-count: 2;

But that breaks of the column mid-element, regards of display: block; overflow: hidden; and many other attemps. It should be said, that the height of these elements can vary.

  1. So I gave up on fixing it using CSS.

If it had been php, then I'd simply do something like this:

<?php
if( $index == count( $items)/2 ):
  echo '</div>';
  echo '</div>';
  echo '<div class="col-md-6">';
  echo '<div class="item-container">';
endif;
?>

... But it's not. And I'm looking for the vue-alternative. I tried this:

{{#if key === Number( items.length / 2 ) }}
  </div>
  </div>
  <div class="col-md-6">
  <div class="item-container">
{{/if}

But it doesn't work. And as far as I can tell, then it's not 'the vue way' of doing it. But I can't figure out what is. :-/

Does any such thing exist?

A simplification of my current code

<div class="col-md-12">
    <div class="items-container">
        <div class="item-container" v-for="item, key in items['data']">
            <!-- A BUNCH OF ITEM-INFO -->
        </div><!-- /.item-container -->
    </div><!-- /.items-container -->
</div><!-- /.col-md-12 -->
like image 882
Zeth Avatar asked Oct 31 '18 03:10

Zeth


People also ask

Can we use V-for and V-if together?

use v-for and v-if together could cause unexpected problem , it is not suggested according to the official document. You need to convert your object to an Array to be able to filter it.

Why is it recommended not to use V-if and V-for directives together on the same element in VUE JS?

It is recommended to avoid using both on the same element due to the syntax ambiguity.

What is the difference between V-if and V show?

The key difference is that v-if conditionally renders elements and v-show conditionally displayselements. This means that v-if will actually destroy and recreate elements when the conditional is toggled. Meanwhile, v-show will always keep the element in the DOM and will only toggle its display by changing its CSS.


2 Answers

What I would do is create a computed property dividing (or chunking) the items array into the appropriate number of columns.

Here's an example that uses a flexbox layout and one extra column element.

new Vue({
  el: 'main',
  data: {
    items: ['Item 1', 'Item 2', 'Item 3', 'Item 4', 'Item 5', 'Item 6', 'Item 7'],
    cols: 2
  },
  computed: {
    columns () {
      let columns = []
      let mid = Math.ceil(this.items.length / this.cols)
      for (let col = 0; col < this.cols; col++) {
        columns.push(this.items.slice(col * mid, col * mid + mid))
      }
      return columns
    }
  }
})
.container {
  display: flex;
  border: 1px solid;
}
.col {
  margin: 10px;
  border: 1px solid;
  flex-grow: 1;
  display: flex;
  flex-direction: column;
}
.item-container {
  border: 1px solid;
  padding: 5px;
  margin: 5px;
}
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.min.js"></script>
<main>
<p><label>Columns:<label> <input type="number" v-model="cols"></p>
<div class="container">
  <div class="col" v-for="column in columns">
    <div class="item-container" v-for="item in column">{{item}}</div>
  </div>
</div>
</main>

If you'd like a less verbose way of chunking the items array, see Split array into chunks

like image 72
Phil Avatar answered Oct 10 '22 06:10

Phil


Its good to see someone who stumbled across the same problem as me. I had to position 6 items in each column. I sliced the API-response into the columns and printed them afterwards.

let allCategory = response.body.Categories.slice(); //clone
while (allCategory.length > 0) {
  let chunk = allCategory.splice(0,6);
  this.ColArray.push(chunk);
}  

ColArray is an array that then will contain arrays of the columns. It would look like this:

{
  ColArray: [
    Column1: [
      Item1,
      Item2,
      Item3,
    ],
    Column2: [
      ...
    ]
  ]
}

In Vue it will just be looped through, like this:

<div v-for="(col,colIndex) in ColArray" :key="'cate_col'+colIndex" class="col-md-2">
  <div v-for="(row,rowIndex ) in col"   :key="'cate_row'+colIndex + rowIndex" class="row">
    {{row}}
  </div>
</div>

Here is a sample fiddle :

https://jsfiddle.net/keysl183/50wL7mdz/775484/

like image 39
keysl Avatar answered Oct 10 '22 07:10

keysl