WordPress Timeline JS Plugin


I like Timeline JS. It’s a nice way to create multimedia timelines. I’d previously done some work that would take WordPress JSON API data and insert it into the Timeline JS view.1 It was nice for creating alternate and standardized views of blogs that might be useful for different reasons. It didn’t serve some other needs and while doing it through a generic URL was handy for many reasons it was odd in other scenarios. As a result I decided to make a new version as a plugin. If you don’t like reading stuff there’s a quick video of how it works below.

Plugin Goals

First, I wanted this to be a plugin rather than a theme. That adds a bit of complexity because you don’t have control of the whole scenario but it makes it much more portable and more likely to be used as it doesn’t require people to change themes or spin up an additional site.

I wanted people to be able to use WordPress rather than a spreadsheet to create the content for Timeline JS. Doing that has a few advantages- the WYSIWYG editor, the ability to upload images directly in WordPress, the ability to use posts you’ve already written, etc. etc.

I also wanted people to be able to choose what posts ended up being used in the timeline by choosing a particular category. That would enable them to keep using the blog as normal but also have the ability to pull particular posts into a timeline or create multiple timelines that display only posts from particular categories.

Featured images would be used to create the main image display in the timeline. I wanted to support both start and end times for events and I wanted a fairly intuitive way to set the starting slide/element on the timeline.

How that Played Out

I made a custom post type called Timelines. I figured I’d use that as the authoring element for the timelines. It solved a few problems for me without requiring a shortcode with tons of variables.2

In this case the title and body of the Timeline custom post type becomes the content of the title element of the timeline. I tied the categories to the categories used in normal posts and that enables you to choose however many categories you’d like for inclusion. I went with ‘category__in’ for this but will likely add another metabox to enable you to choose additional category inclusion/exclusion options. I’ll probably also add tags if I see any interest from people.

Technical Stuff

I’m probably almost certainly not doing this the way WordPress would like. I believe they want me to use wp_localize_script to integrate the variables rather than writing the script into the post body. When I started that seemed more difficult and so I went the way I knew.

Step 1 – Load the Timeline JS & CSS

You only want this happening on the timeline posts so adding the if (get_post_type($post->ID) === ‘timeline’) makes that happen. This is one of those things I ignored initially and then came back and fixed when I started writing this post. Writing blogs posts is nice because it documents things and makes me notice all sorts of things I missed in the heat of trying to get a working plugin but it also sucks because it takes me forever to write the post. These asides are also the reason I have 223 draft posts on my site.

		    function load_timelinejs_script() {		    	
		        global $post;
		        if (get_post_type($post->ID) === 'timeline'){
			        $deps = array('jquery');
			        $version= '1.0'; 
			        $in_footer = false;
			        wp_enqueue_script('knightlab_timeline', plugins_url( '/js/timeline-min.js', __FILE__), $deps, $version, $in_footer);			
		add_action('wp_enqueue_scripts', 'load_timelinejs_script');

		function add_timeline_stylesheet() {
			global $post;
			if (get_post_type($post->ID) === 'timeline'){
		    	wp_enqueue_style( 'timeline-css', plugins_url( '/css/timeline.css', __FILE__ ) );

		add_action('wp_enqueue_scripts', 'add_timeline_stylesheet');

Step 2 – Make the Data

A huge chunk of programming is just figuring out ways to generate patterns. Timeline JS has done all the work of building a framework that accepts a certain pattern of data I just need to make WordPress generate it.

A Custom Post Type with a Custom Template

Since I’d opted to go the custom post type route, I needed to create it and make a particular template for it. I hadn’t done that through a plugin before but the codex came through for me. You might also note that I stick references to where I got things in the code as comments. It helps me when I write these posts, gives additional credit, and builds a useful associative trail that helps me if something breaks and might help others who want to see other related elements.

//FROM https://codex.wordpress.org/Plugin_API/Filter_Reference/single_template
/* Filter the single_template with our custom function*/
function get_custom_post_type_template($single_template) {
     global $post;

     if ($post->post_type == 'timeline') {
          $single_template = dirname( __FILE__ ) . '/timeline.php';
     return $single_template;
add_filter( 'single_template', 'get_custom_post_type_template' );

The template is bare bones. It’s built off the basic structure of the 2017 theme. You can see the way the script integrates local variables from the timeline post into the JSON structure . . . and the more I look at it, the more I realize localizing was the way to go. Live/learn. It’ll do for now.

<?php get_header(); ?>	

    	<div class="timeline">
			<div class="content-area">
				<main id="main" class="site-main" role="main">
       				 <div id="timeline-embed"></div>
				        <?php if(have_posts()): while(have_posts()): the_post(); ?>
				          	 $post_id = get_the_ID();
				          	 $content = json_encode(get_the_content($post_id));          	
						<?php endwhile; endif;?>

				         <script type="text/javascript">
						 var the_json =  {
							"title": {
							"media": {
							"url": "<?php echo $featured_img_url = get_the_post_thumbnail_url( $post_id, 'full'); ?>",
							"caption": "",
							"credit": ""
							"text": {
							"headline": "<?php echo get_the_title($post_id);?>",
							"text": <?php echo $content;?>
							<?php echo makeTheEvents ($post_id);?>

						    window.timeline = new TL.Timeline('timeline-embed', the_json);     

				</main><!-- #main -->
			</div><!-- .content-area -->
		</div><!-- .timeline -->
<?php get_footer();

Event Data

The following function creates the event JSON data using the query loop. I limited it to 40 events.

I’m only sort of using the Event class properly. I got warnings when trying to add elements to the main structures (that’s why you see the @ prepended – @$event->media->url). The object oriented side of things is something I know basically nothing about. It makes it harder to Google things without the right vocabulary so some actual structured attempts to learn this is on the near horizon.

class Event {
    public $media = "";
    public $start_date = "";
    public $text = "";

function makeTheEvents ($post_id){
	        $cats = wp_get_post_categories($post_id); 

	        //if custom field type is set to a custom post type then get that instead
	        if (get_post_meta($post_id, 'type', true )){
	        	$post = get_post_meta( $post_id, 'type', true );
	        } else {
	        	$post = 'post';

			$args = array(
				'posts_per_page' => 40, 
				'orderby' => 'date',
				'category__in' =>  $cats,
				'post_type' => $post,
			$the_query = new WP_Query( $args );
			// The Loop
			$the_events = array();

			if ( $the_query->have_posts() ) :				
			while ( $the_query->have_posts() ) : $the_query->the_post();
				$the_id = get_the_ID();
				//get the featured image to use as media
				if (get_the_post_thumbnail_url( $the_id, 'full')){
					$featured_img_url = get_the_post_thumbnail_url( $the_id, 'full');
					$thumbnail_id = get_post_thumbnail_id( $the_id);
					$alt = get_post_meta($thumbnail_id, '_wp_attachment_image_alt', true); 
					$caption = get_post($thumbnail_id)->post_excerpt;
				} else $featured_img_url = "";

				$event = new Event();
				@$event->media->url = $featured_img_url;
				@$event->media->caption = $alt;
				@$event->media->credit = $caption;
				@$event->start_date->month = get_the_date(n);
				@$event->start_date->day =  get_the_date(j);
				@$event->start_date->year =  get_the_date(Y);
				@$event->text->headline = get_the_title();
				@$event->text->text = get_the_content();
				//END DATE
				if (get_post_meta($the_id, 'end_date', true) && get_post_meta($the_id, 'end_date', true)["text"]){
					$end_date = get_post_meta($the_id, 'end_date', true)["text"];
					@$event->end_date->month = intval(substr($end_date, 5, 2));
					@$event->end_date->day =  intval(substr($end_date, -2));
					@$event->end_date->year =  intval(substr($end_date,0, 4));
			    array_push($the_events, $event);
			// Reset Post Data
			$the_events = json_encode($the_events);
			return $the_events;

1 A blog post on how that works is via Google Sheets is here. I also built another version using URL parameters and PHP but may not have written the blog post. In any case, a working example of that is here.

2 And the looming specter of Gutenberg obsolescence.

Comments on this post

No comments.

Leave a Reply

Trackbacks and Pingbacks on this post

No trackbacks.

TrackBack URL