#
Enhance Your Calendar with Full Calendar Plugin
Transform your basic calendar into a powerful, interactive scheduling tool with FullCalendar. Whether you are managing events, appointments, or project timelines, FullCalendar provides the advanced functionality your users expect in a modern calendar interface.
#
Before You Start
Make sure you have:
- A Solodev CMS installation
- A Website set up in your workspace
Note:
For step-by-step instructions on adding a Calendar Module in your CMS, please visit Calendar.
#
Step-by-Step Setup Guide
These steps will guide you through the process of installing the Full Calendar Plugin to your Calendar.
- Navigate to Modules
- In your CMS dashboard, click Modules in the left navigation menu
- Click the Add Module button
- Select from the three options:
- Datatable: Creates a simple data table structure
- Module: Builds a custom module from scratch
- Package: Installs a pre-built module from a zip file
- Click Submit.

#
Add Full Calendar Shortcode
Create a shortcode file if you don't already have one. Create a new file called shortcode.php and place it under
www > _ > php
(optional, but recommended).Add the FullCalendar code to your newly created shortcode file using the code provided below.
<?php
namespace Website;
use Solodev\Services\Auth\JwtOneTimeToken;
use Solodev\Services\Shortcode\ShortcodeService;
use Solodev\Services\Shortcode\ShortcodeConfig;
class CustomShortcode extends ShortcodeService
{
private JwtOneTimeToken $jwtOneTimeToken;
public function __construct(
ShortcodeConfig $config,
JwtOneTimeToken $jwtOneTimeToken,
) {
$this->jwtOneTimeToken = $jwtOneTimeToken;
parent::__construct($config);
}
//[js_full_calendar_includes_v2]
/*
* no atts - returns libraries for full calendar: http://fullcalendar.io/
*/
function js_full_calendar_includes_v2($atts, $content = null)
{
notify_solodev_shortcode();
$js_full_calendar_includes_v2 = '<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.12.0/moment.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/fullcalendar/2.8.0/fullcalendar.min.js"></script>
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/fullcalendar/2.8.0/fullcalendar.min.css">
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/fullcalendar/2.8.0/fullcalendar.print.css" media="print">';
return do_shortcode($js_full_calendar_includes_v2);
}
/*
* [full_calendar_v2]
*/
function full_calendar_v2($atts, $content = null)
{
$this->notify_solodev_shortcode();
if (!isset($atts['calendar_id']))
return $this->do_shortcode("Error: calendar_id is required. Where is the data coming from?");
else
$calendar_id = $atts['calendar_id'];
$calendar_display_area_id = isset($atts['calendar_display_area_id']) ? $atts['calendar_display_area_id'] : "calendar_display";
if (isset($atts['website_filter'])) $_POST['websiteFilter'] = $atts['website_filter'];
//category_filter
$category_filter_input_id = "";
if (isset($GLOBALS["category_filter_input_id"]))
$category_filter_input_id = $GLOBALS["category_filter_input_id"];
if (isset($atts['category_filter_input_id']))
$category_filter_input_id = $atts['category_filter_input_id'];
//date_filter
$date_filter_input_id = "";
if (isset($GLOBALS["date_filter_input_id"]))
$date_filter_input_id = $GLOBALS["date_filter_input_id"];
if (isset($atts['date_filter_input_id']))
$date_filter_input_id = $atts['date_filter_input_id'];
//month_filter
$month_filter_input_id = "";
if (isset($GLOBALS["month_filter_input_id"]))
$month_filter_input_id = $GLOBALS["month_filter_input_id"];
if (isset($atts['month_filter_input_id']))
$month_filter_input_id = $atts['month_filter_input_id'];
$pull_mongo = isset($atts['mongo']) ? $atts['mongo'] : 0;
if ($pull_mongo == 1) {
$js_includes = "<script>
$(document).ready(function () {
$('#" . $calendar_display_area_id . "').fullCalendar({
loading: function (bool) {
if (bool) {
$('#calendar-loading').show();
} else {
$('#calendar-loading').hide();
}
},
customButtons: {
list: {
text: 'View as List',
click: function() {
window.location = 'list.stml';
}
}
},
header: {
left: 'title',
center: '',
right: 'list today prev,next '
},
events: function (start, end, timezone, callback) {
$.ajax({
url: (function(){
var qryString = '{\"\$and\":[{\"calendar_id\":" . $calendar_id . "},{\"entry_status\":\"publish\"},{\"\$or\":[{\"\$and\":[{\"start_time\":{\"\$gte\":'+start.unix()+'}},{\"start_time\":{\"\$lte\":'+end.unix()+'}}]},{\"\$and\":[{\"end_time\":{\"\$gte\":'+start.unix()+'}},{\"end_time\":{\"\$lte\":'+end.unix()+'}}]}]}';";
if ($category_filter_input_id != "") {
$js_includes .= "
if($('#" . $category_filter_input_id . "').val().trim() != ''){
qryString += ',{\"datatable_category_id\":'+$('#" . $category_filter_input_id . "').val()+'}';
}";
}
if (!empty($atts['where'])) {
$js_includes .= "
qryString += ',{\"" . $atts['where'] . "\":1}';";
}
$js_includes .= "
qryString += ']}';
var jsonURL = '/api/api.php/search/solodev_view?rows=1000&qry='+qryString+'&nonce=" . $this->jwtOneTimeToken->create() . "&orderStr=start_time&orderDir=desc';
return jsonURL;
})(),
dataType: 'json',
data: {
start: start.unix(),
end: end.unix()
},
success: function (data) {
var events = [];
$(data.solodev_view).each(function () {
var eventData = $(this)[0];
var startTime = parseInt(eventData.start_time, 10) * 1000;
var endTime = eventData.end_time ?
parseInt(eventData.end_time, 10) * 1000 :
startTime + (60 * 60 * 1000);
events.push({
end: moment(endTime),
id: eventData.calendar_entry_id,
start: moment(startTime),
title: eventData.name,
url: eventData.url,
className: eventData.datatable_category_name
});
});
callback(events);
},
error: function(xhr, status, error) {
console.error('MongoDB Calendar Error:', status, error);
callback([]);
}
});
}
});
});
</script>";
} else {
$js_includes = "<script>
$(document).ready(function () {
$('#" . $calendar_display_area_id . "').fullCalendar({
customButtons: {
list: {
text: 'View as List',
click: function() {
window.location = 'list.stml';
}
}
},
header: {
left: 'title',
center: '',
right: 'list today prev,next '
},
events: function (start, end, timezone, callback) {
// Build the query string for non-MongoDB
var qryString = '{\"calendar_id\":" . $calendar_id . ",\"entry_status\":\"publish\"}';
var jsonURL = '/api/api.php/search/solodev_view?rows=1000&qry='+qryString+'&nonce=" . $this->jwtOneTimeToken->create() . "&orderStr=start_time&orderDir=desc';
$.ajax({
url: jsonURL,
dataType: 'json',
data: {";
if ($category_filter_input_id != "") {
$js_includes .= "
category_filter: function () {
var categoryFilter = $('#" . $category_filter_input_id . "').val();
return categoryFilter.trim();
},";
}
if (isset($atts['where'])) {
$js_includes .= "
whereStr: '" . str_replace("'", '"', $atts['where']) . "',";
}
$js_includes .= "
start: start.unix(),
end: end.unix()
},
success: function (data) {
var events = [];
// Handle both possible response structures
var eventList = data.solodev_view || data.rows || [];
$(eventList).each(function () {
var eventData = $(this)[0];
// Handle missing end_time by defaulting to start_time + 1 hour
var startTime = parseInt(eventData.start_time, 10) * 1000;
var endTime = eventData.end_time ?
parseInt(eventData.end_time, 10) * 1000 :
startTime + (60 * 60 * 1000);
events.push({
end: moment(endTime),
id: eventData.calendar_entry_id,
start: moment(startTime),
title: eventData.event_title || eventData.name,
url: eventData.path || eventData.url
});
});
callback(events);
},
error: function(xhr, status, error) {
console.error('Non-MongoDB Calendar Error:', status, error);
callback([]);
}
});
}
});
});
</script>";
}
if ($category_filter_input_id != "") {
$js_includes .= "
<script>
$(document).ready(function() {
$('#" . $category_filter_input_id . "').change(function () {
$('#" . $calendar_display_area_id . "').fullCalendar('refetchEvents');
$('#" . $calendar_display_area_id . "').fullCalendar('rerenderEvents');
window.location = '?type_filter=' + $('#type_filter').val()+'&category_filter=' + $('#" . $category_filter_input_id . "').val();
});
var URLCatFilt = Solodev_GetURLParameter('category_filter');
if (typeof URLCatFilt == 'undefined') URLCatFilt = '';
$('#" . $category_filter_input_id . "').val(URLCatFilt);
});
</script>";
}
$js_includes .= "
<script>
$(document).ready(function() {
$('#type_filter').change(function () {
$('#type_filter').fullCalendar('refetchEvents');
$('#type_filter').fullCalendar('rerenderEvents');
window.location = '?type_filter=' + $('#type_filter').val()+'&category_filter=' + $('#" . $category_filter_input_id . "').val();
});
var URLtypeFilt = Solodev_GetURLParameter('type_filter');
if (typeof URLtypeFilt == 'undefined') URLtypeFilt = '';
$('#type_filter').val(URLtypeFilt);";
if ($date_filter_input_id != "" && $month_filter_input_id != "") {
$js_includes .= "
var nYear = new Date().getFullYear();
var nMonth = new Date().getMonth();
var nNextYear = nYear + 1;
$('#" . $month_filter_input_id . "').val(nMonth+1);
$('#" . $date_filter_input_id . "').val(nYear);
$('#" . $date_filter_input_id . ", #" . $month_filter_input_id . "').on('change', function () {
nYear = ($('#" . $date_filter_input_id . "').val() !== 0) ? $('#" . $date_filter_input_id . "').val() : nYear;
nMonth = ($('#" . $month_filter_input_id . "').val() !== 0) ? $('#" . $month_filter_input_id . "').val() - 1 : nMonth;
var newDate = new Date(nYear, nMonth, 1);
$('#" . $calendar_display_area_id . "').fullCalendar('gotoDate', newDate);
});
$('#" . $calendar_display_area_id . "').fullCalendar('refetchEvents');
$('#" . $calendar_display_area_id . "').fullCalendar('rerenderEvents');";
}
$js_includes .= "
});
function Solodev_GetURLParameter(sParam) {
var sPageURL = window.location.search.substring(1);
var sURLVariables = sPageURL.split('&');
for (var i = 0; i < sURLVariables.length; i++) {
var sParameterName = sURLVariables[i].split('=');
if (sParameterName[0] == sParam) {
return sParameterName[1];
}
}
}
</script>";
unset($_POST['websiteFilter']);
return $this->do_shortcode($js_includes);
}
}
?>
Navigate to your main website screen and click Update Website on the right-hand side.
Access the shortcode settings under the Advanced section, click Browse next to Custom Shortcodes File.
Choose the
shortcode.php
file you created. This tells your CMS to start using the shortcodes defined in this file.Click Submit.
#
Create Your Full Calendar View
To display your enhanced calendar, you'll need to create a dedicated page that incorporates the FullCalendar functionality.
Create a new file on your preferred location
Add the HTML code for your calendar list layout using the example code provided
[js_full_calendar_includes_v2]
<div class="container">
<div class="row text-center justify-content-center">
<div class="col-lg-8 col-xxl-7">
<h1>Calendar</h1>
<p class="fs-5">The Pro Theme Calendar module lets you customize your events with a clean, visual organization and update your published schedule with total ease.</p>
</div>
</div>
<div id="calendar_display"></div>
</div>
[js_full_calendar_v2 calendar_id="" calendar_display_area_id="calendar_display" where="start_time asc"]
Important!
You need to add the ID number of your calendar module to the repeater shortcode within the calendar_id=""
attribute.
Create a page in your calendar folder called and name it exactly
list.stml
.Insert your calendar repeater file into the page you just created.
Note:
To learn more about shortcodes and how they work, visit our shortcodes documentation.
#
Create a List View Alternative
Provide users with a traditional list view option alongside your interactive calendar by creating a complementary page.
Create a new file on your preferred location
Add the HTML code for your calendar list layout using the example code provided
<div class="container">
<h1 class="text-center">Calendar</h1>
<div class="mt-5">
<div class="row border-lg-bottom py-4">
[repeater id="" order="start_time desc"]
<div class="col-lg-3">
[is_set value="{{calendar_image}}"]
<img alt="{{event_title}}" src="[get_asset_file_url id='{{calendar_image}}']" class="img-fluid img-thumbnail">
[/is_set]
[is_empty value="{{calendar_image}}"]
<img alt="{{event_title}}" class="img-fluid img-thumbnail" src="/_/images/default-2.jpg" />
[/is_empty]
</div>
<div class="col-lg-9 ps-lg-3 mt-4 mt-lg-0">
<h2><a aria-label="Read more" href="{{path}}">{{event_title}}</a></h2>
<p class="text-muted fs-6">[print_date format="M. d, Y g:i a" timestamp="{{start_time}}"]</p>
<p>{{calendar_intro}}</p>
<p><a aria-label="Read more" href="{{path}}"><strong>Read More</strong></a></p>
</div>
[/repeater]
</div>
</div>
</div>
Important!
You need to add the ID number of your calendar module to the repeater shortcode within the id=""
attribute.
Create a page in your calendar folder called and name it exactly
list.stml
.Insert your calendar repeater file into the page you want to display the entries on.
You now have both an interactive FullCalendar view and a traditional list view, giving your users flexible options for browsing events.
#
Add Filters to Your Calendar View - Optional
Enhance your calendar's usability by adding filter options that allow users to quickly find specific events. You can implement month, year, and category filters to help users navigate your calendar content more efficiently.
Available Filter Options
Your calendar supports three types of filters:
- Month Dropdown - Filter events by specific months
- Year Dropdown - Navigate between different years
- Category Dropdown - Filter events by predefined categories
Important!
To use the Category Dropdown filter, you must first add a category group to your calendar module.
Implementation Steps
- Add the following HTML code to the top of your file:
<div class="container mb-5">
<div class="row">
<div class="col-md-3 form-group">
[month_filter class="form-control"]
</div>
<div class="col-md-3 form-group">
[date_filter years="2023-2025" all_value="" default_filters=0 label="Year" year_format="simple" show_all_option="0" class="form-control"]
</div>
<div class="col-md-3 form-group">
[category_filter category_group_id="" calendar_id=""]
</div>
</div>
</div>
Important!
Configure the [category_filter]
shortcode by adding your Category Group ID to the category_group_id
attribute and your calendar module ID to the calendar_id=""
attribute.
- For Category Filter users: If you're implementing the category filter, add the attribute
category_filter_input_id="category_filter"
to yourfull_calendar_v2
shortcode.
Create a file in your preferred location.
Add the HTML code for your calendar list layout using the example code provided
<div class="container">
[entry]
<article class="row justify-content-between mt-3">
<div class="col-md-7">
<h2 class="border-bottom border-primary border-2 mt-2">Upcoming events</h2>
<div class="mt-4 pe-7">
<p>{{calendar_info}}</p>
</div>
<h3>Dates</h3>
<p>[print_date format="M. d, Y" timestamp="{{start_time}}"] <br>[print_date format="g:i a" timestamp="{{start_time}}"]</p>
<h3>Location</h3>
<p>{{calendar_address}} {{calendar_address_suite}} <br> {{calendar_address_city}}, {{calendar_address_state}} {{calendar_address_zip_code}}</p>
</div>
<div class="col-md-4">
<img src="/_/images/logo.jpg" alt="Logo" class="img-fluid">
</div>
</article>
[/entry]
</div>
Create a page in your calendar folder to display individual calendar entry details.
Insert your calendar detail file into the page you want to display the entries on.
Map your Module to your page:
- In your Module, click Modify
- Under Websites Properties
- On Detail Page Template, select the .stml file for your detail page
- Under Detail Folder Location, select the folder where your detail file is located
- Click Submit
Important!
If you already have entries added to your module, you will need to resave them to generate the path. To do this, go to Modify, scroll to Advanced, check the Resave All Entries option., and then click Submit
Click Add Entry to begin adding content to your module
Go to your website and refresh the page to see your calendar module in action

#
Support
Support for Calendar Module is handled directly through Solodev's global help desk. For more information regarding support queries, go to www.solodev.com.