Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Populating Bootstrap grid with dynamic data using #each in Meteor

Firstly thanks for your help and forgive my infant Meteor and Bootstrap skills. I am having a similar problem to the question raised here that has generate some suggestions but no solution. I want to populate a BootStrap grid using data from MongoDB in a Meteor template using #each. Since the BootStrap grid has 12 columns and I want to display 4 'cells' per row I believe I need to -

  1. Create a row using .
  2. Output four data elements inside ...element...
  3. Close the 'row div' with .
  4. Create the next row using ...
  5. Rinse and repeat from Step 2.

Step 2 is performed using a {{#each...}} block returning data from an array/collection.

My Meteor Template looks like this (I'm extending an example from the excellent 'Discovering Meteor' book) -

<template name="postsList"> 
<div class="posts">
   <div class='row-fluid' style="margin-left:1%">
      {{breakInit}}
      {{#each posts}}
         <div class="span3">
           {{> postItem}}
         </div>
         {{breakNow}}
      {{/each}}
   </div>
</div> 
</template>

The JavaScript for the Helpers look like this -

Template.postsList.breakInit = function() {
Template.postsList.docCount = 0 ;
};

Template.postsList.breakNow = function() {
count=Template.postsList.docCount + 1 ;
result="";
if ( count == 4 ) {
    count = 0 ;
    Template.postsList.docCount = count ;
    result="</div><div class=row>" ;
};
Template.postsList.docCount = count ;
return new Handlebars.SafeString(result);
};

This all appears to work, at least in terms of counting the elements output by the #each, returning the </div><div class=row> to start a new row and resetting the counter... However... the returned HTML to end the current row and start the next does not appear to be handled by Bootstrap (or Meteor or my browser) as I expect it to. It appears to be re-ordered as <div class=row></div>. See this screen-cap from Inspector in FireFox (code outputs 6 elements, 4 in first row, 2 in second) -

<div id="main" class="row-fluid">
  <div class="posts">
    <div class="row">
      <div class="span3"> … </div>
      <div class="span3"> … </div>
      <div class="span3"> … </div>
      <div class="span3"> … </div>
      <div class="row"> … </div>  <-- The problem...
      <div class="span3"> … </div>
      <div class="span3"> … </div>
    </div>
  </div>
</div>

Notice the <div class=row>...</div> in the middle of the spans? Doesn't look right, it should be closing the previous 'row' DIV and starting the next one. Can anyone suggest either a fix for my code or an alternative method for popluating a grid using dynamic data in Meteor?

like image 594
Andy Avatar asked Sep 17 '13 08:09

Andy


3 Answers

You can group your data before it gets rendered:

Template.projectList.helpers({
    projects: function () {
        all = Projects.find({}).fetch();
        chunks = [];
        size = 4;
        while (all.length > size) {
            chunks.push({ row: all.slice(0, size)});
            all = all.slice(size);
        }
        chunks.push({row: all});
        return chunks;
    }
});

<template name="projectList">
  {{#each projects}}
    {{> projectRow }}
  {{/each}}
</template>

<template name='projectRow'>
  <div class='row span12'>
    {{#each row }}
      {{> projectItem}}
    {{/each}}
  </div>
</template>

<template name="projectItem">
  <div class="span4">
    <h3><a href="{{projectPagePath this}}"> {{title}} </a></h3>
    <p> {{subtitle}} </p>
    <p> {{description}} </p>
    <p><img src="{{image}}"/></p>
    <p> {{openPositions}} </p>
  </div>
</template>
like image 195
Jim Mack Avatar answered Oct 22 '22 21:10

Jim Mack


The trouble lies in trying to add invalid HTML (</div><div class=row>). Even as a DocumentFragment, it's not valid (in general, difference between a Document and DocumentFragment is that a Document can only have a single document element (root node, like <html> for example), whereas a DocumentFragment can have multiple document elements).

So, when Spark tries to process your string and stuff it in the DOM, it gets automatically corrected. It's beyond Spark's control too, the browser DOM does that.

What you'll need to do is do an each nested within another each. The first will iterate over rows, the second over the columns for that row.

Some pseudo-code:

{{#each rows}}
    <div class="row-fluid">
        {{#each posts row}}
            <div class="span3">
                {{> postItem}}
            </div>
        {{/each}}
    </div>
{{/each}}

Template.postsList.rows = function () {
    // 1. Get cursor of posts (cursor = posts.find({...});
    // 2. Get count from cursor (count = cursor.count());
    // 3. Divide count by desired columns per row, then Math.ceil it (I think!)
    // 4. Return an array of objects each containing a "row" key, with some various values (current row probably, count, etc)
};

Template.postsList.posts = function (row) {
    // Return a cursor of posts that is offset and limited (see meteor docs) based on values found in the row object
};

Hope that's enough to get you going.

EDIT: In step 3 (divide count by desired columns per row), you could potentially have this number come from the template instead so you're not mixing those details between code and templates. For example: {{#each rows 4}}.

like image 2
matb33 Avatar answered Oct 22 '22 20:10

matb33


The less complicated solution is simply using an adaption of the Foundation Grid System

https://github.com/JohnnyTheTank/bootstrap-block-grid

It follows a better approach, where you define the column count once and all elements will follow that rule and layout dynamically.

<div class="block-grid-xs-2 block-grid-sm-3 block-grid-md-4">
    <div>
        Content 1
    </div>
    <div>
        Content 2
    </div>
    <div>
        Content 3
    </div>
    <div>
        Content 4
    </div>
    <div>
        Content 5
    </div>
    <div>
        Content 6
    </div>
</div>
like image 1
Marian Klühspies Avatar answered Oct 22 '22 21:10

Marian Klühspies