Stage 1 – Create a JSON web template to feed the calendar
Key skills covered
- Liquid – fetchxml, if, for and filters
- Creating a JSON endpoint
- Accessing columns from related records
- Preparing dates in ISO8601 format using liquid filters
- Escaping HTML using liquid filters
- Encoding URLs using liquid filters
- Dealing with comma separators
Snippets
Here’s a static example of the JSON before we make dynamic and loop over our advanced find results:
[
{
"id": "39b0f281-7fbe-ed11-83fe-002248c5ec11",
"title": "Hayarkon Park",
"start": "2023-03-09T19:00:00",
"end": "2023-03-09T23:30:00",
"url": "https://www.tmisrael.co.il/event/MHP09/ALL/en",
"extendedProps": {
"venue1": "Hayarkon Park",
"venue2": "Tel Aviv, IL",
"artist": "Guns N' Roses",
"tour": "Middle East: Summer 2023"
},
"description": "Guns N' Roses is headed back out on the road in 2023 with a global tour that will touch down in the Middle East, Europe and North America."
},
{
"id": "020bb706-7fbe-ed11-83fe-002248c5ec11",
"title": "Etihad Arena",
"start": "2023-06-01T19:00:00",
"end": "2023-06-01T23:30:00",
"url": "https://www.tmisrael.co.il/event/MHP09/ALL/en",
"extendedProps": {
"venue1": "Etihad Arena",
"venue2": "Abu Dhabi, AE",
"artist": "Guns N' Roses",
"tour": "Middle East: Summer 2023"
},
"description": "Guns N' Roses is headed back out on the road in 2023 with a global tour that will touch down in the Middle East, Europe and North America."
}
]
The completed liquid template, with events populated dynamically:
{% fetchxml tour_dates_query %}
<fetch version="1.0" mapping="logical" no-lock="false">
<entity name="musdyn_tourdate">
<attribute name="musdyn_name"/>
<order attribute="musdyn_startdateandtime" descending="false"/>
<filter type="and"><condition attribute="statecode" operator="eq" value="0"/></filter>
<attribute name="musdyn_venue"/>
<attribute name="musdyn_tour"/>
<attribute name="musdyn_ticketavailability"/>
<attribute name="statuscode"/>
<attribute name="musdyn_startdateandtime"/>
<attribute name="musdyn_tourdateid"/>
<attribute name="musdyn_endtimeanddate"/>
<attribute name="musdyn_alldayevent"/>
<attribute name="musdyn_bookinglink"/>
<link-entity name="musdyn_tour" from="musdyn_tourid" to="musdyn_tour" link-type="outer" alias="tour" visible="false">
<attribute name="musdyn_description"/>
<attribute name="musdyn_mainartist"/>
<attribute name="musdyn_artwork_url"/>
</link-entity>
</entity>
</fetch>
{% endfetchxml %}
{% if tour_dates_query.results.entities.size > 0 %}
[
{% for result in tour_dates_query.results.entities %}
{
"id": "{{ result.musdyn_tourdateid }}",
"title": "{{ result.musdyn_name }}",
"start": "{{ result.musdyn_startdateandtime | date_to_iso8601 }}",
"end": "{{ result.musdyn_endtimeanddate | date_to_iso8601 }}",
"extendedProps": {
"venue1": "{{ result.musdyn_name }}",
"venue2": "{{ result.musdyn_venue }}",
"artist": "{{ result['tour.musdyn_mainartist'].name }}",
"tour": "{{ result.musdyn_tour.name }}",
"ticket_availability": "{{ result.musdyn_ticketavailability.label }}",
"purchase_url": "{{ result.musdyn_bookinglink }}",
"description": "{{ result['tour.musdyn_description'] | escape }}",
"artwork": "{{ result['tour.musdyn_artwork_url'] | url_encode }}",
"tour_id": "{{ result.musdyn_tour.id }}",
},
}{% unless forloop.last %},{% endunless %}
{% endfor %}
]
{% endif %}
Useful links
Stage 2 – Basic output
Key skills covered
- Including the fullcalendar library
- Initialising the calendar using script tags
- Including a web template to provide the JSON feed of events
Snippets
Boilerplate including the fullcalendar library via CDN, a placeholder div with an id of calendar, providing a feed of events from another web template, and initialising a calendar:
<div class="container">
<div id='calendar'></div>
</div>
<script src='https://cdn.jsdelivr.net/npm/fullcalendar@6.1.4/index.global.min.js'></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
var calendarEl = document.getElementById('calendar');
var calendar = new FullCalendar.Calendar(calendarEl, {
initialView: 'dayGridMonth',
events: {% include 'Name of Event JSON Web Template here' %}
});
calendar.render();
});
</script>
Useful links
Stage 3 – Advanced output with Bootstrap Modal
Key skills covered
- Useful FullCalendar configuration to customise event details, including nextDayThreshold, timeZone, eventTimeFormat
- Adding on click functionality to fullcalendar events
- Adding a Bootstrap Modal (aka a popup dialogue / modal dialogue)
- Using jQuery selectors, attributes and data attributes to populate the model dynamically with details from our event objects
Snippets
Bootstrap Modal with placeholders, ready to populate
<div id="fullCalModal" class="modal fade" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
<h4 class="modal-title"></h4>
</div>
<div class="modal-body">
<div class="row">
<div class="col-md-6">
<img data-id="event-artwork" class="img-responsive" alt="Tour poster">
</div>
<div class="col-md-6">
<h2 data-id="artist"></h2>
<h3 data-id="tour"></h3>
<p><span class="glyphicon glyphicon-map-marker" aria-hidden="true"></span> <span data-id="event-venue1"></span>
<span data-id="event-venue2"></span></p>
<p><span class="glyphicon glyphicon-calendar" aria-hidden="true"></span> <span data-id="event-start-date"></span> <span class="glyphicon glyphicon-time" aria-hidden="true"></span> <span data-id="event-start-time"></span></p>
<p data-id="event-description"></p>
<p data-id="ticket-availability"></p>
</div>
</div>
</div>
<div class="modal-footer">
<a role="button" class="btn btn-default" data-dismiss="modal">Close</a>
<a data-id="buy-tickets" role="button" class="btn btn-primary">Buy Tickets</a>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
Final JavaScript to initialise the calendar with our custom settings and to populate the modal on click:
document.addEventListener('DOMContentLoaded', function() {
var calendarEl = document.getElementById('calendar');
var calendar = new FullCalendar.Calendar(calendarEl, {
initialView: 'dayGridMonth',
nextDayThreshold: '06:30:00',
timeZone: 'Europe/London',
eventClick: function(info) {
var dateSettings = { "year": "numeric", "month": "2-digit", "day": "2-digit" };
var timeSettings = { "hour": "2-digit", "minute": "2-digit", "hour12": false };
var startdate = calendar.formatDate(info.event.start, dateSettings);
var starttime = calendar.formatDate(info.event.start, timeSettings);
var enddate;
var endtime;
if (info.event.end != null) {
enddate = calendar.formatDate(info.event.end, timeSettings );
endtime = calendar.formatDate(info.event.end, dateSettings );
}
else {
enddate = startdate;
endtime = starttime;
}
$('.modal-title').html(info.event.title);
$('[data-id="event-start-date"]').html(startdate);
$('[data-id="event-start-time"]').html(starttime);
$('[data-id="event-venue1"]').html(info.event.extendedProps.venue1);
$('[data-id="event-venue2"]').html(info.event.extendedProps.venue2);
$('[data-id="artist"]').html(info.event.extendedProps.artist);
$('[data-id="tour"]').html(info.event.extendedProps.tour);
$('[data-id="ticket-availability"]').html(info.event.extendedProps.ticket_availability);
$('[data-id="buy-tickets"]').attr('href',info.event.extendedProps.purchase_url);
var artwork = decodeURIComponent(info.event.extendedProps.artwork) + "&Full=true";
$('[data-id="event-artwork"]').attr('src',artwork);
$('#fullCalModal').modal();
},
events: {% include 'Concert Calendar' %},
eventTimeFormat: {
hour: 'numeric',
minute: '2-digit',
omitZeroMinute: true,
meridiem: 'short'
}
});
calendar.render();
});


