In this article, we’ll talk about how we can work with JavaScript-based events in The Events Calendar plugin to build a complex customization. Our goal is to turn the photo view into a slider on small screens (e.g. phones and tablets), but retain the full layout on larger screens.

Creating a slider in Photo View

One thing we’ll do is adjust the slider based on whether the device is portrait view (we’ll show a single event and navigation) or in landscape view (we’ll show two events per slide).

Override the template files

Let’s make a few adjustments to the template first. We’re going to override these template files in the plugin:

/wp-content/plugins/events-pro/src/views/v2/photo.php
/wp-content/plugins/events-pro/src/views/v2/photo/event.php

So, let’s copy those files and place them in our theme just as we would do when customizing any template in the plugin. They can go here:

[your-theme]/tribe/events-pro/v2/photo.php
[your-theme]/tribe/events-pro/v2/photo/event.php

Customizing the templates

Here’s the code we want to use for the photo.php file:

<?php
$header_classes = [ 'tribe-events-header' ];
if ( empty( $disable_event_search ) ) {
  $header_classes[] = 'tribe-events-header--has-event-search';
}
?>
<div
  <?php tribe_classes( $container_classes ); ?>
  data-js="tribe-events-view"
  data-view-rest-nonce="<?php echo esc_attr( $rest_nonce ); ?>"
  data-view-rest-url="<?php echo esc_url( $rest_url ); ?>"
  data-view-manage-url="<?php echo esc_attr( $should_manage_url ); ?>"
  <?php foreach ( $container_data as $key => $value ) : ?>
  data-view-<?php echo esc_attr( $key ) ?>="<?php echo esc_attr( $value ) ?>"
  <?php endforeach; ?>
  <?php if ( ! empty( $breakpoint_pointer ) ) : ?>
  data-view-breakpoint-pointer="<?php echo esc_attr( $breakpoint_pointer ); ?>"
  <?php endif; ?>
>
  <div class="tribe-common-l-container tribe-events-l-container">
    <?php $this->template( 'components/loader', [ 'text' => __( 'Loading...', 'tribe-events-calendar-pro' ) ] ); ?>

    <?php $this->template( 'components/data' ); ?>

    <?php $this->template( 'components/before' ); ?>

    <header <?php tribe_classes( $header_classes ); ?>>
      <?php $this->template( 'components/messages' ); ?>

      <?php $this->template( 'components/breadcrumbs' ); ?>

      <?php $this->template( 'components/events-bar' ); ?>

      <?php $this->template( 'photo/top-bar' ); ?>
    </header>

    <?php $this->template( 'components/filter-bar' ); ?>

    <div class="tribe-events-pro-photo swiper-container">

      <div class="tribe-events-pro-photo__events tribe-common-g-row tribe-common-g-row--gutters swiper-wrapper">

      <?php foreach ( $events as $event ) : ?>
        <?php $this->setup_postdata( $event ); ?>

        <?php $this->template( 'photo/event', [ 'event' => $event ] ); ?>

      <?php endforeach; ?>

      </div>

      <button class="swiper-button-prev tribe-common-c-btn-icon tribe-common-c-btn-icon--caret-left"></button>
      <button class="swiper-button-next tribe-common-c-btn-icon tribe-common-c-btn-icon--caret-right"></button>

      <div class="swiper-pagination"></div>

    </div>

    <?php $this->template( 'photo/nav' ); ?>

    <?php $this->template( 'components/ical-link' ); ?>

    <?php $this->template( 'components/after' ); ?>
  </div>
</div>

<?php $this->template( 'components/breakpoints' ); ?>

Next, edit event.php so it looks like this

<?php
$classes = get_post_class( [ 'tribe-common-g-col', 'tribe-events-pro-photo__event', 'swiper-slide' ], $event->ID );

if ( $event->featured ) {
  $classes[] = 'tribe-events-pro-photo__event--featured';
}

?>
<article <?php tribe_classes( $classes ) ?>>

  <?php $this->template( 'photo/event/featured-image', [ 'event' => $event ] ); ?>

  <div class="tribe-events-pro-photo__event-details-wrapper">
  <?php $this->template( 'photo/event/date-tag', [ 'event' => $event ] ); ?>
    <div class="tribe-events-pro-photo__event-details">
      <?php $this->template( 'photo/event/date-time', [ 'event' => $event ] ); ?>
      <?php $this->template( 'photo/event/title', [ 'event' => $event ] ); ?>
      <?php $this->template( 'photo/event/cost', [ 'event' => $event ] ); ?>
    </div>
  </div>

</article>

We haven’t made too many changes here. Let’s add some styles so our slider looks good. Add the following to the style.css file in your theme, or using s CSS override.

.tribe-events-pro .tribe-events-pro-photo {
  margin: 0 -21px;
  overflow: hidden;
  position: relative;
}

.tribe-common--breakpoint-medium.tribe-events-pro .tribe-events-pro-photo {
  margin: 0;
  padding: 0;
}

.tribe-events-pro .tribe-events-pro-photo__events {
  flex-wrap: nowrap;
  margin: 0;
}

.tribe-common--breakpoint-medium.tribe-events-pro .tribe-events-pro-photo__events {
  flex-wrap: wrap;
}

.tribe-events-pro .tribe-events-pro-photo__event {
  flex: none;
}

.tribe-events-pro .tribe-events-pro-photo .swiper-pagination {
  display: flex;
  justify-content: center;
  padding: 15px 16px;
}

.tribe-events-pro .tribe-events-pro-photo .swiper-pagination-bullet {
  background-color: #bababa;
  border-radius: 50%;
  flex: none;
  height: 10px;
  margin: 0 6px;
  width: 10px;
}

.tribe-events-pro .tribe-events-pro-photo .swiper-pagination-bullet:hover,
.tribe-events-pro .tribe-events-pro-photo .swiper-pagination-bullet:focus,
.tribe-events-pro .tribe-events-pro-photo .swiper-pagination-bullet-active {
  background-color: #727272;
}

.tribe-events-pro .tribe-events-pro-photo .swiper-button-prev,
.tribe-events-pro .tribe-events-pro-photo .swiper-button-next {
  position: absolute;
  bottom: 10px;
}

.tribe-events-pro .tribe-events-pro-photo .swiper-button-prev {
  left: 21px;
}

.tribe-events-pro .tribe-events-pro-photo .swiper-button-next {
  right: 21px;
}

.tribe-common--breakpoint-medium.tribe-events-pro .tribe-events-pro-photo .swiper-pagination,
.tribe-common--breakpoint-medium.tribe-events-pro .tribe-events-pro-photo .swiper-button-prev,
.tribe-common--breakpoint-medium.tribe-events-pro .tribe-events-pro-photo .swiper-button-next {
  display: none;
  visibility: hidden;
}

Now that we have all our styles set up, we need to add in some JavaScript functionality. I’ll be creating my file in [your-theme]/assets/js/photo-swiper.js, but you can create yours anywhere you want, as long as it contains this:

var photoSwiper = {};

( function( $, obj ) {
  'use strict';
  var $document = $( document );
 
  obj.selectors = {
    slider: '.tribe-events-pro-photo.swiper-container',
  };
 
  obj.instances = {};
 
  obj.deinitSwiper = function( $container ) {
    var uid = $container.attr( 'data-view-breakpoint-pointer' )
    $container
    .find( obj.selectors.slider )
    .each( function( index, slider ) {
      if ( ! obj.instances[ uid + '-' + index ] ) {
        return;
      }
 
      obj.instances[ uid + '-' + index ].destroy();
    } );
  };
 
  obj.initSwiper = function( $container ) {
    var uid = $container.attr( 'data-view-breakpoint-pointer' );
    $container
    .find( obj.selectors.slider )
    .each( function( index, slider ) {
      if ( obj.instances[ uid + '-' + index ] && obj.instances[ uid + '-' + index ].initialized ) {
        return;
      }
 
      obj.instances[ uid + '-' + index ] = new Swiper( slider, {
        slidesPerView: 'auto',
        navigation: {
          prevEl: slider.querySelector( '.swiper-button-prev' ),
          nextEl: slider.querySelector( '.swiper-button-next' ),
        },
        pagination: {
          el: '.swiper-pagination',
          type: 'bullets',
          bulletElement: 'button',
          clickable: true,
        }
      } );
    } );
  };
 
  obj.setSwiper = function( $container ) {
      var containerState = $container.data( 'tribeEventsState' );
      var isMobile = containerState.isMobile;
 
      if ( isMobile ) {
        obj.initSwiper( $container );
      } else {
        obj.deinitSwiper( $container );
      }
    };
 
    obj.handleResize = function( event ) {
      obj.setSwiper( event.data.container );
    };
 
    obj.bindEvents = function( $container ) {
      $container.on( 'resize.tribeEvents', { container: $container }, obj.handleResize );
    };
 
    obj.deinit = function( event, jqXHR, settings ) {
      var $container = event.data.container;
      obj.deinitSwiper( $container );
      $container.off( 'beforeAjaxSuccess.tribeEvents', obj.deinit )
    };
 
    obj.init = function( event, index, $container, data ) {
      var $sliders = $container.find( obj.selectors.slider );
 
      if ( ! $sliders.length ) {
        return;
      }
 
      obj.setSwiper( $container );
      obj.bindEvents( $container );
      $container.on( 'beforeAjaxSuccess.tribeEvents', { container: $container }, obj.deinit );
    };
 
    obj.ready = function( event ) {
      const manager = tribe.events.views.manager;
      manager.$containers.each( function( index, container ) {
        var $container = $( container );
        obj.init( event, index, $container, manager.getContainerData( $container ) );
      } );
      $document.on( 'afterSetup.tribeEvents', manager.selectors.container, obj.init );
    };
 
    $document.ready( obj.ready );
} )( jQuery, photoSwiper );

Phew, that’s a lot of code! Let’s break this down a little bit.

First, we are using a great slider library called Swiper. This is a very customizable and accessible slider. However, you are more than welcome to use any other slider library. If you do, keep in mind that the templates and styles may have to change.

The main interaction with The Events Calendar is through the listener for the 'resize.tribeEvents' event. You can see that we aren’t just hooking into the 'resize' event on the window object. This is because The Events Calendar does some processing before firing the 'resize.tribeEvents' event that can come in handy.

The Events Calendar views have breakpoints much like media queries but do not use media queries. Instead, classes are applied based on the view container. So after every 'resize' event, we do some calculations to determine whether the container is in mobile or desktop view. In the resize handler for the photo swiper, we read the 'isMobile' container state variable which was processed just before firing the 'resize.tribeEvents' event. This determines whether to initialize the slider or not.

Lastly, we need to enqueue the required JavaScript files. Add the following code to your functions.php file:

function add_photo_swiper_js() {
  wp_enqueue_script( 'swiper-js', 'https://cdnjs.cloudflare.com/ajax/libs/Swiper/4.5.1/js/swiper.min.js', [ 'tribe-events-views-v2-viewport' ], false, true );
  wp_enqueue_script( 'custom-photo-swiper-js', get_stylesheet_directory_uri() . '/assets/js/photo-swiper.js', [ 'swiper-js' ], false, true );
}
add_action( 'wp_enqueue_scripts', 'add_photo_swiper_js' );

Once that’s done, visit your photo view to see the result. You should see a slider on mobile and a grid on desktop.

Congratulations! You’ve just implemented slider customization for photo view on mobile.

Custom slider in Photo View