A client of ours recently wanted to display events on their website. They weren’t planning a huge number of events, maybe one a month spread through the year. They wanted a simple way to add events, write a post about the event and then display these ‘event posts’ both as a grid (for upcoming events on the home page) and as an archive (for listing all of the events).

We spent some time looking at available calendar and event plugins and there are four of five great options. However, all of these options would require some styling (to match the website) and some of these would only display events in a calendar format which we did not really want because the relatively small number of events needed would look a bit ‘lost’ in this format. Lastly, when we can, we prefer to create a solution ourselves rather than use a plugin.

So, we decided to build a very simple event system, using blog posts to represent each event. We added custom fields to these blogs to allow users to enter the date information needed for the events. We used some PHP to modify the back-end user interface to display these extra fields using a meta-data box.  We also created a custom module (an Event Module) to display events, using the existing Blog Module as a template. Finally, we styled the output from the new event module using CSS.

Here is what the events look like when displayed in grid format –

…and here is what the events look like when displayed fullwidth. You can see that the new Event Module is based on the existing Blog Module but that it has been modified to output the event dates.

We are going to be using CSS and PHP to achieve this and we recommend using a child theme. There are many good articles on the internet about creating child themes.

We Can’t Simply Use An Event Post Category

We had always thought that using blog posts would be a good basis for meeting the client requirement. So why not simply create a new category called ‘event’ and then use the blog module to display these? There are two problems with this. Firstly, blogs are ordered by publication date and we wanted to order them by event date instead. We could simply manipulate the publication date to make sure the order of blog posts matched the event date order but we thought that would be confusing and difficult to get right. The bigger issue, though, was that we really wanted some way of displaying the event information automatically and we can’t do that easily by just using the blog module. We thought about having a standard format for the first few lines of the blog post that would include the event information. so that this was displayed on the post and in the excerpts, but, again, this just seemed prone to error and we really wanted more control over how the information was being displayed.

So, we decided that using blogs would not be good enough. We needed to allow for the input and editing of additional meta data in the backend (the event dates) and to create a custom blog post module that would allow us more control over the output format of grid and fullwidth displays, including their ordering.

About The Event Data

From looking at the events the client had planned we could see that they were either single day events, or that they repeated on the same day of the week for a short period (every Wednesday for four weeks from the 5th of May for example) or they covered consecutive days (the 10th to the 14th May for example). So we needed to allow for these three event types and the information we needed to store for each of these was –

Single Day Event – START DATE

Repeating Event – START DATE and REPEAT NUMBER

Consecutive Event – START DATE and END DATE

We agreed with the client not to store (or display) any other information that might be associated with an event such as start and end times, location etc. The client would include that information in the blog content.

About The Meta Data

We decided to store the additional event data as post meta data. So each ‘event post’ would now have the event data attached to it in the database. So, given the event types, we would need three extra pieces of meta data – we needed to store the start date, the end date and the ‘repeat’ number. These extra meta data fields would not all be used though, depending on the event type. This would also allow us to identify the three event types simply from the information we held about each of them. Single day events had a start date but nothing else, repeating events had a start date and a repeat number but no end date and consecutive events had a start and end date but no repeat length. There was no need to have an additional field to identify the ‘event type’ – we could identify them from the ‘missing’ meta data.

Another important decision we took about the meta data was what format to use to store dates. The existing blog module is written in PHP and we planned to use this module as a template to make a new ‘event module’. So we knew we would be using PHP and it seemed therefor appropriate to store dates as Unix timestamps. This is a data type intended to allow us to easily manipulate dates (including comparing dates, something we would need to order the events). A Unix timestamp actually stores dates as a number – the number of seconds since January 1st 1970. So, date arithmetic and date comparison becomes straight forward as it is, literally, reduced to arithmetic.

Adding Post Meta Data Using A Meta Box

The obvious way to add extra meta data to a blog is to use a custom field plugin. However, we decided to use a meta box instead and to code it ourselves. A meta box is a small UI element that will appear when editing a post on the right hand side, near where Featured Image etc. is displayed. We used a meta box because we wanted to keep the event system separate from other custom meta data we might use in the user interface. We are going to write a little PHP code to create the meta box. There are many tutorials available that explain how to make meta boxes. We list a few of these at the end of this blog. Generally, if the code is simple, we prefer to add code than to add a plugin. 

The PHP code for the meta box needs to go into the functions.php file of your child theme. Here is the first fragment of code to add –

/* Create custom meta data box to display on the post edit screen */
function event_date_meta_box ( $post ){
  add_meta_box( 
    'custom_event_date', 
    'Event Information', 
    'display_event_date', 
    'post' ,
    'side'
    );
}
add_action( 'add_meta_boxes', 'event_date_meta_box' ); 

What this does is to create the meta box by adding an action to the add_meta_boxes hook. it is this action, our new function – event_date_meta_box – that is run when WordPress sets up the meta boxes just before displaying posts in the back end. This new function uses add_meta_box to create the meta box. The first argument passed to add_meta_box is the ID for the meta box, the second argument is used as the title of the meta box when it is displayed, the third is the callback function (we haven’t defined this yet) used to populate the meta box with fields, the fourth argument (‘post’) tells WordPress to add our meta box to all posts, not just event category posts. This means that our meta box is going to be displayed on all posts. The final, fifth argument is telling WordPress to display the meta box in the sidebar.

If you add this code to your child theme’s functions.php file and then edit one of your posts in the backend of your site, you should see the meta box in the sidebar. It will have a title of ‘Event Information’ but otherwise, it will do nothing. It might give you an error message, the callback function, display_event_date, is not defined 🙂

Next we will add the PHP code to request the meta data from the database and display it in the meta box. it should not matter for now that we have not entered any data into the database. Here is the code. Add it to your functions.php –

/* get our meta data from the database and display it in the Meta Box */
function display_event_date( $post ) {
  wp_nonce_field( basename( __FILE__ ), 'event_date_nonce' );

  echo '<br/>' . 'Single Day Events - Add Start Date' . '<br/>' . '<br/>';
  echo 'Weekly Repeating Events - Add Start Date and Number of Repitition Weeks' . '<br/>' . '<br/>';
  echo 'Consecutive Day Events - Add Start Date and End Date' . '<br/>' . '<br/>';
  
  $event_start_date = get_post_meta( $post->ID, '_event_start_date', true);	
  echo '<p>Start Date (dd/mm/yy)</p>';
  if ($event_start_date) {
    echo '<p><input type="text" name="event-start-date" value="' . date('d/m/y', $event_start_date) . '" /></p>';
  } else {
        echo '<p><input type="text" name="event-start-date" value="" /></p>';
  }
  
  $event_reps = get_post_meta( $post->ID, '_event_reps', true);
  echo '<p>Repetition Weeks</p><p><input type="number" name="event-reps" value="' . $event_reps . '" /></p>';
 
  $event_end_date = get_post_meta( $post->ID, '_event_end_date', true);
  echo '<p>End Date (dd/mm/yy)</p>'; 
  if ($event_end_date) {
    echo '<p><input type="text" name="event-end-date" value="' . date('d/m/y', $event_end_date) . '" /></p>';
  } else {
        echo '<p><input type="text" name="event-end-date" value="" /></p>';
  }
}

This new function, display_event_date, runs when WordPress initialises the meta box, just before displaying it. WordPress knows to do this because we passed this function to add_meta_box when we called it.

display_event_date looks a bit complicated but it isn’t – most of the work it is doing is to format the content of the meta box. That’s what all of the calls to echo are doing – they are outputting HTML to format the meta box display. The really interesting part of this code are the calls to get_post_meta. It is these that request the meta data from the database. We know the ID of the post to ask for because it is stored in the $post->ID. Lastly, we have created names for each of the new meta data fields needed – _event_start_date etc.

One other point to mention is our use of the PHP date function. This outputs Unix timestamp dates in a specified format. To do this requires two arguments – a string to tell it the output format to be used and the Unix timestamp itself. So, for the start date, for example, we made a request to the database for the content of the _event_start_date field of the current post, then stored it in the $event_start_date, variable, which we, in turn, output to the meta box in a ‘d/m/y’ format using the PHP date function.

Lastly, you might notice that we use a nonce. Let’s not worry too much about what this is but simply note that it is part of the WordPress security system, so it’s a good idea to include it!

So, when you add this code, you should now be able to see the new meta data fields in the meta box –

Of course, it won’t do anything yet 🙂 We still need to add the code to enable the meta box to write data from its fields into the database. Here is the code to do this. Add it to your functions.php file and you should now be able to store and display the new meta data –

/* Save the input to the database */
function save_event_date( $post_id ){
  if ( !isset( $_POST['event_date_nonce'] ) || !wp_verify_nonce( $_POST['event_date_nonce'], basename( __FILE__ ) ) ){
    return;
  } 
  if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ){
    return;
  }
  if ( ! current_user_can( 'edit_post', $post_id ) ){
    return;
  }
  if ( isset( $_REQUEST['event-start-date'] ) ) {
    update_post_meta( $post_id, '_event_start_date',
        date_timestamp_get(date_create_from_format('d/m/y', sanitize_text_field( $_POST['event-start-date'])))
    );
  }
  if ( isset( $_REQUEST['event-end-date'] ) ) {
    update_post_meta( $post_id, '_event_end_date', 
        date_timestamp_get(date_create_from_format('d/m/y', sanitize_text_field( $_POST['event-end-date'])))
    );
  }
   if ( isset( $_REQUEST['event-reps'] ) ) {
    update_post_meta( $post_id, '_event_reps', sanitize_text_field( $_POST['event-reps'] ) );
  }
}
add_action( 'save_post', 'save_event_date' );

So this new function, save_event_date, is the ‘opposite’ to display_event_date. It is also simpler, because it does not need to output HTML. The first thing it does is some housekeeping (including sorting out the nonce) then it uses update_post_meta to write the data from the meta box back into the correct field (_event_start_date etc.) for the current post (using the $post_id which, this time, is passed to us as an argument).

Notice also that this time we need to use add_action to make sure that our function is called when the post is saved. This is quite an important point because running this code on the post save means WordPress has arranged to place the data in the $_POST array. This is something that WordPress does for forms (such as our meta box). So we lookup the $_POST array, then write the data into the WordPress database. $_POST is an associative array, so we can use the field names to access it. It’s also a good idea to use the built-in WordPress function sanitize_text_field to remove any unwanted characters and tags that might have been input to the meta box fields. Lastly, date_create_from_format is a built in PHP function that takes a string specifying the expected date format (we use ‘d/m/y’) along with the string containing the date and returns a PHP DateTime object. This, in turn is converted into a Unix timestamp by date_timestamp_get, another built in PHP function. 

So, if you are still with us 🙂 you should be seeing your meta box on your posts in the backend and you should be able to input dates, save the post, navigate away, then return to the post and see your event dates displayed correctly. Try changing the dates, updating the post, navigating away and returning just to be sure this is working. Just be careful of the data formats though, our code does not check them. The year needs to be a two digit number – four digit numbers for the year will not work!

if this all works, take a break and put your feet up for five minutes. You’ve actually done something rather awesome! You have stored and retrieved data in the WordPress database using your own meta data fields and successfully input the data in the backend using a meta box.

 

Creating a Divi Builder Event Module

We are using posts to represent events and have added some extra meta data to ‘attach’ the event start date, end date and number of weeks of repetition to the post records in the database. Because we are using posts the Blog Module will display our event posts. The problem is that the Blog Module does not know that we have attached extra meta data to these posts so it cannot display this extra information. Nor can it order the event blogs. It orders blogs by publication date. To solve these issues, we are going to make a new Event Module, using the existing Blog Module as a template. The new Event Module will order events by event date and display the extra meta data in both grid and fullwidth layouts. We will be able to add this new module to our page layouts. The Event Module will not automatically recognise event blogs though and we will need to flag them by creating a new blog category called ‘Event’ (for example) and the new Event Module will need to be set by the user to only display Event category blogs.

There are a few articles that describe how to make custom modules but we have not found anything that was really successful until we read an article by Jonathan Bossenger – thank you Jonathan. We have included a link to Jonathan’s article in the ‘Other Resources’ section at the foot of this blog. It is well worth reading.

So, the first thing we did was to create a folder in our child theme folder to store the file for our new custom blog module. We created a folder called custom-modules. We then copied the blog module template Blog.php out of the Divi folder –

wp-content/themes/Divi/includes/builder/module

and saved it in our new folder, at the same time renaming it to be Event.php.

Now, as Jonathan explains in his article, there are a couple of changes that need to be made to the code in this file to turn it into our new event module. First, right at the top of the file we need to insert a couple of lines of code. The first lines of the original file should be something like –

<?php

class ET_Builder_Module_Blog extends ET_Builder_Module_Type_PostBased {
	function init() {

Change this code so that is looks as below. be careful to NOT remove the <?php statement from the file. 

<?php

/* Create a custom module for Events */
/* Based on the BLOG module */
/* Instructions here - https://jonathanbossenger.com/building-your-own-divi-builder-modules */

function ex_divi_child_theme_setup() {
    if ( class_exists('ET_Builder_Module')) {

class ET_Builder_Module_Blog extends ET_Builder_Module_Type_PostBased {
	function init() {

Next, at the end of the file add –

    $et_builder_module_event = new ET_Builder_Module_Event();
    add_shortcode( 'et_pb_event', array($et_builder_module_event, '_shortcode_callback') );

    }
}
add_action('et_builder_ready', 'ex_divi_child_theme_setup');

Because we have ‘wrapped’ the original code with this new ‘top’ and ‘tail’, it would be a good idea to use your editor to indent all the original code , just to neaten things up. We use Notepad++ and this is easy to do with it. But you don’t need to do this if you don’t want to.

Now we need to change the name of the new module to be Event rather than Blog. So, at the start of the file change this –

class ET_Builder_Module_Blog extends ET_Builder_Module_Type_PostBased {
    function init() {
        $this->name       = esc_html__( 'Blog', 'et_builder' );
        $this->slug       = 'et_pb_blog';

to this (replace Blog and blog with Event and event, basically) –

class ET_Builder_Module_Event extends ET_Builder_Module_Type_PostBased {
    function init() {
        $this->name       = esc_html__( 'Event', 'et_builder' );
        $this->slug       = 'et_pb_event';

Lastly, make sure that the new module is loaded into your website by going to the functions.php file in your child theme and adding –

/* include custom event module file */ 
include(get_stylesheet_directory() . '/custom-modules/Event.php');

At this stage, you should see a new Event module in the Divi Builder – 

You should be able to insert it into your website pages and use it just like the Blog module. In fact, at the moment, it’s just an exact copy of the blog module – renamed to be the Event Module. We haven’t actually customised it yet. That’s next.

Getting The Event Module To Display Events

So by now we have a new PHP file in our child theme that is a copy of the Divi Blog module, except it has been renamed to be the Event Module. The first change we are going to make to this new module is to get it to output the date meta data stored in our event posts. To do this we insert some extra code that we will write in a moment. First though, let’s find the right place in the file to insert this code.

Search the Event.php file for a function called render. It should look something like this –

function render( $attrs, $content = null, $render_slug ) {
    global $post;

All of the changes we will be making to the Event.php file from now on will be made to this function. From the start of this render function, search forward for the phrase entry-title. This is the code in the file that outputs the blog title, along with a link to the blog post itself. Here it is with an additional comment to show where to insert our extra code –

<?php if ( ! in_array( $post_format, array( 'link', 'audio' ) ) || post_password_required( $post ) ) { ?>
    <<?php echo $processed_header_level; ?> class="entry-title"><?php the_title(); ?></<?php echo $processed_header_level; ?>>
    <?php } ?>
/* ADD EXTRA CODE HERE TO DISPLAY OUR META DATA */
<?php
    if ( 'on' === $show_author || 'on' === $show_date || 'on' === $show_categories || 'on' === $show_comments ) {

So, at this point in the code, the post title is output. We are going to insert code to output our extra date meta data just after the title is output and just before the default meta data is output. The code that we are going to use is below. Insert it into the Events.php file at the position incicated above.   

<?php
$start_date = get_post_meta( $post->ID, '_event_start_date', true );

if ( $start_date ) {
    $end_date = get_post_meta( $post->ID, '_event_end_date', true );
    $repeat_length = get_post_meta( $post->ID, '_event_reps', true );

    if ( $end_date ) {
        /* A START AND END DATE */
        echo '<h3 class="event-date">' . "FROM " . date("l", $start_date) . " " . date("d/M/Y", $start_date) . '';
        echo '<h3 class="event-date">' . "TO " . date("l", $end_date) . " " . date("d/M/Y", $end_date) . '';

    } elseif ( $repeat_length ) {
        /* A REPEATER */
        echo '<h3 class="event-date">' . "EVERY " . date("l", $start_date) . '';
        echo '<h3 class="event-date">' . "FROM " . date("d/M/Y", $start_date) . '';
        echo '<h3 class="event-date">' . "TO " . date("d/M/Y",
                    strtotime("+" . $repeat_length . " weeks", $start_date)) . '';

    } else {
        /* A START DATE ONLY */
        echo '<h3 class="event-date">' . date("l", $start_date) . " " . date("d/M/Y", $start_date) . '';
    }
} else {
    /* NO START DATE */
}
?>

The main thing this code does is to retrieve the date information from the database by calling get_post_meta. Remember, the code we used to populate the meta box with the meta data from the database used this function as well. Notice the code here is careful to deal with the three different event types. It does this by testing for the presence or absence of the data. We also use an h3 tag with a classname of event-date to display the date and we are going to use that later to style it. Also, note we use a couple of very handy PHP functions to manipulate our dates. Remember, we have stored these as Unix timestamps, so we use the built in PHP function date to convert them into strings before being output. This function is very convenient because it allows us to specify the output format of the dates. We also use strtotime which takes a ‘date expression’ as a string and evaluates it. We use this to convert the repeat period into an end date. We thought this would be easier for visitors to the website to understand than outputting the number of weeks that the event is repeated. In our code, strtotime is adding the number of repeat weeks to the start date to calculate the end date.

At this point, the grid and fullwidth event modules should output ‘event blogs’ and display their additional meta data – the start date, end date etc. Try it now to make sure everything is working. Create two new blogs, A & B, and put them into a new category called Event. Create A first then B, but give A an event date before the event date of B. So, event date order would be A,B but publication date order is B,A. So, here is what this should look like for an Event Module in grid layout. Remember, we have not looked at event ordering yet, that’s next.

As an aside, in this article we are not going to deal at all with the standard meta data, the author, date and category for events, so best not to display it. 

Ordering The Events

This is where our decision to store date information as Unix timestamps simplifies our code a lot. Remember, a Unix timestamp is a date format that stores dates as the number of seconds since January 1st 1970. Yes, these are big numbers! Of course, the really interesting consequence of this is that, to order them chronologically, all you need to do is order them numerically because if one Unix timestamp is less than another, its date will be chronologically earlier.

To order our events, we are going to add some more code to the same render function. We are actually going to ask WordPress to order them. We will do this by changing the query used to get the event blogs from the database. We will be using a query that asks the database to order the results by event date, rather than the default ordering by publication date.

Go to the event module file again and search for the render function. When you find it, search forward for a call to the function query_posts. It should look like this –

    query_posts( $args );

It is the call to this function that asks the database to give us the posts we need, the event posts. Immediately after this function call, the code goes into a big while loop to process (and display) each post.

Immediately before the call to query_posts, add the following –

    $args['orderby'] = 'meta_value';
    $args['meta_key'] = '_event_start_date';
    $args['order'] = 'ASC';

This changes the query used to request the event blogs. We are now asking WordPress to sort the blogs into ascending order (Unix timestamp ascending order) using the _event_start_date field. In other words, events are ordered youngest to oldest.

Go and look at your output again. Your event posts should be ordered by event start date. Try changing some start dates to make sure that this is working –

Dealing with Past Events

One issue that came up was whether or not to display past events, ‘out of date’ events. We could simply assume that the website owner will delete them, so they are no longer displayed. But we decided to try and automatically stop them from being displayed. We decided that the simplest way to do this was to modify the while loop that is processing the blogs to first check to see if each blog is in the past or not and to not display the blog if it was. 

When ordering the events by start date, we inserted some code just before calling query_posts to get the event posts from the database. We now need to insert some extra code, just after this, to test the event date against the current date. So, if you are not still looking at the same code, search for the render function, then search forward for query_posts again, just as we did before. Immediately after query_posts, have_posts is used to see if any results were returned then  there is some code that is the start of the while loop used to process and display each post in turn. The code looks like –

    
while ( have_posts() ) { 
    the_post();

    global $post;

Immediately after this code, after the declaration of the global $post, insert the following –

    
$start_date = get_post_meta( $post->ID, '_event_start_date', true );
		
if ( $start_date ) {
    $end_date = get_post_meta( $post->ID, '_event_end_date', true );							
    $repeat_length = get_post_meta( $post->ID, '_event_reps', true );
						
    if ( $end_date ) {
        /* A START AND END DATE */
        if (time() > $end_date + 24*60*60)
            continue;
    } elseif ( $repeat_length ) {
        /* A REPEATER */
        if (time() > strtotime("+" . $repeat_length . " weeks", $start_date) + 24*60*60)
            continue;								
    } else {	
        /* A START DATE ONLY */
        if (time() > $start_date + 24*60*60)
            continue;
    }
} else {
    /* NO START DATE */
}

If you looked closely at the code we used to display the date meta-data, this should look familiar. Again, we use the presence or absence of data to work out what type of event we have. This, in turn, allows us to determine the logic to be used when deciding if the date is in the past or not. The only place where there is an interesting decision to make about how this should work is how to deal with multi-day events. We decided that it would be their end date that we would use for comparison with the current date. In other words, the end date of multi-day events had to be in the past for us to consider them as being finished and not display them. We use the built in PHP time function to get the current time (in Unix timestamp format, again making comparison simple, reducing it to a numerical comparison). So, in the simplest case, if we have a single day event (a start date only) we compare the current time to the start date plus 24hrs (24*60*60 seconds). We add the 24hrs on to the comparison to make sure the day is over, to stop us from rejecting the event, when it still happening today. In all cases, if we decide the event was in the past, we use continue to jump out of the code and go back to the start of the while loop. In other words, all code after this, inside the while loop, is skipped over and not executed.

Test this by creating an event that is in the past and make sure this is no longer being displayed. Try an event that is today and make sure this is displayed.

That seems to work fine but there is a problem… if we are choosing to not display all posts, we may not be displaying the right number of posts. After all, we can specify in the back end how many posts are to be displayed. Usually, Divi does this by simply asking the query to give it the right number of posts, but we have ‘broken’ this because we may not display all posts that are returned to us from the database, if some are in the past. We decided to deal with this by ‘counting’ the number of posts we were displaying and stopping when we got to the right number. One consequence of this is that we will now have to always ask the database for all event posts. This is because we no longer know how many posts we need in order to get the required number of displayed posts.

So the first thing to do is to ammend our code that modifies the database query. It should now look like this –

    $args['orderby'] = 'meta_value';
    $args['meta_key'] = '_event_start_date';
    $args['order'] = 'ASC';
    $args['posts_per_page'] = -1;

What this does now is to ask the database for all of the posts. By default, we ask for the number of posts needed, but by setting posts_per_page to -1 we force the database query to always return all posts. Deciding to not display past events means we don’t know beforehand how many are needed.

Next we need to change our ‘display logic’ to not display event posts if we have already displayed all that we need. To do this we will keep a count of how many have been displayed and compare this with the number needed. Again, change the code to do this by adding an extra line to initialise our counter, $displayed_posts, to zero –

    $args['orderby'] = 'meta_value';
    $args['meta_key'] = '_event_start_date';
    $args['order'] = 'ASC';
    $args['posts_per_page'] = -1;
    $displayed_posts = 0;

Finally, we add a couple of lines to the code we inserted earlier, at the start of the while loop that processes the blogs. This code should now look as below. We have a couple of extra lines at the end – 

    
$start_date = get_post_meta( $post->ID, '_event_start_date', true );
		
if ( $start_date ) {
    $end_date = get_post_meta( $post->ID, '_event_end_date', true );							
    $repeat_length = get_post_meta( $post->ID, '_event_reps', true );
						
    if ( $end_date ) {
        /* A START AND END DATE */
        if (time() > $end_date + 24*60*60)
            continue;
    } elseif ( $repeat_length ) {
        /* A REPEATER */
        if (time() > strtotime("+" . $repeat_length . " weeks", $start_date) + 24*60*60)
            continue;								
    } else {	
        /* A START DATE ONLY */
        if (time() > $start_date + 24*60*60)
            continue;
    }
} else {
    /* NO START DATE */
}

$displayed_posts = $displayed_posts + 1;
/* only display the number required */
if ($displayed_posts > $posts_number) 
    continue;

So all we have done here is added a couple of lines of code to increment our count of the number of displayed posts and check it against the number of posts required, skipping the remainder of this while loop (that is displaying the blogs) if we have displayed enough already. Divi tells us how many blogs have been requested in a variable called $posts_number. The original code did not need logic like this because the original query asked for the required number of posts and we displayed all of them. By choosing to not display all blogs, we no longer know how many are needed before running the query and we need to keep a count while we are displaying them.

This is getting a bit complicated now but, hopefully, it is still making sense 🙂

Using Pagination and the Blog Offset

Unfortunately, there is one other issue that we need to grapple with. We have set the number of posts required from the database to be all of the event posts. One consequence of this is that we have broken the pagination and offset functionality used by the blog module. It will no longer work in the Event Module. 

It’s quite awkward to get pagination to work. Pagination sets an offset as its start position then asks for the required number of posts for that page. Again, because we have chosen to not display all event posts, not only do we not know how many will be displayed on the page (we don’t know how many are displayable) but we also don’t know what the correct offset is for the same reason. We could conceivably introduce a ‘pre-loop’ to look through the event blogs and work this out but we chose not to do this. Instead we simply accept that pagination will not work. Remember, our client only wanted to display a small number of events – no more than a dozen.

Pagination does not work in the new Event Module.

Nonetheless, allowing an offset might be useful and that is much easier to achieve because it fits better with our modified code.

To get the offset to work, we will simply modify the code to ignore all the ‘displayable’ event posts up to the offset post. To do this, we again need to introduce a counter and set it to zero at the start of the while loop that processes and displays the blogs. So modify our code as follows –

    $args['orderby'] = 'meta_value';
    $args['meta_key'] = '_event_start_date';
    $args['order'] = 'ASC';
    $args['posts_per_page'] = -1;
    $displayed_posts = 0;
    $skipped_posts = 0;

Now add some additional logic to test against the offset counter –

    
$start_date = get_post_meta( $post->ID, '_event_start_date', true );
		
if ( $start_date ) {
    $end_date = get_post_meta( $post->ID, '_event_end_date', true );							
    $repeat_length = get_post_meta( $post->ID, '_event_reps', true );
						
    if ( $end_date ) {
        /* A START AND END DATE */
        if (time() > $end_date + 24*60*60)
            continue;
    } elseif ( $repeat_length ) {
        /* A REPEATER */
        if (time() > strtotime("+" . $repeat_length . " weeks", $start_date) + 24*60*60)
            continue;								
    } else {	
        /* A START DATE ONLY */
        if (time() > $start_date + 24*60*60)
            continue;
    }
} else {
    /* NO START DATE */
}

if ($offset_number > $skipped_posts) {
    $skipped_posts = $skipped_posts + 1;
    continue;
}

$displayed_posts = $displayed_posts + 1;
/* only display the number required */
if ($displayed_posts > $posts_number) 
    continue;

The additional code here compares the offset number to the posts we have skipped so far, incrementing the skipped posts and jumping out of the while loop if the offset has not been achieved yet. So, if there is an offset, we skip the offset number of displayable posts.

Styling The Event Module Output

We are now going to use CSS to add some styling to our output – to both the grid and fullwidth layouts. Before we do this though, we just have one more change to make to our Event.php file. We are going to get it to automatically add classnames to the output so that we can target the event output with our CSS. We don’t need to do this – we could manually add a classname to the Event Module in the backend, but we would need to always remember to do this, so we prefer to get the Event Module to do it for us. 

To do this, edit Event.php then search for the phrase add_classname.  In our version of this file it is used in three places. The first of these is just before the code we inserted to set up the query for the events, just before the call to query_posts. We will not change this. Instead, look at the second and third place where add_classname is used in the file. In both cases, the class for the blog output is being added to the output code. The first is using et_pb_blog_grid_wrapper as the classname and the second is using et_pb_posts. Here is what the first piece of code looks like, before we modify it. It outputs the classname for the blog grid –

 
// Module classname
$this->add_classname( array(
    'et_pb_blog_grid_wrapper',
) );

…and here is the second piece of code, before modification. This outputs the classnames for the fullwidth blog format –

// Module classname
$this->add_classname( array(
    'et_pb_posts',
    "et_pb_bg_layout_{$background_layout}",
    $this->get_text_orientation_classname(),
) );

Modify the first fragment of code to add a new classname for us to use when targeting our CSS styling. It should now look like this –

 
// Module classname
$this->add_classname( array(
    'et_pb_blog_grid_wrapper',
    'et_pb_events_grid',
) );

…and now do the same for the second fragment which should look like this now  – 

// Module classname
$this->add_classname( array(
    'et_pb_posts',
    "et_pb_bg_layout_{$background_layout}",
    $this->get_text_orientation_classname(),
    'et_pb_events',
) );

These simple modifications will add classnames of et_pb_events_grid to the event blog grid layout and et_pb_events to the event blog fullwidth layout. We are ‘flagging’ the HTML with our new classnames to make it easy to target them later with our CSS. if you use Google Chrome Inspect, you should be able to see these additional classnames and check they are being output.

Now we are going to add some CSS to style our events. The ones that are in grid format are actually not too bad but fullwidth events will need a little more work. So let’s start by adding some styling to the grid events. Here is what we will do. Add this to your styling, ideally in your child theme –

/* increase title size and make it RED */
.et_pb_events_grid h2.entry-title a {
	color:red !important;
	font-size:20px;
}

/* make the event meta data stand out */
.et_pb_events_grid h3.event-date {
	font-weight: bold;
	font-size:16px;
}

/* add some margin beneath the content to move the READ MORE down a bit*/
.et_pb_events_grid .post-content p {
	margin-bottom:2em;
}

/* remove the border */
.et_pb_events_grid .et_pb_post {
	border-width:0px;
}

We have added comments to the CSS to make it clear what is going on here. Hopefully, this all makes sense. Notice two things though… first, we are using the classname that the Event Module generates for the grids (et_pb_events_grid) to target them. Secondly, we are also using the classname we generated for the event date info to target that as well (h3.event-date).

Here is what the event grid should look like after this styling is applied. To make this more realistic, we have added featured images, some content and switched on the Read More button. We also made sure that we included an example of each date format, so three events are now displayed  –

Now, we are going to style the fullwidth events. We need a little more CSS for this and it is not as simple as for grids, because we want to manipulate the featured image and move the title and excerpt to the right. So we are going to add this CSS a bit at a time, starting with the easy bits 🙂

/* increase title size and make it RED */
.et_pb_events h2.entry-title a {
	color:red !important;
	font-size:20px;
}

/* make the event meta data stand out */
.et_pb_events h3.event-date {
	font-weight: bold;
	font-size:16px;
}

Ok, we said we were starting with the easy bits 🙂 This CSS adds the same styling to the titles and date meta data for the fullwidth format as we used for the grids. We could have chosen to re-use the CSS code from earlier to do this but we decided to keep this styling separate so that, if required, different styling could be used for both.

Now, the next CSS that we will add is going to have a more important effect – we are going to shrink the featured images  –

/* shrink the featured image */
.et_pb_events .et_pb_post img {
	width:200px;
}

Often, when we specify the width of an object, we use a percentage. The width of the object would then be ‘fixed’ relative to (proportional to) its container, even as the viewport changes in width. We are trying to turn the featured image into a thumbnail. Usually, thumbnails are a fixed, absolute width and, to achieve this, are specified in pixels. They do not vary in width as the viewport varies. This is so that they do not get too small (or too big). We will sort out how the featured image behaves on tablet and phone later. On desktop, this is what the fullwidth events will look like now –

Next, we are going to push the content over to the right, moving it to the side of the featured image thumbnail. We might consider using flex to do this but let’s using the traditional float for now. Here is the CSS –

/* float the image on the left to make everything else flow past it to the right */
.et_pb_events .et_pb_post>a {
    float:left;
}

…and here is what this now looks like –

…which is absolutely fine but we really want all of the items on the right to be left aligned and to have a small gap between them and the thumbnail. We have deliberately used Google Chrome Inspect above to narrow the viewport and make the alignment issue clearer. We can align the content by adding a left margin that is a little bigger than the width of the thumbnail –

/* push everything to the right to line up with the thumbnail image */
/* BE CAREFUL WITH THIS 230px NUMBER BECAUSE IT DEPENDS ON THE WIDTH OF THE THUMBNAIL */
.et_pb_events .et_pb_post>*:not(:first-child) {
	margin-left:230px !important;	
} 

No great surprise about what this does –

There are two comments that need to be made about how this is done though. First of all, our CSS is a little ‘unsafe’ because we have hard-wired the width of the image into this code. The code depends on the width of the thumbnail. If we changed the width of the thumbnail, we would need to change this code as well. A solution might be to introduce a variable…

The second issue that is interesting is that the CSS uses .et_pb_post>*:not(:first-child). All modern browsers support this and it is the sort of construction that is incredibly handy. We are using it to target all immediate children of the et_pb_post container apart from the first child (the thumbnail). The ‘>’ targets immediate children only, the ‘*’ targets all of them and the not(:first-child) avoids targeting the thumbnail, the first child object. We could target all children manually, just by adding their classnames, but the code as written is slightly more robust because it will still work if Divi changes the classnames or adds a new element to the blog container. Neither of these is likely to happen but….

Narrowing The Read More Button

You may or may not notice, but the Read More link on the fullwidth format blogs is eh….. fullwidth. You can see this by just moving the mouse over them, then moving it to the right. You should see that the ‘mouse click’ pointer is still active even when the mouse is not directly over the Read More. You can force these to be ‘narrow’ if you add this CSS –

/* the read more buttons on the Blog LIST archive are display:block */
/* so they take up the whole width of the column..... */
.et_pb_events a.more-link {
	display:inline-block;
	margin-top:20px;
}

As the comment in the code points out, changing the Read More link from display:block (which creates an object that will take up as much space as it can) to display:inline-block (which creates an object that takes up as little space as it needs to – in this case, it shrinks to fit the button text) will solve this. Because of this change, you also need a little more whitespace above the button and setting the margin-top does this. 

Styling for Tablet and Mobile

Because we did very little to the format of the event grid we will simply rely on Divi to sort out the responsive behavior of the grids. However, we pushed the fullwidth event layout around quite a lot so we are going to have to look closely at that.

It turns out that, on tablet, it looks fine. The choice of 200px wide thumbnails is not taking up so much of the horizontal space that the text looks unnaturally ‘narrowed’. So, no changes needed there. 

On mobile though, we need to make some adjustments, which basically means, undoing the CSS we used to float the content to the right of the thumbnail plus adding a little more whitespace beneath the title –

@media (max-width:480px) {
    .et_pb_events .et_pb_post>*:not(:first-child) {
        margin-left:0px !important;	
    } 
    .et_pb_events .et_pb_post img {
        width:100%;
    }	
    .et_pb_events .et_pb_post>a {
        clear:both;
    }
    .et_pb_events h2.entry-title {
        padding-bottom:1em !important;
    }
}

All The CSS In One Place

/**********************************************************/
/*                                                        */
/*        Style the grid layouts for the Events           */
/*                                                        */
/**********************************************************/

/* increase title size and make it RED */
.et_pb_events_grid h2.entry-title a {
	color:red !important;
	font-size:20px;
}

/* make the event meta data stand out */
.et_pb_events_grid h3.event-date {
	font-weight: bold;
	font-size:16px;
}

/* add some margin beneath the content to move the READ MORE down a bit*/
.et_pb_events_grid .post-content p {
	margin-bottom:2em;
}

/* remove the border */
.et_pb_events_grid .et_pb_post {
	border-width:0px;
}




/**********************************************************/
/*                                                        */
/*        Style the fullwidth layouts for the Events      */
/*                                                        */
/**********************************************************/

/* increase title size and make it RED */
.et_pb_events h2.entry-title a {
	color:red !important;
	font-size:20px;
}

/* make the event meta data stand out */
.et_pb_events h3.event-date {
	font-weight: bold;
	font-size:16px;
}

/* shrink the featured image */
.et_pb_events .et_pb_post img {
	width:200px;
}

/* float the image on the left to make everything else flow past it to the right */
.et_pb_events .et_pb_post>a {
    float:left;
}

/* push everything to the right to line up with the thumbnail image */
/* BE CAREFUL WITH THIS 230px NUMBER BECAUSE IT DEPENDS ON THE WIDTH OF THE THUMBNAIL */
.et_pb_events .et_pb_post>*:not(:first-child) {
	margin-left:230px !important;	
} 

/* the read more buttons on the Blog LIST archive are display:block */
/* so they take up the whole width of the column..... */
.et_pb_events a.more-link {
	display:inline-block;
	margin-top:20px;
}

@media (max-width:480px) {
    .et_pb_events .et_pb_post>*:not(:first-child) {
        margin-left:0px !important;	
    } 
    .et_pb_events .et_pb_post img {
        width:100%;
    }	
    .et_pb_events .et_pb_post>a {
        clear:both;
    }
    .et_pb_events h2.entry-title {
        padding-bottom:1em !important;
    }
}

Limitations

Pagination does not work.

The meta box is displayed on all posts, not just on Event posts.

There is no error checking in the meta box for date formats.

Other Things To Try

Try some different styling on the grid and fullwidth layouts.

Can you work out how to only display the meta box on Event Category posts? At the moment it is displayed on all posts.

If you wanted to add start and end times to the events, what changes would need to be made to achieve this?

Can you work out how to get the date formats to work with four digit years as well as two digit years?

Can you add some error checking to the meta box to check the dates are in the correct format?

At the moment, the Event Module needs to be told to use only Event category posts. Could it automatically use only Event posts? Would this be a good idea? Would it then disallow the selection of any categories and simply assume a category called ‘Event’ was being used?

Can you get pagination to work for the Event Module?

Can you change the CSS so that the width of the thumbnail is only specified in one place? At the moment the width is specified in two places in the CSS.

 

Be Aware When Using Child Themes

Using a child theme is a great option because it allows you to modify the code of the theme in a way that preserves changes when Divi is updated. However, it is not just your changes that are preserved but all of the code in the copied file as well. If you make an update, this code could be out of date (it is a previous version) and the incompatibilities could introduce some quite bizarre behaviour. The safest thing to do is to always update the child files from the new version of your theme and make your changes again to them. Yes, that’s quite time consuming…  If you choose not to keep your child theme files up to date and you get bad things happening after an update…. go straight to your child theme files.

 

Subscribe To Our Blog

If you found our blog article useful, why not subscribe? Get all of our articles and tutorials delivered direct to your inbox!

You have Successfully Subscribed!