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(); });