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

11,813

Solution 1

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

Solution 2

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/

Share:
11,813

Related videos on Youtube

Zeth
Author by

Zeth

Web developer. Making websites in primarily WordPress and Vue.

Updated on September 15, 2022

Comments

  • Zeth
    Zeth over 1 year

    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 -->
    
  • Zeth
    Zeth over 5 years
    This is a pretty nice work-around. But can it really be true, that it isn't possible to print/echo/check-something at a given iteration using Vue? That I, in that case, have to do it in a method or something?
  • keysl
    keysl over 5 years
    You can implement the splice and slicing in a computed so you don't need to use a method