LiveWhale Calendar comes with a set of extensively customizable theming tools. We see some teams take on ambitious design projects, and others who change very little from the basic setup and get something launched quickly. It’s totally up to you.
You also aren’t bound by your first approach: some of the most successful calendars we’ve seen launch a “Phase 1” quickly, with minimal theme customizations, and then come back after using the calendar awhile to approach a thoughtful redesign.
Questions we’ll answer in this guide include:
- What are the different kinds of things I can customize about LiveWhale Calendar?
- Where are the theming files in the file structure?
- How can I get my school’s header and footer on my calendar?
- How would I make more detailed HTML edits to individual calendar elements?
- How can I edit my calendar’s CSS stylesheets to change colors, spacing, and more?
- How can I add custom JavaScript interactivity to my calendar?
Elements of calendar theming
Let’s start with an overview, establishing some key terms. Then, we can step through specific examples of common theming tasks to get you customizing your calendar’s theme.
Themes and Theme Inheritance
Each LiveWhale version comes packaged with a basic “core” theme, and your calendar already began with some starter theming in the /_ingredients/themes/global/ folder. That folder is where you’ll spend 90%+ of the time when working on calendar theming, as it contains some files that override the core theming components, plus any additional files specific to your theme. More on theming, theme inheritance, and organization of the ingredients folder can be found in the docs.
Templates
The major skeletal elements of your calendar page – the header, footer, sidebar – are managed in what we call templates. These are the large, static elements of the page. They usually live in /_ingredients/themes/global/includes/.
Components
All of the smaller, “atomic” elements of LiveWhale Calendar are theme-able using components. These are things like the event view, day view, date selector, and more. A complete set of default components is packaged with the core theme, and you’ll copy individual files into /_ingredients/themes/global/components/calendar/ to customize them.
Styling
LiveWhale Calendar is bundled with all of the CSS you need for a great looking basic calendar, but you can extend or override this CSS as you like. LiveWhale supports LESS and SASS processing out of the box, so you can work with .less or .scss files in your theme and we’ll do the work of rendering and caching those for you. Any CSS/LESS/SASS files you add to /_ingredients/themes/global/styles/ will be included on the calendar.
Scripting
You don’t need to write any JavaScript for LiveWhale Calendar to function—however, there are opportunities to extend functionality or add custom behavior using JS, and we’ll talk about some examples below. Any JS files you add to /_ingredients/themes/global/scripts/ will be included on the calendar.
Adding your site header and footer
Pulling in your main site’s header and footer can usually get you most of the way towards a finished-looking calendar.
Your main calendar template usually can be found at /_ingredients/templates/main_template.html and contains file includes pulling in a number of other theme files:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
<head>
<widget type="file"><arg id="path">/_ingredients/themes/global/includes/head.html</arg></widget>
<widget type="file"><arg id="path">/_ingredients/themes/global/includes/calendar_settings.html</arg></widget>
<xphp var="theme"><xphp var="group_theme"/></xphp>
</head>
<body>
<widget type="file"><arg id="path">/_ingredients/themes/global/includes/header.html</arg></widget>
<widget type="file"><arg id="path">/_ingredients/themes/global/includes/main.html</arg></widget>
<widget type="file"><arg id="path">/_ingredients/themes/global/includes/footer.html</arg></widget>
</body>
</html>
Templates and file includes can live anywhere, but we recommend keeping them in /_ingredients/themes/global/includes/.
To install your site’s header and footer:
- Login via SFTP and edit /_ingredients/themes/global/includes/header.html
- Copy the header HTML from your main website using View Source and paste it into header.html.
- Review the HTML and update any relative links (a href=”/about”) to absolute links (a href=”https://www.myschool.edu/about”), including image sources. You may also want to selectively delete elements that you don’t need to reproduce on the calendar.
- Repeat these steps for footer.html.
Customizing the header for different groups
You can create entirely separate themes for individual calendar groups, but most of the time that’s not necessary. It’s common instead to use a simple XPHP if/then block in order to show group-specific header text when visiting a group calendar. For example, you can use this in header.html:
<xphp content="true">
<if var="group_title" />
<content>
<!– LW: Use the group name on group calendars –>
<h1><xphp var="group_title"/> Events Calendar</h1>
</content>
<else content="true">
<content>
<!– LW: Use a generic name on the main calendar –>
<h1>Events Calendar</h1>
</content>
</else>
</xphp>
Loading styles and scripts from your main site
You can also copy site-wide style and script references from <head>…</head> on your website into the head.html include, following the same steps to replace relative with absolute URLs. A few things to be wary of, though:
- LiveWhale Calendar comes bundled with jQuery. In many cases, you don’t need to load a second version of jQuery if you’re using it on your site, too.
-
Pasting in hard-coded references to your main site assets like
can be a risk—if those files get deleted or updated from your main site, it can break functionality on the calendar. You can use your judgment if you’d prefer to hard-link to those files; in cases where you’re not sure how often those files get updated, we suggest downloading a current copy of what you need and uploading those files into your global theme’s styles/ and scripts/ folder. That way, your calendar’s look and feel will stay the same until you choose to update it.<link rel="stylesheet" href="//www.myschool.edu/css/main.css" media="all" /> <script src="//www.myschool.edu/js/scripts.js" type="text/javascript"></script>
- Often times, even with all the files loading, certain JavaScript elements—like, an expandable menu—might not work correctly when copied to your calendar site. You can either re-build those elements with custom JavaScript (below) or create a pared-down navigation for the calendar that doesn’t require those elements.
Directly importing a header and footer
By following the above steps, you’re essentially hosting a static version of your site’s header/footer on the LiveWhale Calendar server. So, any changes made to those files on your main site will need to be manually copy into the calendar theme in order for those to stay in sync. This is often why schools opt to create a pared-down version of those elements for the calendar, so they’re not having to update theme files every time their main navigation or footer info changes.
At the same time, you may be wondering: is it possible to import the live header and footer directly from my website? You’re welcome to try, by placing a file include like this in header.html.
<xphp include="https://www.myschool.edu/includes/header.html"/>
However, in our experience, most of the time there’s going to be HTML in that header that prevents it from working as a standalone file include. It may be unenclosed HTML tags (LiveWhale requires fully parsing HTML5), relative links where you need absolute, or other functionality that requires server- or client-side code specific to your CMS.
It’s not impossible—we’ve seen a small handful of cases where teams are able to generate a fully-importable version of the header this way—but nine times out of ten, manually maintaining your header/footer via SFTP is easy and low-maintenance.
Common calendar settings
The /_ingredients/themes/global/includes/calendar_settings.html file include has a widget where you can set a number of configuration options—some of which were discussed in the setup onboarding guide. Specific to theming, some settings you might want to consider are:
-
Default view: the default is “today”, but some schools choose a different default view for visitors just arriving on the calendar.
<arg id="default_view">all</arg>
-
Image width/height: depending on your design, you may want to customize the size of event thumbnails in the day/week/all views.
<arg id="thumb_width">250</arg> <arg id="thumb_height">250</arg>
Note: you can opt for a design that uses non-square thumbnails, but the calendar manager tools for selecting a thumbnail are locked to a 1:1 aspect ratio. If you use calendar or widget thumbnails in a non-square aspect ratio, LiveWhale will provide the closest possible crop that honors the user’s square thumbnail selection.
-
All day events: by default, all day events appear first in the calendar. You can change this in the settings.
<arg id="display_all_day_events_last">true</arg>
See the full list of calendar settings for everything that you can configure inside calendar_settings.html. More examples of settings—including which events to show on the main calendar and how they’re organized—can be found in the setup onboarding guide.
Editing calendar CSS styles
Your calendar page will render and load /_ingredients/themes/global/styles/calendar.less on every view by default.
If you have a different or additional theme set (say, a custom “music” theme for the “music” group), it will also load the associated /_ingredients/themes/music/styles/calendar.less, if one exists.
You’ll notice your calendar.less begins with something like this:
/* Import core calendar styles */
"../../../../livewhale/theme/core/styles/calendar.less";
/* Import core calendar mobile mixins */
"../../../../livewhale/theme/core/styles/calendar-mobile.less";
This is very important— including these styles and mobile mixins allow you to build on top of the existing core calendar styles from LiveWhale, rather than starting from scratch. It also means that the next time you are upgraded, any changes to the core theme/UI will filter down into your theme with minimal work.
There are also a few default variables you’ll inherit from the core theme, but can override in your own calendar.less should you choose:
@highlight1: #c00; // Primary color
@highlight2: #900; // Secondary color
@white: #fff;
@dark-gray: #333;
@mid-gray: #2a3132;
@light-gray: #eee;
@accentfont: inherit;
At minimum, we recommend setting your school color(s) to the
@higlight1
and
@highlight2
variables in /_ingredients/themes/global/styles/calendar.less. You may want to do more hands-on styling of individual elements, but this will do a first-pass at matching the various UI elements to your chosen colors.
Using mobile mixins
The calendar-mobile.less include doesn’t generate any rendered CSS, but rather provides a few mixins that you can choose to include in your own style, to allow for custom mobile breakpoints and styling.
Mobile Mixin | Description |
---|---|
.mobileMonthViewList() |
Display month view as a list of events |
.mobileMonthViewGrid() |
Display month view as a grid of dots |
.mobilePaymentsTable() |
Display RSVP form in a single column |
.mobileViewSelector() |
Display pared-down view selector |
Example usage:
/* Import core calendar mobile mixins */
"../../../../livewhale/theme/core/styles/calendar-mobile.less";
// Set the mobile breakpoint
@media only screen and (max-width: 767px) {
.mobileViewSelector(); // use the mobile view selector
.mobileMonthViewGrid(); // use a grid of dots for month view
.mobilePaymentsTable(); // use the mobile RSVP form
}
Optional: Using LESS partials
The above approach of including the entire /livewhale/theme/core/styles/calendar.less is a great way to get a good-looking calendar up quickly. Even if you are working on a highly-customized design, we don’t recommend starting your calendar.less from scratch, since then you can miss out on future feature additions or accessibility improvements to the core calendar.
In LiveWhale 2.16.1 and later, you can opt to select individual LESS partials from the core theme, rather than the entire block of CSS at once. This way, you might choose to inherit base styles and certain UI elements (like the mini-cal or month view) from core, even if you end up starting from scratch for other elements (like the event list or event details views).
To get started with this approach, replace your /_ingredients/themes/global/styles/calendar.less with this:
/* Core theme styles – LiveWhale Calendar */
"../../../../livewhale/theme/core/styles/calendar/_variables.less";
"../../../../livewhale/theme/core/styles/calendar/_mixins.less";
"../../../../livewhale/theme/core/styles/calendar/_button.less";
"../../../../livewhale/theme/core/styles/calendar/_layout.less";
// Calendar views
"../../../../livewhale/theme/core/styles/calendar/_calendar-header.less";
"../../../../livewhale/theme/core/styles/calendar/_calendar-list.less";
"../../../../livewhale/theme/core/styles/calendar/_calendar-month.less";
"../../../../livewhale/theme/core/styles/calendar/_calendar-day.less";
"../../../../livewhale/theme/core/styles/calendar/_event-detail.less";
"../../../../livewhale/theme/core/styles/calendar/_feed-builder.less";
// Calendar design elements
"../../../../livewhale/theme/core/styles/calendar/_map.less";
"../../../../livewhale/theme/core/styles/calendar/_online.less";
"../../../../livewhale/theme/core/styles/calendar/_timezone.less";
"../../../../livewhale/theme/core/styles/calendar/_mini-cal.less";
"../../../../livewhale/theme/core/styles/calendar/_selectors.less";
"../../../../livewhale/theme/core/styles/calendar/_subscribe.less";
"../../../../livewhale/theme/core/styles/calendar/_search.less";
// Print styles
"../../../../livewhale/theme/core/styles/calendar/_print.less";
// Styles included for legacy support
// "../../../../livewhale/theme/core/styles/calendar/_legacy.less";
From this starting point, you can add or import additional styles, commenting out the individual LESS partials from core that you may not need for your design.
Note: If you do choose to bypass or override CSS from core this way, please make note of future releases that might include features or changes to the core CSS. To accommodate those features in your design, you may need to update your custom CSS when upgrading.
Editing calendar HTML components
Edit your calendar’s components allows you to tweak the appearance and functionality of individual pieces of the calendar, like the event view, day view, date selector, and more.
The most common components to edit during theming are
- lw_cal_event.html – how an individual event appears in the day, week, and all views
- lw_cal_event_details.html – the details page for an individual event, when a visitor clicks the event title
See the full list of calendar components and variables.
As an example, let’s say you wanted to customize the HTML markup of an individual event from the day/week/all lists.
If you copy /livewhale/theme/core/components/calendar/lw_cal_event.html to /_ingredients/themes/global/components/calendar/lw_cal_event.html and edit it, you’ll see this:
<script id="lw_cal_event_template" type="text/template">
<div class="lw_cal_event{[ obj.is_canceled ? print(' lw_is_canceled') : '' ]}{[ obj.is_online ? print(' lw_is_online') : '' ]}{[ obj.image_src ? print(' lw_has_image') : '' ]}{[ obj.repeats ? print(' lw_repeats') : '' ]}{[ obj.is_multi_day ? print(' lw_multi_day') : '' ]}{[ obj.is_first_multi_day ? print(' lw_first_multi_day') : '' ]}{[ obj.multi_day_span ? print(' lw_multi_day_span'+obj.multi_day_span) : '' ]}{[ obj.href ? '' : print(' lw_no_details') ]}{[ obj.tag_classes ? print(' '+obj.tag_classes) : '' ]}{[ obj.category_classes ? print(' '+obj.category_classes) : '' ]}" data-id={{ id }}>
<div class="lw_cal_event_info">
{[ if (obj.has_map) { ]}
<div class="lw_cal_location_link_wrapper">
[ <a href="?event_id={{ id }}" data-latitude="{{ latitude }}" data-longitude="{{ longitude }}" class="lw_cal_location_link">map</a> ]
</div>
{[ } ]}
<div class="lw_events_location">
{{ location }}
</div>
<div class="lw_events_time">
{[ if (obj.is_online) { ]}
<i class="lw-icon lw-icon-videocam" aria-label="Online event"></i>
{[ } ]}
{{ time }}
</div>
{[ if (obj.image_raw ) { ]}
<span class="lw_item_thumb">
<a href="{{ href }}">{{ image_raw }}</a>
</span>
{[ } ]}
<div class="lw_events_title">{{ title_link }}</div>
{[ if (obj.summary) { ]}
<div class="lw_events_summary">{{ summary }}</div>
{[ } ]}
{[ if (obj.until) { ]}
<div class="lw_events_until">{{ until }}</div>
{[ } ]}
</div>
</div>
</script>
A few things to notice about editing component files:
- Components are all wrapped in a <script> tag that you shouldn’t edit.
- Components use a special {{ double bracket style of variable }}. See the full variable list.
- Components can use simple JavaScript-style conditional logic in {[ curly-and-square brackets ]}.
- You may discover that most JavaScript code runs in those {[ curly-and-square bracket ]} – however, we do not recommend writing lots of JavaScript code into your components. In most cases, if you’re trying to do something, there’s a simpler way using other configuration options. Feel free to ask in the forum if you aren’t sure where to make a theming or configuration change.
Advanced theming techniques
Group-specific theming
When you create a new group in LiveWhale Calendar, you have the option to give it a URL. Visitors to that URL will see the “group calendar,” which uses a special template group_calendar.php. In most cases, your group_calendar.php is probably the same as your main /index.php — that is, individual group calendars look like your calendar homepage.
You can customize that template if you like at /_ingredients/templates/group_calendar.php. Or, if no group_calendar.php is found, LiveWhale will instead fall back to your calendar homepage /index.php.
The Group Calendar URL that you specify in the Edit Group settings can be used to override theme files (file includes, CSS, and JS) on a group-specific basis. That is, if you have a calendar at /biology/, LiveWhale will automatically check for an /_ingredients/themes/biology/ when loading that calendar page, following the normal theming inheritance rules. This can be useful if you have certain calendars that should have a somewhat or significantly different look than the rest of your main calendar.
That being said: most group-specific calendar customizations are very simple, and don’t require a separate theme. Before making a new theme, ask if you may instead be able to accomplish what you need with a simple XPHP if/then in your existing theme. More examples of this are in the setup onboarding guide.
Using the “home” view
In addition to the “day,” “week,” “month,” and “all” views, there is an optional “home” view in LiveWhale Calendar for showing only featured events. You can enable it for your calendar’s landing page by adding this to calendar_settings.html:
<arg id="default_view">home</arg>
Adding custom JavaScript interactivity
If you create a /_ingredients/themes/global/scripts/calendar-custom.js, it will be loaded in every view of your front-end calendar. This can be used to implement additional JavaScript or jQuery customization on top of the default calendar behavior.
The below template for calendar-custom.js aliases the livewhale.jQuery object for you to use as
$
. Note also, there are several JavaScript triggers you can bind to, in order to run custom code at particular times or for particular views.
(function($, LW) {
// Events:
// calBeforeChange.lwcal - fires before loading new view data
// calChange.lwcal - fires after loading new view data
// calLoad.lwcal - fires after each view finishes loading
$('body').bind('calLoad.lwcal', function(e, controller, data) {
var view = controller.getView();
if ('day' === view) {
// do something on day view
}
if ('week' === view) {
// do something else on week view
}
if ('event' === view) {
// do something else on event view
}
});
}(livewhale.jQuery, livewhale));
Here’s a very simple example, where you may want to hide a featured header when looking at individual event pages:
(function($, LW) {
$('body').bind('calLoad.lwcal', function(e, controller, data) {
var view = controller.getView();
if ('event' === view) {
$('div.featured-header').hide(); // hide featured header on event view
} else {
$('div.featured-header').show(); // show featured header on all other views
}
});
}(livewhale.jQuery, livewhale));