Power Pages

Integrate FullCalendar.io with Power Pages – from JSON basics to advanced output with Bootstrap Modal

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">&times;</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();
    });

Useful links

Franco Musso

You may also like

Leave a reply

Your email address will not be published.

More in Power Pages