fullcalendar event categories

208 views
Skip to first unread message

Dave Hunsberger

unread,
Jun 9, 2016, 11:36:24 AM6/9/16
to Mura CMS
I've come up with a way to bring the event categories along to the full calendar page so that you can use them to add classes, etc. on event render.

I think there must be a better way though, and was hoping someone else might have suggestions.

The easy part:

in the /display_objects/calendar/index.cfm (either site or theme level).

add method to fullCalendar init.
,eventRender : function(event, element, view){
   
if(event.categories && event.categories.length){
       
//convert to lower case & remove spaces
       classStr
= event.categories.replace(/ /g, "-").toLowerCase();
       element
.addClass(classStr);
   
}
 
}

This function requires 'categories' be part of the data return, which is the difficult part.

I found a better way to do it in the sql that is used to retrieve the event query, but wasn't able to use the mura '.addJoin()' method to actually get it to work, so I did the following instead:

in the file \requirements\mura\content\contentCalendarUtilityBean.cfc

modify function fullCalendarFormat() to be (added 2 lines for 'categories')

public any function fullCalendarFormat(required query data) {
   
var serializer = new mura.jsonSerializer()
     
.asString('id')
     
.asString('url')
     
.asDate('start')
     
.asDate('end')
     
.asString('title')
     
.asString('categories');

   
var qoq = new Query();
    qoq
.setDBType('query');
    qoq
.setAttributes(rs=arguments.data);
    qoq
.setSQL('
      SELECT
        url as [url]
        , contentid as [id]
        , menutitle as [title]
        , displaystart as [start]
        , displaystop as [end]
        , categories as [categories]
      FROM rs
    '
);

   
local.rsQoQ = qoq.execute().getResult();

   
return serializer.serialize(local.rsQoQ);
 
}

This will allow a return of the categories to the event Calendar, provided you can get one.


Now the part that I'm not sold on.

For the function getCalendarItems()
I tried using the '.addJoin()', but it didn't seem to work, so I had to write queries & query-of-queries.

  public any function getCalendarItems(
    calendarid
='#variables.$.content('contentid')#'
   
, start='#variables.$.event('year')#-#variables.$.event('month')#-1'
   
, end=''
   
, categoryid='#variables.$.event('categoryid')#'
   
, tag='#variables.$.event('tag')#'
   
, siteid='#variables.$.event('siteid')#'
   
, returnFormat='query'
 
) {
   
var tp = variables.$.initTracePoint('mura.content.contentCalendarUtilityBean.getCalendarItems');
   
var local = {};


   
local.contentBean = variables.$.getBean('content').loadBy(contentid=arguments.calendarid, siteid=arguments.siteid);


   
local.applyPermFilter = variables.$.siteConfig('extranet') == 1
     
&& variables.$.getBean('permUtility').setRestriction(local.contentBean.getCrumbArray()).restrict == 1;


   
if ( local.contentBean.getIsNew() || local.contentBean.getType() != 'Calendar' ) {
     
return QueryNew('url,contentid,menutitle,displaystart,displaystop');
   
}


   
// Start Date
   
if ( !IsDate(arguments.start) ) {
      arguments
.start = CreateDate(Year(Now()), Month(Now()), 1);
   
}


   
// End Date
   
if ( !IsDate(arguments.end) || ( IsDate(arguments.end) && Fix(arguments.end) < Fix(arguments.start) )  ) {
      arguments
.end = CreateDate(Year(arguments.start), Month(arguments.start), DaysInMonth(arguments.start));
   
}


   
// start and end dates
   
local.displaystart = DateFormat(arguments.start, 'yyyy-mm-dd');
   
local.displaystop = DateFormat(arguments.end, 'yyyy-mm-dd');


   
// the calendar feed
   
local.feed = variables.$.getBean('feed')
     
.setMaxItems(0) // get all records
     
.setNextN(0) // no pagination
     
.setSiteID(arguments.siteid)
     
.addParam(
        relationship
='AND'
       
,field='tcontent.parentid'
       
,condition='EQ'
       
,criteria=local.contentBean.getContentID()
     
)
     
// filter records with a displayStart date that is before the displayStop date
     
.addParam(
        relationship
='AND'
       
,field='tcontent.displaystart'
       
,condition='LTE'
       
,criteria=local.displaystop
     
)
     
// OPEN GROUPING
       
// filter records with a displayStop date that occurs after the displayStart date
       
// OR doesn't have one at all
       
.addParam(relationship='andOpenGrouping')
         
.addParam(
            field
='tcontent.displaystop'
           
,condition='GTE'
           
,criteria=local.displaystart
         
)
         
.addParam(
            relationship
='OR'
           
,field='tcontent.displaystop'
           
,criteria='null'
         
)
       
.addParam(relationship='closeGrouping');
     
// CLOSE GROUPING


   
// Filter on CategoryID
   
if ( Len(arguments.categoryid) && IsValid('uuid', arguments.categoryid) ) {
     
local.feed.setCategoryID(arguments.categoryid);
   
}

   
// Filter on Tags
   
if ( Len(arguments.tag) ) {
     
local.feed.addParam(
        relationship
='AND'
       
,field='tcontenttags.tag'
       
,condition='CONTAINS'
       
,criteria=URLDecode(arguments.tag)
     
);
   
}

   
// THIS IS WHERE USING addJoin() WOULD HAVE BEEN IDEAL.
   
// INSTEAD, CATEGORIES HAVE TO BE QUERIED SEPARATELY.

   
// the recordset/query
   
local.rs = local.feed.getQuery(from=local.displaystart, to=local.displaystop, applyPermFilter=local.applyPermFilter);

   
// ADDED TO INCLUDE CATEGORIES
   
local.catsQ = new query();
   
local.catsQ.setSQL("
        SELECT DISTINCT tcontentcategories.name as catname, tcontentcategoryassign.contentID
        FROM tcontentcategories
        LEFT JOIN tcontentcategoryassign with (nolock) on (tcontentcategoryassign.categoryID = tcontentcategories.categoryID)
        WHERE tcontentcategoryassign.contentID is not null
        AND tcontentcategoryassign.contentID in ( :catslist )
    "
);

   
//param to only fetch categories of events that were fetched, to keep down overhead
   
local.catsQ.addParam(
        name
='catslist'
       
, cfsqltype='cf_sql_varchar'
       
, value=valueList(local.rs.contentID)
       
, list='true'
   
);
   
local.cats=local.catsQ.execute().getResult();

   
// prepare to add URL column
   
// MODIFIED TO ALSO INCLUDE A CATEGORIES FIELD
               
      qoq
= new query();
      qoq
.setDBType('query');
      qoq
.setAttributes( rs1=local.cats );

     
QueryAddColumn(local.rs, 'URL', []);

     
QueryAddColumn(local.rs, 'CATEGORIES', []); //ADDED

     
for ( local.i=1; local.i<=local.rs.recordcount; local.i++ ) {
       
// add URL to rs
       
local.rs['URL'][i] = variables.$.createHref(filename=local.rs['filename'][i]);
       
// convert dates to UTC, then use browser's local tz settings to output the dates/times
       
/*
        local.tempstart = DateConvert('local2utc', local.rs['displaystart'][i]);
        local.tempend = DateConvert('local2utc', local.rs['displaystop'][i]);
        local.rs['displaystart'][i] = isoDateTimeFormat(local.rs['displaystart'][i]);
        local.rs['displaystop'][i] = isoDateTimeFormat(local.rs['displaystop'][i]);
        */


       
local.rs['CATEGORIES'][i] = ""; //ADDED

       
// TRY TO LINK CATEGORIES TO CONTENT
        qoq
.clearParams();
        qoq
.setSQL('SELECT * FROM rs1 where contentID = :cid ');
        qoq
.addParam(
          name
='cid'
         
, cfsqltype='cf_sql_varchar'
         
, value=local.rs['contentID'][i]
       
);
        rsQoQ
= qoq.execute().getResult();

       
if(rsQOQ.recordcount){        
         
local.rs['CATEGORIES'][i] = valueList(rsQoQ.catname); //ACTUALLY ADD CATEGORIES TO QUERY RETURN
       
}

     
}

   
local.rs = filterCalendarItems(data=local.rs, maxItems=0);


    variables
.$.commitTracePoint(tp);

   
return arguments.returnFormat == 'iterator'
     
? variables.$.getBean('contentIterator').setQuery(local.rs)
     
: local.rs;
 
}

Needless to say, this is non update-safe, and it seems to me that this is a lot of modification for something that should have been simple.

Perhaps the best approach would be to modify the feed getQuery() function to always include categories, and all this file modification could go away?

Dave Hunsberger

unread,
Jun 9, 2016, 1:27:52 PM6/9/16
to Mura CMS
I figured a simpler way.

* Do the first part in the display_objects/calendar/index.cfm file (theme or site).

* In the file \requirements\mura\content\contentCalendarUtilityBean.cfc

* Modify function fullCalendarFormat() as above.

In the function getCalendarItems(), do this instead (ignoring all the queries & QoQ stuff):

add to this block (around line 127):


     
QueryAddColumn(local.rs, 'URL', []);
     
QueryAddColumn(local.rs, 'CATEGORIES', []); // ------------------------------ ADDED

     
for ( local.i=1; local.i<=local.rs.recordcount; local.i++ ) {
       
// add URL to rs
       
local.rs['URL'][i] = variables.$.createHref(filename=local.rs['filename'][i]);
       
// convert dates to UTC, then use browser's local tz settings to output the dates/times
       
/*
        local.tempstart = DateConvert('local2utc', local.rs['displaystart'][i]);
        local.tempend = DateConvert('local2utc', local.rs['displaystop'][i]);
        local.rs['displaystart'][i] = isoDateTimeFormat(local.rs['displaystart'][i]);
        local.rs['displaystop'][i] = isoDateTimeFormat(local.rs['displaystop'][i]);
        */



       
// ADD CATEGORIES
       
local.rs['CATEGORIES'][i] = "";
        itCats
= variables.$.getBean('content').loadBy( contentID=local.rs['contentID'][i] ).getCategoriesIterator();

       
if( itCats.hasNext() ){
         
while ( itCats.hasNext() ) {          
            thisItem
= itCats.next();
           
local.rs['CATEGORIES'][i] = listAppend( local.rs['CATEGORIES'][i], thisItem.getName() );
         
}
       
}

     
}


Still not update-safe, but simpler.

Dave Hunsberger

unread,
Apr 24, 2019, 6:07:45 PM4/24/19
to Mura Digital Experience Platform
Here's yet another way, that works in mura 6 -- sub calendars.

Using the default calendar class extension, enable the subtype of calendar to be added, and add sub calendars to your main calendar, ensuring the isNav is checked to show in navigation.

Clone display_objects/calendar into your theme, or edit in your site layout to add the following to the /display_objects/calendar/index.cfm file:

After the calendar colors, modify to have the colors you want, and add the code to import any sub calendars.
<cfset this.calendarcolors=[
 {background='##2FBDA8',text='##666'},
 {
background='##FFC184',text='##666'},
 {
background='##FFFF00',text='##666'},
 {
background='##AA8EF2',text='##666'},
 {
background='##84A6BC',text='##666'}
 ]
>

 
<!-- modified to find children calendars and add to search items --->
 
<cfset calBean = variables.$.getBean('content').loadBy(contentID='#variables.$.content('contentid')#') />
 
<cfset calBeanKids = calBean.getKidsIterator() />
 
<cfif calBeanKids.getRecordCount()>
   
<cfloop condition="calBeanKids.hasNext()">
     
<cfset calKidItem = calBeanKids.next()>
     
<cfif calKidItem.getType() EQ 'calendar' AND calKidItem.getIsNav()>
       
<cfset arrayAppend(objectParams.items,calKidItem.getValue('contentid'))>
     
</cfif>
   
</cfloop>
 
</cfif>


Also, right before the js calls around line 219, add the following function, to fix the way that the legend is indexed, but the parent calendar events are attempted to be loaded, which offsets the color array by 1
 <!--- if using children calendars, the main calendar will not get a legend, so need to remove any events, otherwise the legend colors will be offset by 1 --->
<cfif arrayLen( objectParams.items ) GT 1>
  <cfset arrayDeleteAt(objectParams.items, 1) />
</cfif>

var colors=#lcase(serializeJSON(this.calendarcolors))#;
var calendars=#lcase(serializeJSON(objectparams.items))#;


Note that the call to get main events doesn't set a legend for the parent calendar, but only the children, so the colors are offset if you look at the parent calendar as well as children for events, so the above will simply remove the parent calendar, ergo no overall events, just those in separate calendars.

Toro

unread,
Jul 24, 2019, 5:52:12 AM7/24/19
to Mura Digital Experience Platform
Hi,

I needed to do something similar to what was posted here which was very helpful, thank you.

In order to use the functions mentioned in this post but without changing Mura's code, I registered a custom API method

To register the API method follow this post

I copied findCalendarItems (renamed to myFindCalendarItems) and checkForChangesetRequest from Mura's core code and added it to my event handler and I also registered 'myFindCalendarItems' API method on application load.

I also copied getCalendarItems and filterCalendarItems from Mura's core code and added it to my contentRenderer. This has now allowed me to customise these functions (as described in Dave's post above ) without needing to make changes to the core version.

Final step, I copied the calendar module folder from core and added it to my site, and I just changed the endpoint calling my newly registered API method myFindCalendarItems

I'm using Mura v7.1 but I think it would also work for Mura 6.

Dave Hunsberger

unread,
Mar 20, 2024, 4:40:51 PMMar 20
to Mura Digital Experience Platform
Finally, an update-safe method (at least in mura 7.0)

Building on the above scenarios, instead do the following:

If not already, copy the site display_objects/calendar folder into your theme (unless modifying at site level instead).

Within calendar/index.cfm, add method within the fullCalendar init

,eventRender: function(event, element) {
    if(event.categories && event.categories.length){
        var valueArray = event.categories.split(",");
        for(var i=0;i<valueArray.length;i++){
            element.addClass('cat_' + valueArray[i].toLowerCase());
        }
    }
}

Within your site/theme contentRenderer.cfc

Copy the function fullCalendarFormat(...) from requirements\mura\content\contentCalendarUtilityBean.cfc and paste into contentRenderer.cfc as myFullCalendarFormat(...)

Copy the function getCalendarItems(...) from requirements\mura\content\contentCalendarUtilityBean.cfc and paste into contentRenderer.cfc as myGetCalendarItems(...)

Modify myFullCalendarFormat(...) with the following

            var serializer = new mura.jsonSerializer()
            .asString('id')
            .asString('url')
            .asBoolean('allDay')
            .asString('title')
            .asUTCDate('start')
            .asUTCDate('end')
            .asString('categories'); // add this line
.
.
.
            SELECT
                url as [url]
                , contentid as [id]
                , menutitle as [title]
                , displaystart as [start]
                , displaystop as [end]
                , allday as [allday]
                , categories as [categories] //add this line
            FROM rs

Modify myGetCalendarItems(...) with the following

.
.
.

            // prepare to add URL column
            QueryAddColumn(local.rs, 'url', []);
            QueryAddColumn(local.rs, 'allday', []);
            QueryAddColumn(local.rs, 'categories', []); // add this line
'
'
'
            // ADD CATEGORIES
            local.rs['categories'][i] = "";
            itCats = variables.$.getBean('content').loadBy( contentID=local.rs['contentID'][i] ).getCategoriesIterator();

            if( itCats.hasNext() ){
                while ( itCats.hasNext() ) {
                    thisItem = itCats.next();
                    local.rs['categories'][i] = listAppend( local.rs['categories'][i], thisItem.getName() );
                }
            }

Finally, add this function to contentRenderer.cfc to use your copies above instead of mura defaults (it's a clone of the one within the mura contentRenderer.cfc, with added method injection)

        public any function getCalendarUtility() {
            return variables.$.getBean('contentCalendarUtilityBean')
            .setMuraScope(variables.$)
            .injectMethod( "fullCalendarFormat", myFullCalendarFormat )
            .injectMethod( "getCalendarItems", myGetCalendarItems );
        }


Update your css to match your categories with the colors you want:
(this may change slightly based on actual fullCalendar html output)
 
div.mura-calendar-wrapper td.fc-event-container a.fc-event.cat_YOURCATEGORYNAME {
    background-color: #123123 !important;
    border-color: #123123 !important;
    color: #fff !important;
}

save changes, doesn't even require an appreload to begin working!

Dave Hunsberger

unread,
Mar 20, 2024, 4:46:46 PMMar 20
to Mura Digital Experience Platform
better eventRender method for fullCalendar init, as it replaces spaces in category names with hyphens

,eventRender: function(event, element) {
    if(event.categories && event.categories.length){
        var valueArray = event.categories.split(",");
        for(var i=0;i<valueArray.length;i++){
            element.addClass('cat_' + valueArray[i].replace(/ /g, "-").toLowerCase());
        }
    }
}

Dave Hunsberger

unread,
Mar 20, 2024, 5:04:36 PMMar 20
to Mura Digital Experience Platform
Further Improvements:

,eventRender: function(event, element) {
    if(event.categories && event.categories.length){
        var valueArray = event.categories.split(",");
        for(var i=0;i<valueArray.length;i++){
            element.addClass('cat_' + valueArray[i].replace(/\W/g, "-").toLowerCase());
        }
    }
}


better css:
.fc-event-container a.fc-event.cat_YOURCATEGORY-NAME {
    background-color: #2FBDA8 !important;
    border-color: #2FBDA8 !important;
    color: #fff !important;
}
Reply all
Reply to author
Forward
0 new messages