javascriptmeteormeteor-blazespacebars

Blaze template helper only returning on in a Spacebars each loop in Meteor/Mongo


I have a helper which should find all events that correspond to a given month and year argument and return them as an array for the template to loop through. It appears my helper is only returning for the first instance of year though rather than looping and I don't understand why.

Here's the template:

  <template name="list">
  <ul id="ulShell">
    {{#each year in getYears}}
      <li class="liYear">
        <h2>{{year}}</h2>
      </li>
      <ul class="ulSubShell">
        {{#each month in (getMonths year)}}
          <li class="liMonth">
            <h3>{{month}}</h3>
          </li>
          <ul>
            {{#each event in (getEvents month year)}}
              <li>
                <h4>{{dayOfWeek event.start}} – {{formatDate event.start}}</h4>
                <div class="divEvent">{{event.title}} <span class="spanDep pull-right">{{event.department}}</span></div>
              </li>
            {{/each}}
          </ul>
        {{/each}}
      </ul>
    {{/each}}
  </ul>
  </template>

and here's the logic:

  let monthNames = ["January", "February", "March", "April", "May", "June",
    "July", "August", "September", "October", "November", "December"
  ];

  let today = new Date();
  //today.setDate(today.getDate());

  let upcoming = {
    start: {
      $gt: today.toISOString()
    }
  }

  let findYears = function(){
    var selectedDep = Session.get('selectedDep');
    var distinctYears = _.uniq(Events.find( { $and: [ upcoming, selectedDep ] }, {
      sort: {start: 1}, fields: {start: true}
    }).fetch().map(function(x) {
      var d = Number(x.start.substring(0, 4));
      return d;
    }), true);
    return distinctYears;
  };

  let findMonths = function(year){
    var selectedDep = Session.get('selectedDep');
    var query = {
      start: {
        $gt: new Date(year - 1, 11, 31, 21, 59, 59, 999).toISOString(),
        $lt: new Date(year + 1, 0, 0, 22, 00, 00, 001).toISOString()
      }
    }
    var distinctMonths = _.uniq(Events.find( { $and: [ upcoming, query, selectedDep ] }, {
      sort: {start: 1}, fields: {start: true}
    }).fetch().map(function(x) {
        var d = Number(x.start.substring(5, 7));
        return monthNames[d];    
    }), true);
    return distinctMonths;
  };

  ///////////  I think this is where the problem is. Maybe with the forEach() function?
  let findEvents = function(month, year){
    var selectedDep = Session.get('selectedDep');
    var events = Events.find( { $and: [ upcoming, selectedDep ] }, {sort: {start: 1}}).fetch();
    var finalEvents = new Array();
    events.forEach(function(event){
      var mDigits = monthNames.indexOf(month);
      mDigits += 1
      mDigits = mDigits.toString();
      var yearMonthSlice;
      if(mDigits.length === 1){
        yearMonthSlice = year+"-"+"0"+mDigits;
      }else if(mDigits.length === 2){
        yearMonthSlice = year+"-"+mDigits;
      }
      var getStart = event.start.substring(0, 7);
      if(yearMonthSlice === getStart){
        finalEvents.push(event);
      }
    });
    return finalEvents;
  };

  Template.list.onCreated( () => {
    let template = Template.instance();
    template.subscribe( 'events' );
  });

  Template.list.helpers({
    getYears() {
      foundYears = findYears();
      return foundYears;
    },
    getMonths(year) {
      foundMonthNames = findMonths(year);
      return foundMonthNames;
    },
    getEvents(month, year) {
      var foundEvents = findEvents(month, year);
      return foundEvents;
    },
    formatDate(start) {
      var dayNumber = start.substring(8, 10);
      return dayNumber;
    },
    dayOfWeek(start) {
      var days = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'];
      var x = Number(new Date(start).getDay());
      var dayName = days[x];
      return dayName;
    }
  });

The list is rending in my browser like this:

Screen shot of list

Any help would be very much appreciated. I'm new to all of this especially Blaze/Spacebars/Meteor/MongoDB


Solution

  • Let's simplify your example down to the bare minimum in blaze assuming that dates are stored as dates instead of strings!

    We're going to:

    1. Find the list of unique years corresponding to all the events
    2. Find the list of unique month numbers corresponding to the year we are looking at
    3. Find all the events that are in that month and year combination
    4. Do as little date math as possible and no string manipulations

    html:

    <template name="list">
      {{#each year in getYears}}
        {{year}}
        {{#each monthNumber in getMonths}}
          {{monthName monthNumber}}
          {{#each event in (getEvents monthNumber year)}}
            {{start}}
          {{/each}}
        {{/each}}
       {{/each}}
    </template>
    

    js:

    Template.list.helpers({
      getYears(){
        const years = Events.find({},{sort: {start: 1}}).map(event=>event.start.getFullYear()));
        return _.uniq(years)
      },
      getMonths(year){
        const months = Events.find(
          {start: {$gte: new Date(year,0,1), $lt: new Date(year+1,0,1)}},
          {sort: {start: 1}})
          .map(event=>event.start.getMonth()));
        return _.uniq(months); // this returns integers in [0,11]
      },
      getEvents(monthNumber,year){
        return Events.find(
          {start: {$gte: new Date(year,monthNumber,1), $lt: new Date(year,monthNumber+1,1)}},
          {sort: {start: 1}});
      },
      monthName(month){
        return monthNames[month];
    });
    

    Note that the javascript Date() class is smart enough to wrap the year when month > 11 - ditto for the remaining date fields.