Creating the Plugin
In this article I will explain how I created a custom view based on the fullcalendar js library.
Custom views give you complete control over how a Block is rendered in the backoffice and this enables you to give a better representation of the content.
As part of an Umbraco upgrade I needed to create a custom block view to display events using fullcalendar.io (The site was currently using it in the UI so needed it to do the same in the backoffice)
After some digging around I came across Angularjs Full Calendar Example - Plunker (plnkr.co) which explained how to display a calendar in Angular-JS using fullcalendar 2.1.1 while this was an older version of fullcalendar. I decided it would probably suffice as all I wanted was to display some events in the calendar.
Really the only thing I needed to do was write my own Angular-JS controller and work out how to get the other files loaded and registered so I could use them.
An App_Plugins folder was created for the files and a package manifest added.
{
"name": "FullCalendar BlockItem",
"version": "1.0.0",
"javascript": [
"~/App_Plugins/FullCalendar/scripts/fullcalendar.min.js",
"~/App_Plugins/FullCalendar/scripts/moment.min.js",
"~/App_Plugins/FullCalendar/scripts/ui-calendar.min.js",
"~/App_Plugins/FullCalendar/fullcalendar-controller.js",
"~/App_Plugins/FullCalendar/fullcalendar-resources.js"
],
"css": [
"~/App_Plugins/FullCalendar/css/fullcalendar.min.css"
]
}
The ui-calendar javascript file contains a module that needs to be imported into the Angular-JS app in order for it all to work, by some trial and error I managed to work out how to do that with the following code at the top of my controller.
angular.module('umbraco').requires.push('ui.calendar');
With that part figured out I could now create my controller (fullcalendar-controller.js).
angular.module('umbraco').requires.push('ui.calendar');
angular.module('umbraco').controller('fullCalendarController',
['$scope', '$log', 'uiCalendarConfig', '$timeout','fullCalendarResource',
function($scope, $log, uiCalendarConfig, $timeout,fullCalendarResource) {
$scope.uiCalendarConfig = uiCalendarConfig;
$scope.events = [];
$scope.eventSources = [];
$scope.calendarConfig = {
selectable: true,
selectHelper: true,
editable: true,
header:{
left: $scope.block.data.left,
center: $scope.block.data.center,
right: $scope.block.data.right
}
};
fullCalendarResource.getEventsFromApi($scope.block.data.dataSource).then(function(response) {
angular.forEach(angular.fromJson(response.data), function(item) {
$scope.events.push({ id: $scope.generateGuid(), title: item.title, className:item.className, start: item.start, end: item.end, description: item.description, allDay: item.allDay });
});
$scope.eventSources.push($scope.events);
});
$scope.generateGuid = function() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = Math.random() * 16 | 0,
v = c == 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}
}
]);
The Angular-JS controller calls an api which should return a JSON array of events to render for the displayed month. The event JSON should be the format below.
[
{
"title":"Example event",
"description":"Some sort of event is happening",
"start":"2022-10-19T23:00:00Z",
"end":"2022-10-19T23:00:00Z",
"id":"0f87be74-7321-ec11-a97f-0022483fd864",
"url":null,
"allDay":true,
"daysOfWeek": "[2,5]",
"startRecur":"2022-10-01",
"endRecur":"2022-10-30",
"startTime":"14:30",
"endTime":"15:30"
}
]
The custom view template is a simple HTML file written as Angular-JS template. Any HTML can be used, but we will use Angular-JS syntax for data binding.
This is the code for the view:
App_Plugins/FullCalendar/fullcalendar.html
<div ng-controller="fullCalendarController">
<div ui-calendar="calendarConfig" ng-model="eventSources" calendar="myCalendar" style="margin: 1em;"></div>
</div>
Document Type
With the backoffice Angular-JS stuff done, it was time to fire up the website and add an element document type to hold the settings and add to the BlockGrid blocks.
The element contains some textstrings to hold various settings for the calendar.
DataSource - A Url to an api that returns a JSON array of events
left : title
center : today
right : prevYear prev,next nextYear
Items to display in the Toolbar sections (left,center,right), each section can contain any of the following values and should be seperated by a comma or a space.
today,prev,next,prevYear,nextYear,title
The element was then added to the BlockGrid template and it's custom view and style sheet set to our new fullcalendar templates.
The new block item was added to a content page and the DataSource set to an api (/testevents/) success! The fullcalendar displayed and the events were showing, well some of them anyway , the recurring events were not being displayed. It turns out this was because fullcalendar 2.1.1 did not support recurring events
Angular-JS controller
Given that the code was just for displaying the events and the event JSON contained all the data needed for the recurring event the easiest thing was to just generate the recurring events in the controller and add them to the array of events to display. I extended the controller as below.
angular.module('umbraco').requires.push('ui.calendar');
angular.module('umbraco').controller('fullCalendarController',
['$scope', '$log', 'uiCalendarConfig', '$timeout','fullCalendarResource',
function($scope, $log, uiCalendarConfig, $timeout,fullCalendarResource) {
$scope.uiCalendarConfig = uiCalendarConfig;
$scope.events = [];
$scope.eventSources = [];
$scope.calendarConfig = {
selectable: true,
selectHelper: true,
editable: true,
header:{
left: $scope.block.data.left,
center: $scope.block.data.center,
right: $scope.block.data.right
}
};
fullCalendarResource.getEventsFromApi($scope.block.data.dataSource).then(function(response) {
angular.forEach(angular.fromJson(response.data), function(item) {
if (item.daysOfWeek) { //recurring event so create instances
angular.forEach(angular.fromJson(item.daysOfWeek),
function(dayOfWeek) {
const date = moment(item.startRecur);
const dow = date.isoWeekday();
// if we haven't yet passed the day of the week that I need:
if (dow <= dayOfWeek) {
// then just give me this week's instance of that day
date.isoWeekday(dayOfWeek);
} else {
// give me next week's instance of that day
date.add(1, "w");
date.isoWeekday(dayOfWeek);
}
while (date < moment(item.endRecur)) {
item.start = date.format("YYYY-MM-DD") + "Z" + item.startTime;
item.end = date.format("YYYY-MM-DD") + "Z" + item.endTime;
$scope.events.push({ id: $scope.generateGuid(), title: item.title, className:item.className, start: item.start, end: item.end, description: item.description, allDay: item.allDay });
date.add(1, "w");
}
});
} else {
$scope.events.push({ id: $scope.generateGuid(), title: item.title, className:item.className, start: item.start, end: item.end, description: item.description, allDay: item.allDay });
}
});
$scope.eventSources.push($scope.events);
});
$scope.generateGuid = function() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = Math.random() * 16 | 0,
v = c == 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}
}
]);
NuGet Package
The custom view has been released as a Nuget package
Umbraco.Community.FullCalendar.Block
Umbraco.Community.FullCalendar.Block
In this article I will explain how to configure the TinyMCE rich text editor in Umbraco v14
This is my dive into the new Umbraco 14 backoffice to create a Member EntityAction in order to send an email to the selected member.
Previously known as Tree Actions, Entity Actions is a feature that provides a generic place for secondary or additional functionality for an entity type. An entity type can be a media, document and so on.
In this blog post I explain how to implement an email validation flow for Member registration.