4

I have a section for holding calendar entries stored by postDate in the CMS. On the front end I want to basically display these in a calendar.

I have code which displays the calendar looping through each day of the month. For each day it currently queries if there is any entries for this day.

Is there a better way to do it? - my current method had a query for each day?

Current code is:

<table class="cal">
  <thead class="hide-for-small">
    <tr>
      <th width="300">Sun</th>
      <th width="300">Mon</th>
      <th width="300">Tue</th>
      <th width="300">Wed</th>
      <th width="300">Thu</th>
      <th width="300">Fri</th>
      <th width="300">Sat</th>
    </tr>
  </thead>
  <tbody>
    <tr>
    {% set time = "now"|date("U") %}        
    {% set daysInMonth = time|date('t') %}
    {% set startDow = time|date('F 1\\s\\t Y')|date('w') %}
    {% set dow = startDow %}
    {% for day in range(1,daysInMonth) %}
        {% if loop.first and startDow != 0 %}
            <td colspan="{{ startDow }}"></td>
        {% endif %}
        <td>
            <div>
                <span class="day">{{ day }}</span>
                    {% set today=thisyear ~ "/" ~ thismonth ~ "/" ~ day %}

                    {% if (category is defined) and (category is not empty) %}
                    {% set params = { section: 'calendar', status: null, postDate:today|date('Y-m-d'), relatedTo: category } %}
                    {% else %}
                    {% set params = { section: 'calendar', status: null, postDate:today|date('Y-m-d') } %}
                    {% endif %}
                    {% set entries = craft.entries(params)%}
                    {% if entries|length %}
                <div>
                    {% for entry in entries %}
                    <span class="event green">{{ entry.title }}</span>
                    {% endfor %}
                </div>
                    {% endif %}
            </div>
        </td>
        {% if loop.last and dow != 6 %}
            <td colspan="{{ 6 - dow }}">&nbsp;</td>
        {% endif %}
        {% if dow == 6 %}
            {% set dow = 0 %}
        </tr>
        <tr>
        {% else %}
            {% set dow = dow + 1 %}
        {% endif %}
    {% endfor %}
    </tr>
  </tbody>
</table>  
mmc501
  • 1,779
  • 13
  • 34

3 Answers3

9

I haven't tested this, but it should be a good starting point, at the very least.

{# Determine the number of days in the month #}
{% set daysInMonth = "now"|date("t") %}

{# Determine the date range spanning the month #}
{% set firstDayOfMonth = "now"|date("Y-m-01") %}
{% set firstDayOfNextMonth = "now"|date_modify("first day of next month")|date("Y-m-01") %}
{% set postDateParam = "and, >= " ~ firstDayOfMonth ~ ", < " ~ firstDayOfNextMonth %}

{# Create our parameters object #}
{% if (category is defined) and (category is not empty) %}
    {% set params = {section: 'calendar', postDate: postDateParam, status: null, order: 'postDate', relatedTo: category} %}
{% else %}
    {% set params = {section: 'calendar', postDate: postDateParam, status: null, order: 'postDate'} %}
{% endif %}

{# Retrieve all of the events for the month, and group them by day #}
{% set groupedEvents = craft.entries(params)|group('postDate|date("Y-m-d")') %}

{# Set a shortcut for the YYYY-MM portion of the current month #}
{% set yearMonth = "now"|date("Y-m") %}

{# Loop through all of the days in the month #}
{% for day in range(1, daysInMonth) %}
    {% set formattedDay = "%02d"|format(day) %}
    {% set dayOfMonth = yearMonth ~ "-" ~ formattedDay) %}

    {# If the current day is a key in our groupedEvents array, we have events #}
    {% if dayOfMonth in groupedEvents|keys %}
        <span class="day">{{ dayOfMonth }}</span>

        <div>
            {% for event in groupedEvents[dayOfMonth] %}
                <span class="event green">{{ event.title }}</span>
            {% endfor %}
        </div>
    {% endif %}
{% endfor %}

As I said, I haven't tested the above code, but the basic principle works (I've used it for grouping people by surname, and so forth).

Let me know if anything is unclear, I realise it's quite the chunk of Twig.

Stephen

carlcs
  • 36,220
  • 5
  • 62
  • 139
Stephen Lewis
  • 2,429
  • 11
  • 17
  • 2
    Stephen, you need to change the date filtering to get all entries within a datetime range. See this Q/A for more info. – carlcs Aug 12 '14 at 20:48
  • Thanks Stephen - works well - needed to change the date filtering as carlcs said. But I'm not getting all events pulled in - missing one on the 1st of the month. will add code below – mmc501 Aug 15 '14 at 06:22
  • 1
    @mmc501 when did you add your events to Craft? There were a couple of timezone related bugs which got fixed with Craft version 2.0.2528, 1.2.2375 and 0.9.2235. I'd try to edit the date of that missing event on the 1st of the month with the timepicker (change it to the 2nd → save, change it back to the 1st → save). – carlcs Aug 15 '14 at 08:55
  • Within the last couple of weeks. Tried editing the date of the event - discovered that if it's the 1st - 9th of the month it will not display but from the 10th on it will - must be something to do with single figure date - will have a play with it not to see if can resolve. – mmc501 Aug 15 '14 at 09:54
  • @mmc501 I've updated my example to format the day as a two-digit number (take a look at the first line of the for day in range loop). – Stephen Lewis Aug 15 '14 at 10:35
  • @carlcs Thanks for fixing the date range query. – Stephen Lewis Aug 15 '14 at 10:39
  • Excellent! - working great now! Much better than my original solution with a query for every day. – mmc501 Aug 15 '14 at 10:47
  • @mmc501 Nice one. We got there eventually. – Stephen Lewis Aug 15 '14 at 10:50
  • Was a pleasure to contribute to this nice Q/A, @stephen. I'm sure that I will use this code snippet at some point! One question (and please correct it with the code), what's the right or most common way to say it: timeframe, date range, interval? – carlcs Aug 15 '14 at 11:05
  • @carlcs In this case I'd be inclined to go with timeframe or date range. Interval suggests a regular, repeating period of time. – Stephen Lewis Aug 15 '14 at 11:27
  • @carlcs Having said that, I've just updated the example to use the rather more prosaic (but less potentially confusing) postDateParam. – Stephen Lewis Aug 15 '14 at 11:30
  • @mmc501 didn't you miss events that are on the last calendar day? See my edit to the code.. – carlcs Aug 15 '14 at 18:26
  • Cheers @carlcs - hadn't tested an entry on the last day of the month - updated to your edit now, thanks! – mmc501 Aug 18 '14 at 05:44
2

Just an update to show final current version of code for calendar - re stefan comment.

  {% if year is defined and month is defined %}
      {% set time = (year ~ "-" ~ month ~ "-" ~ "01") %}
      {% set catslug = craft.app.request.segment(2) %}
      {% if catslug != 'all' %}
        {% set category = craft.categories.group('eventFilters').slug(catslug) %}
      {% endif %}
  {% endif %}
  {% if time is not defined%} 
      {% set time = "now" %}
  {% endif %}
  {% if catslug is not defined or catslug is empty%} 
      {% set catslug = "all" %}
  {% endif %}

  {% set thisyear = time|date("Y") %}
  {% set thismonth = time|date("m") %}
  {% set prevyear = time|date_modify("-1 month")|date("Y") %}
  {% set prevmonth = time|date_modify("-1 month")|date("m") %}
  {% set nextyear = time|date_modify("+1 month")|date("Y") %}
  {% set nextmonth = time|date_modify("+1 month")|date("m") %}



  <!-- CONTENT -->

  <div class="row calhead text-center align-items-center py-2 mb-3">
      <div class="col-auto">
        <a class="calchange calleft" href="#" data-slug="{{ catslug }}" data-month="{{ prevmonth }}" data-year="{{ prevyear }}">
          <span class="fa-stack ">
            <i class="fas fa-circle fa-stack-2x"></i>
            <i class="fas fa-angle-left fa-stack-1x fa-inverse"></i>
          </span>  
        </a>
      </div>

      <div class="col"><div class="caldate">{{ time|date("F")}} {{ time|date("Y")}}</div></div>
      <div class="col-auto">
        <a class="calchange calright" href="#" data-slug="{{ catslug }}" data-month="{{ nextmonth }}" data-year="{{ nextyear }}">
          <span class="fa-stack ">
            <i class="fas fa-circle fa-stack-2x"></i>
            <i class="fas fa-angle-right fa-stack-1x fa-inverse"></i>
          </span>          
        </a>
      </div>

  </div>


  <div class="dayscontainer clearfix pb-3">
    <div class="row text-center dayhead mb-3">
      <div class="col">M</div>
      <div class="col">T</div>
      <div class="col">W</div>    
      <div class="col">T</div>
      <div class="col">F</div>
      <div class="col">S</div>
      <div class="col">S</div>
    </div>

    <div class="row text-center mb-1">
        {% if year is defined and month is defined %}
            {% set time = (year ~ "-" ~ month ~ "-" ~ "01") %}
        {% endif%}
        {% if time is not defined%} 
            {% set time = "now" %}
        {% endif %}

            {% set daysInMonth = time|date('t') %}
            {% set startDow = time|date('F 1\\s\\t Y')|date('w') %}
            {% set dow = startDow %}



        {% set yearMonth = time|date("Y-m") %}                 

        {% set startdate=(yearMonth ~ "-" ~ "01") %}
        {% set enddate=(yearMonth ~ "-" ~ daysInMonth) %}
         {# Create our parameters object #}

        {% if (category is defined) and (category is not empty) %}
        {% set params = { section: 'events', orderBy: 'eventDate', relatedTo: category } %}
        {% else %}
        {% set params = { section: 'events', orderBy: 'eventDate' } %}
        {% endif %}          
        {% set j = 0 %}
        {# Retrieve all of the events for the month, and group them by day #}
        {% set groupedEvents = craft.entries(params).eventDate(['and', '>= ' ~ startdate|date('Y-m-d'), '<= ' ~ enddate|date('Y-m-d')]).all() | group('eventDate|date("Y-m-d")') %}



          {% for day in range(1,daysInMonth) %}
            {% set formattedDay = "%02d"|format(day) %}
            {% set dayOfMonth = yearMonth ~ "-" ~ formattedDay %}       

            {# set dayOfMonth = "#{yearMonth}-#{day}" #}
            {% if startDow == 0 %}
              {% set startDow = 7 %}
            {% endif %}


              {% if loop.first and startDow > 1 %}
                {% for i in range(1,startDow-1) %}
                  {% set j = j + 1 %}
                  <div class="col">&nbsp;</div>
                {% endfor %}              

              {% endif %}
              {% set j = j + 1 %}

              {% if j == 8 %}

                </div>
                <div class="row text-center mb-1">
                {% set j = 1 %}
              {% endif %}

              <div class="col">

                {# If the current day is a key in our groupedEvents array, we have events #}
                {% if dayOfMonth in groupedEvents|keys %}
                  {% set ccontent %}
                      {% for entry in groupedEvents[dayOfMonth] %}
                      {% set category = entry.eventFilter.one() %}

                      <span class="event"><a href="{{ entry.url }}">{{ entry.title }}</a></span>
                      {% endfor %}
                  {% endset %}

                  <span class="day"><a role="button" class="pop daylink" data-toggle="popover" data-trigger="click hover focus" data-html="true" title="Events" data-content='{{ ccontent }}'>{{ day }}</a></span>
                {% else %}
                  <span class="day">{{ day }}</span>  

                {% endif %}
              </div>

              {% if loop.last and j < 7 %}
                {% for i in range(1,7 -j) %}
                  <div class="col">&nbsp;</div>
                {% endfor %}              

              {% endif %}              

          {% endfor %}
    </div>
  </div>
mmc501
  • 1,779
  • 13
  • 34
2

Thanks for this @mmc501 I have adapted the code to work the wonderful sprig plugin.

{% set time = time ?? "now" %}

<div class="grid text-center"> <div class="col"> <button sprig s-vars="time: '{{time|date_modify('-1 month')|date()}}'">Prev</button> </div> <div class="col"> <div class="caldate">{{ time|date("F")}} {{ time|date("Y")}}</div> </div> <div class="col"> <button sprig s-vars="time: '{{time|date_modify('+1 month')|date()}}'">Next</button> </div> </div>

<div class="dayscontainer">

&lt;div class=&quot;grid text-center&quot;&gt;
    &lt;div class=&quot;col&quot;&gt;M&lt;/div&gt;
    &lt;div class=&quot;col&quot;&gt;T&lt;/div&gt;
    &lt;div class=&quot;col&quot;&gt;W&lt;/div&gt;
    &lt;div class=&quot;col&quot;&gt;T&lt;/div&gt;
    &lt;div class=&quot;col&quot;&gt;F&lt;/div&gt;
    &lt;div class=&quot;col&quot;&gt;S&lt;/div&gt;
    &lt;div class=&quot;col&quot;&gt;S&lt;/div&gt;
&lt;/div&gt;

&lt;div class=&quot;grid text-center&quot;&gt;
    {% set daysInMonth = time|date('t') %}
    {% set startDow = time|date('F 1\\s\\t Y')|date('w') %}

    {% set yearMonth = time|date(&quot;Y-m&quot;) %}                 

    {% set startdate=(yearMonth ~ &quot;-&quot; ~ &quot;01&quot;) %}
    {% set enddate=(yearMonth ~ &quot;-&quot; ~ daysInMonth) %}

    {% set params = { section: 'externalEvents', orderBy: 'startDateTime' } %}

    {% set j = 0 %}

    {% set groupedEvents = craft.entries(params).startDateTime(['and', '&gt;= ' ~ startdate|date('Y-m-d'), '&lt;= ' ~ enddate|date('Y-m-d')]).all()|group('startDateTime|date(&quot;Y-m-d&quot;)') %}

    {% for day in range(1,daysInMonth) %}
        {% set formattedDay = &quot;%02d&quot;|format(day) %}
        {% set dayOfMonth = yearMonth ~ &quot;-&quot; ~ formattedDay %}       

        {% if startDow == 0 %}
            {% set startDow = 7 %}
        {% endif %}

        {% if loop.first and startDow &gt; 1 %}
            {% for i in range(1,startDow-1) %}
                {% set j = j + 1 %}
                &lt;div class=&quot;col&quot;&gt;&lt;/div&gt;
            {% endfor %}              
        {% endif %}

        {% set j = j + 1 %}

        {% if j == 8 %}
            &lt;/div&gt;
            &lt;div class=&quot;grid text-center&quot;&gt;
            {% set j = 1 %}
        {% endif %}

        &lt;div class=&quot;col&quot;&gt;
            {% if dayOfMonth in groupedEvents|keys %}
                {% for entry in groupedEvents[dayOfMonth] %}
                    &lt;a href=&quot;{{ entry.url }}&quot;&gt;{{ entry.title }}&lt;/a&gt;
                {% endfor %}
                {{ day }}
            {% else %}
                {{ day }}
            {% endif %}
        &lt;/div&gt;

        {% if loop.last and j &lt; 7 %}
            {% for i in range(1,7 -j) %}
                &lt;div class=&quot;col&quot;&gt;&lt;/div&gt;
            {% endfor %}
        {% endif %}

    {% endfor %}
&lt;/div&gt;

</div>