11

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>
    </div>
</div>
Zeth
  • 2,273
  • 4
  • 43
  • 91
  • In order to avoid getting this closed as a duplicate, could you please [edit your question](https://stackoverflow.com/posts/53075877/edit), referencing those other posts and explain how / why they are not working for you? – Phil Oct 31 '18 at 03:35
  • I've now added the ones, that was the closest to a solution, and why I couldn't use them ( @Phil ). – Zeth Oct 31 '18 at 03:50
  • Cheers. I imagine you'd prefer not to set a fixed height for the `items-container`, correct? So you'd like the second column to start at the logical half-way point based on the number of items? – Phil Oct 31 '18 at 03:55
  • Thinking you could go for an easy solution using conditional class `v-for="item, key, index in items['data']" v-bind:class="{ clearfix: !(index % 2) }"1 – Antony Gibbs Oct 31 '18 at 03:55
  • @Phil : Yes, - I'd strongly prefer for that not to have a set height, since the content will be pulled from an API, - so I can't control if a bunch of stuff is pulled. So the grid should be as flexible as possible. – Zeth Oct 31 '18 at 04:01
  • Thanks @Anthony Gibbs for your suggestion : That would add the classes, - and then I could float them around. But is that really the proper way of dealing with a situation like this? Seems a bit hacky to me? ... :-/ – Zeth Oct 31 '18 at 04:02

3 Answers3

13

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/vue@2.5.17/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

Phil
  • 157,677
  • 23
  • 242
  • 245
1

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/

keysl
  • 2,127
  • 1
  • 12
  • 16
  • 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? – Zeth Oct 31 '18 at 04:17
  • You can implement the splice and slicing in a computed so you don't need to use a method – keysl Oct 31 '18 at 04:23
0

For anyone who's attempting to achieve the same result (divide items into columns), but splitting an object instead of an array.

This solution is based in Phil's selected answer.

It uses Object.keys to loop through the object and Object.entries to return an array out of the object. Instead of accessing through index in your html template, you would do it by key.

new Vue({
    el: 'main',
    data: {
        items: { 
          item1: { name: 'Item 1' }, 
          item2: { name: 'Item 2' }, 
          item3: { name: 'Item 3' },
          item4: { name: 'Item 4' },
          item5: { name: 'Item 5' },
          item6: { name: 'Item 6' },
          item7: { name: 'Item 7' },
          item8: { name: 'Item 8' },
          item9: { name: 'Item 9' },
          item10: { name: 'Item 10' },
        },
        cols: 2
    },
    computed: {
          columns: function() {
              let columns = [];
              let mid = Math.ceil(Object.keys(this.items).length / this.cols);

              for (let col = 0; col < this.cols; col++) {
                  columns.push(Object.entries(this.items).slice(col * mid, col * mid + mid).map(entry => entry[1]));
              }
              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://cdnjs.cloudflare.com/ajax/libs/vue/2.5.9/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/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, index) in Object.keys(column)">
        {{column[item].name}}</div>
      </div>
    </div>
</main>
Aurinxki
  • 111
  • 1
  • 6