[FullCalendar Reactive] FullCalendar reactive icons in events
fullcalendar-reactive
Reactive icon
Forge component by André Cabral
Application Type
Reactive

Hi everyone,

I'm trying to add icons to events to help flag status' etc.

I have added a boolean value to the event structure called icon and for testing purposes if this is set to true then show an icon. I've been playing with various bits of code I found on other forums but cannot get the icon to display.  I am struggling to access the elements of the event to append/prepend the icon to when using .find() etc. 

What should I be adding to this code to display the icon.


eventRender: function(info) {
    if ( info.event.extendedProps.icon) {
       var i = document.createElement('i');
       i.className = 'fa fa-flag';
        
// I have tried a number of different methods here but all throw errors.Help please.
        
       console.log(info);
        
    }
Solution

Here is another oml I had forgotten about. In this one I create objects for the events containing different lists of items. I have done most of the js in the last assign of the BuildOptions action. A lot of it won't mean anything to you but it will show you how I went about customizing the events  in the eventRender function.


Also be aware I am fairly new to programming so there may be a better way than this.

FullCalenderListViewEvents.oml

This seems to have done the trick.


eventRender: function(info) {
    
    var i = document.createElement('i');
        if ( info.event.extendedProps.icon) {   
                i.className = 'fa fa-flag';
                info.el.append(i);
                }
}

Where, in outsystems, is the eventRender function placed? I'm not getting it.

You can add it into the advanced options as text.

It is also available to add to in the BuildOptions client action in the fullCalender module.

The build options you are talking about is the Advanced Options of the screen calendar?

Screenshot_2.png

Screenshot_3.png

Did you add the icon to the event properties in the fullCalendar module. You would need to do this then refresh references to the fullCalendar module from the module where you are using it. Then when generating your event data you can set the boolean to true/false. Alternatively in the fullCalender module in the buildOptions client action in the interface there is an eventRender function there you can add to. You will still need to add your new extended property to the event structure.

extendedProps.PNG

My version does not have this complete structure. I think it is v3

Screenshot_5.png

You can add to the structure whatever you like and then access it via 'info.event.extendedProps.exampleValue'

I made a clone of the fullCalender module so I could add as many extendedProperties as I needed.

Are you saying to change here (image)? In what way? Can you give me an example please?

Screenshot_4.png

This image refers to the above in the fullCalender module.

eventRender.PNG

Beware, when the calender uses its callBack functions, for example 'eventOnClick' it will not pass to the action the extended properties. You will need to keep a copy of your events lists to filter once you have the event id in case you want to take action on the extended props.

You wouldn't happen to have an .oml example that you could provide? I would appreciate it very much

I can put something together later but need to work right now.

When you can, I would appreciate it if you could provide me with the example. That's very kind of you

Below is an example of the full calender with an example extendedProp in the event structure. If you use this in an application and provide it with event data making sure to use the event structure from this module and making sure to set the exampleExtendedProp as true or false. You can then add this to your advanced options input var of the fullCalender block of your application filling in the eventRender function with what ever javascript you desire.


"
header: {


   
    left: 'prev,next',
    center:'title',
    right: ' weekResourceTimeline, resourceTimelineMonth'
},

defaultView:'resourceTimelineMonth',

buttonText: {
   
    
    weekResourceTimeline: '7 Days',
    resourceTimelineMonth: 'Month'   
},

views: {

   
        weekResourceTimeline:{
            type:'resourceTimeline',
            slotDuration:'24:00:00',
            slotLabelInterval: '24:00:00',
            duration: { days: 7 },
            dateIncrement:{ days: 7 },
            editable:false,
            slotLabelFormat:[
                    {weekday:'short', day: 'numeric'},
                   
                    ],
            resourceAreaWidth:'20%',

                resourceColumns: [
                    {
                      labelText: 'Name',
                      field: 'title'
                        
                    },
                    {
                      labelText: 'Average engagement',
                       
                      text: '',

                       
                    }
                  ]

        },
                  resourceTimelineMonth: {
      type: 'resourceTimelineMonth',
      slotDuration:'24:00:00',
        duration: { days: 31 },
            dateIncrement:{ days: 31 },
        
        resourceAreaWidth:'17%',
        editable:false,
            resourceColumns: [
                    {
                          labelText: 'Name',
                            field:'title'
                     
                    },
                    
                    
                    
                    {
                      labelText: 'Average engagement',
                       
                      text: ''
                      },
                    ]                 
    }
  },


eventRender:  function(info) {

            if(info.event.extendedProps.customExtendedProp1){
            //do something here
}


}"
exampleFullCalendarReactive.oml

Thank you very much! It will help me a lot!

By the way, can you help me put the title / description in the events when rendering is background? I get an error :\


Thank you very much! Everything is working correctly. You are a genius

Now I just need to add a  line break between the title and the description that I'm not getting

Solution

Here is another oml I had forgotten about. In this one I create objects for the events containing different lists of items. I have done most of the js in the last assign of the BuildOptions action. A lot of it won't mean anything to you but it will show you how I went about customizing the events  in the eventRender function.


Also be aware I am fairly new to programming so there may be a better way than this.

FullCalenderListViewEvents.oml

Your version looks awesome, you can learn a lot. Thanks a lot!

Do you know if it is possible to simply click a day in the calendar instead of selecting multiple days?

You would need to create a custom view or one day view in the advanced ( https://fullcalendar.io/docs/custom-views ) options, also in these options assign the default view to a client variable. This way you can destroy the calender , set the defaultview client variable to the view you want then ask the calender to render again.

With the version you kindly made available to me, the popove's (3+ more) don't open. How can I get it to work?

Thank you.

The best way to create popovers on event hover is to use the fullCalenderMouseenter callback event. In here I take the event_id passed back to the action and filter my events list to find the extendedProps I need to display in the popover. I then pass these into a javascript node and build the popover in there with html.


  

  

    

    



 Dont forget to remove the popover in the eventMouseLeave callBack.

This is my js as an example

  

           function timeConvert(n) {

//var num = n*60;

var hours = n;//(num / 60);

var rhours = Math.floor(hours);

var minutes = (hours - rhours) * 60;

var rminutes = Math.round(minutes);

return   rhours + " hour(s) and " + rminutes + " minute(s).";

}

            

   function locationCat(arg){

       if(arg == 'site'){

           return 'On Site';

       }else if(arg == 'thirdParty'){

           return 'Other Site';

       } else if(arg == 'm5'){

           return 'Inplant M5';

       } else if(arg == 'a30'){

           return 'Inplant A30';

       }else if(arg == 'm25'){

           return 'Inplant M25';

       } else{

           return 'Online';

       }

   } 

   

   function depotArea(arg){

       if(arg ==1){

           return 'A30';

       }else if(arg == 3){

           return 'M25';

       }else if(arg == 4){

           return 'M5';

       }else{ 

           return 'North/other';

       }

   }

   

   function custPrefs(arg){

       if(arg === ""){

           return 'None';

       }else{

           return arg;

       }

       

   }

            

            

            

            if($parameters.type == 3){

                

                 tooltip = '<div class="tooltiptopicevent" style="width:auto;border: 5px solid black;height:auto;background:white;color:black;position:absolute;z-index:10001;padding:10px 10px 10px 10px ; line-height: 105%;">'+$parameters.contractor+'<br>'+$parameters.title+'<br>'+$parameters.address+'<br><div id="contactTable"></div><br><u>Actions: </u>'+$parameters.actions+'<br><u>Scheduled dates:</u><br>'+$parameters.scheduledTime+'<br><u>Proposed Dates: </u><br>'+$parameters.AllowedDates+'</div>';

            }else{

            

            tooltip = '<div class="tooltiptopicevent" style="width:auto;border:2px solid black; border-radius:20px;height:auto;background:white;color:black;position:absolute;z-index:10001;padding:10px 10px 10px 10px ; "><h3 class="toolTitle">'+$parameters.title+'</h3><br><h4 class="toolAddress" style="min-width:300px; width:100%; display:block; background-color:grey; padding:5px; border-radius:5px;">'+$parameters.address+'</h4><br><p class="'+$parameters.locCat+'" style="width:20%; display:inline-block; padding:5px;border:1px solid black; border-radius:5px;margin-top:3px;text-align:center;">'+locationCat($parameters.locCat)+'</p><p class="'+$parameters.eventCat+'" style="width:20%; display:inline-block; padding:5px;border:1px solid black; border-radius:5px;margin-top:3px; margin-left:5px;text-align:center;">'+$parameters.eventCat+'</p><p style="width:40%; display:inline-block; padding:5px;border:1px solid black; border-radius:5px;margin-top:3px; margin-left:5px;text-align:center;">Depot area: '+depotArea($parameters.depot)+'</p><br><b><u>Duration:</u></b>'+timeConvert($parameters.durationonsite)+'<br><b><u>Scheduled dates:</u></b>  '+$parameters.scheduledTime+'<br><b><u>Proposed Dates:</u></b>  '+$parameters.AllowedDates+'<div><u><b>Customer preferences: </b></u><br>'+custPrefs($parameters.customerPrefs)+'</div><br><div style="display:inline-block; width:50%;"><b><u>Notes:</u></b><div> '+$parameters.actions+'</div><b><u>Contacts</u></b><br><div id="contactTable" style="margin-bottom: 0px;width:50%;"></div ></div><div style="display:inline-block; width:50%;" ><b><u>Competencies:</u></b><div id="skillsTable"></div></div><br><br><b><div style="display:inline-block; width:50%;"><u>Actions: </u></b><br><div id="actionsTable" ></div></div><div style="display:inline-block; width:50%;"><b><u>Equipment:</u></b>  <div id="equipTable"></div></div></div>';

            

                

                

            }

            

            


            

           

            $('body').append(tooltip);

            $(this).mouseover(function (e) {

            $(this).css('z-index', 10000);

            $(this).css('min-width', '850px');

            $('.tooltiptopicevent').fadeIn('500');

            $('.tooltiptopicevent').fadeTo('10', 1.9);

                }).mousemove(function (e) {

                    var x = e.pageY;

                   

                    if(x<625){

            $('.tooltiptopicevent').css('top', e.pageY + 10);

            $('.tooltiptopicevent').css('left', e.pageX + 20);

                    }else{

                         $('.tooltiptopicevent').css('top', e.pageY - 310);

            $('.tooltiptopicevent').css('left', e.pageX + 20);

                    } 

        });

                if($parameters.KC !== ""){

             var kcTable  = JSON.parse($parameters.KC);

    

       

        for (x=0 ;x< kcTable.length;x++) {

          if(kcTable[x].name !=="" ) { 

            var name =  kcTable[x].name;

            var kc =  kcTable[x].keyContact;

            var pp1 = ""; 

            if(!kcTable[x].phone_personal_1){

               pp1 = ""}else{pp1 = kcTable[x].phone_personal_1}

              

            var pp2 = "";

             if(!kcTable[x].phone_personal_2){

               pp2 = ""}else{pp2 = kcTable[x].phone_personal_2}

            var pw1 = "" ;

            if(!kcTable[x].phone_work_1){

               pw1 = ""}else{pw1 = kcTable[x].phone_work_1}

            var pw2 = ""; 

            if(!kcTable[x].phone_work_2){

               pw2 = ""}else{pw2 = kcTable[x].phone_work_2}

              

            

            var table = document.createElement('table');

       table.innerHTML = "<table><tr><td>" +name + "</td><td>" + kc + "</td><td>" +pp1.replace(/ /g,'') + "</td><td>" + pp2.replace(/ /g,'') + "</td><td>" + pw1.replace(/ /g,'') + "</td><td>" + pw2.replace(/ /g,'') + "</td></tr></table>";

   

   document.getElementById('contactTable').appendChild(table);

          }

    }

    

     

                } 

       

       if($parameters.equipment !== ""){

             var equipTable  = JSON.parse($parameters.equipment);

       

           

           for (x=0 ;x< equipTable.length;x++) {

            

            var quant =  equipTable[x].Quantity;

            var desc =  equipTable[x].description;

           

           var eTable = document.createElement('table');

       eTable.innerHTML = "<table><tr><td>" +quant + "</td><td>" + desc + "</td></tr></table>";

   

   document.getElementById('equipTable').appendChild(eTable);

           

          }

           

       }

       

      if($parameters.RRActionsList !== ""){

             var actTable  = JSON.parse($parameters.RRActionsList);

       

           

          for (x=0 ;x< actTable.length;x++) {

            

           

            var adesc =  actTable[x];

           

          var aTable = document.createElement('table');

      aTable.innerHTML = "<table><tr><td>" +adesc + "</td></tr></table>";

   

  document.getElementById('actionsTable').appendChild(aTable);

           

          }

           

      }

      

      if($parameters.skills !== ""){

             var skTable  = JSON.parse($parameters.skills);

       

           

          for (x=0 ;x< skTable.length;x++) {

            

           

            var sdesc =  skTable[x].description;

           

          var skillTable = document.createElement('table');

      skillTable.innerHTML = "<table><tr><td>" +sdesc + "</td></tr></table>";

   

  document.getElementById('skillsTable').appendChild(skillTable);

           

          }

           

      }

    

    


This is a snippit from my resourceRender function . This is all done in the fullCalender advanced options. Again this will have a lot of variables that do not mean anything to you but they are all extendedProps added to my resource structure and populated in the dataAction of the screen and serve as a good example of how you can expand upon your event and resource data objects.


   
resourceRender: function(renderInfo) {
    //Needed to select child elements of the resource <tr> <td>
    var length = renderInfo.el.childNodes.length;
    
// This mouse over function adds the resource id to a hidden input in preperation to be used by outsystems
renderInfo.el.addEventListener('mouseover',function(){
document.getElementById('"+hiddenInput.Id+"').value = renderInfo.resource.id;

});

//this click function clicks a hidden button which fires an OS clientEvent which uses JS to extract the above id and passes it to a
// client action which opens the popUp for editStaffLocation weblock
renderInfo.el.addEventListener('click',function(event){
//trying to capture the event bubbling which at one point was an issue
if(event.eventPhase !== 3){
   document.getElementById('"+hiddenButton2.Id+"').click();
  }
},true);

// create an element in the resource <td> to hold the icons

var iconCont = document.createElement('div');
iconCont.setAttribute('class','iconCont');
 renderInfo.el.childNodes[length-1].firstChild.append(iconCont);


// here I am calculating the tolerances for working hours (< or > than contracted).
var targetLowerTol = "+Client.targetHours + "-(("+Client.targetHours + "/100)*25);
var targetHigherTol = "+Client.targetHours + "+(("+Client.targetHours + "/100)*25);

//format date
function formatDate(date){
var options = { day: 'numeric', month: 'short', year: 'numeric'};
  if(date){
  return date.toLocaleDateString('en-US', options);
  };
}

if(renderInfo.resource.id > 0 && renderInfo.resource.extendedProps.type !== 4){
// define color according to hours worked
var alertColor = '';
var hours = renderInfo.resource.extendedProps.wtd.period_average_as_hours;
    if (hours <20) {
    alertColor= '#1a53ff'}
    else if (hours > 20 && hours < 30){
    alertColor = '#59ace3'}
    else if (hours >30 && hours < 45){
    alertColor='#70db70'}
    else if (hours > 45 && hours < 48 ){
    alertColor = '#ffc266'}
    else{
    alertColor = '#ff4d4d'}


  // Here I create a div to represent the amount of hours worked in relation to the working time directive.
  // if the staff member has opted out the div is greyed out by the opacity setting.
  // This info is in the resource object and is set in the getStaff action of the screens dataAction.

var t = document.createElement('div');
var opt_out = renderInfo.resource.extendedProps.wtd.wtd_opt_out;
var opacity = '';
if(opt_out){
opacity = '0.3';
}
else{
opacity = '1';
 t.classList.add('iBoxShadow')
}

  

        t.style.backgroundColor = alertColor;
        t.innerText = 'WTD';
        t.style.opacity = opacity;
        t.style.margin = '0px 3px 0px';
        t.style.border = '1px solid black';
        t.style.color = 'black';
        t.style.fontWeight = 'bold';
                
                t.classList.add('iBox');
                var length = renderInfo.el.childNodes.length;
                
                iconCont.append(t);

                //here I am doing practically the same thing for contracted hours

var alertColorC = '';

var hoursC = renderInfo.resource.extendedProps.contractedHours.total_combined_minutes/3600;
var hoursSched = renderInfo.resource.extendedProps.contractedHours.scheduledTotal/3600;
var hoursUc = renderInfo.resource.extendedProps.contractedHours.scheduledUnconfirmed/3600;
    

var validateHours = function(hours){
    if(!hours){
    return '0'}
    else{
    return hours.toFixed(2)
    }
  }

if (hoursC < targetLowerTol) {
alertColorC= '#1a53ff'}
else if (hoursC > targetLowerTol && hoursC < targetHigherTol ){
alertColorC = '#70db70'}
else  {
alertColorC='red'}


// create the icon for contracted hours
var C = document.createElement('div');

        C.style.backgroundColor = alertColorC;
        C.innerText = 'C';
        C.style.margin = '0px 3px 0px';
        C.style.border = '1px solid black';
        C.style.fontWeight = 'bold';
        C.style.borderRadius = '50%';
        C.style.color = 'black';               
        C.classList.add('cBox');
        C.style.opacity = '0.7';
       
   
        iconCont.append(C);



// this section creates the tooltip to display were a resources displayed travel time is calculated from and also displays their
// skill set with an arrow showing how skilled they are in relation to the skills required for the event the optimization was run on.
var score = renderInfo.resource.extendedProps.score;
 
    renderInfo.el.nextSibling.addEventListener('mouseenter', function(){
        function  myLocation(){ 
        if(renderInfo.resource.extendedProps.travel.location === undefined) {
        return ''}
        else{ return ' @ ' + renderInfo.resource.extendedProps.travel.location};
        }; 
        var tooltip = document.createElement('div');
        tooltip.className = 'tooltiptopicresource';
        tooltip.setAttribute('id', 'contentTable');
        tooltip.innerText = 'Optimized for '+ renderInfo.resource.extendedProps.travel.toJob +'\n'+' Driving time of '+                     renderInfo.resource.extendedProps.travel.text + ' from '+ renderInfo.resource.extendedProps.travel.origin  + ' ' + myLocation();                                                
        

     if(renderInfo.resource.extendedProps.travel.text){
            $('body').append(tooltip);
            $(this).mouseover(function (e) {
            $(this).css('z-index', 10000);
            $('.tooltiptopicresource').fadeIn('500');
            $('.tooltiptopicresource').fadeTo('10', 1.9);
                }).mousemove(function (e) {
            $('.tooltiptopicresource').css('top', e.pageY + 10);
            $('.tooltiptopicresource').css('left', e.pageX + 20);
    });
}
 var skillsTableData = ''
if(renderInfo.resource.extendedProps.jsonSkill !== undefined){
skillsTableData = JSON.parse(renderInfo.resource.extendedProps.jsonSkill);
}
for (x=0; x <skillsTableData.length; x++) {
var skill = skillsTableData[x].skill;
var level = skillsTableData[x].level;



function icon(equivalent){
var i = document.createElement('i');
if (  equivalent == 1) { 
                i.style.color = 'blue'  
                i.className = 'fa fa-arrow-down ';
                i.style.textShadow = '1px 1px 10px #ffffff, -1px -1px 10px #ffffff';               
                }
else if ( equivalent == 2) { 
        i.style.color = 'green'  
                i.className = 'fa fa-arrow-right ';
                i.style.textShadow = '1px 1px 10px #ffffff, -1px -1px 10px #ffffff';
                }
else { 
        i.style.color = 'pink'  
                i.className = 'fa fa-arrow-up ';
                i.style.textShadow = '1px 1px 10px #ffffff, -1px -1px 10px #ffffff';
                };
return i;

};
    var table = document.createElement('table');    
    table.innerHTML = '<table><tr><td>' +skill + '</td><td>' + level + '</td><td></td></tr></table>';
    document.getElementById('contentTable').appendChild(table);
    table.appendChild(icon(skillsTableData[x].equivalent));
};

 
});

renderInfo.el.nextSibling.addEventListener('mouseleave', function(){
 $('.tooltiptopicresource').remove();
    });


Dont forget to add some css to allow your tooltips to render


.tooltiptopicevent{
    min-width:250px; max-width:500px; border-radius: 5px;height:auto;background:#808080;color:white;position:absolute;z-index:10001;padding:10px 10px 10px 10px ; line-height: 105%;
}

.tooltiptopicresource{
    width:auto;border-radius: 5px;height:auto;background:#808080;color:white;position:absolute;z-index:10001;padding:10px 10px 10px 10px ; line-height: 105%;
}

.tooltipWtdInfo{
    width:auto;border-radius: 5px;height:auto;background:#808080;color:white;position:absolute;z-index:10001;padding:10px 10px 10px 10px ; line-height: 105%;
}

.tooltipCInfo{
    width:auto;border-radius: 5px;height:auto;background:#808080;color:white;position:absolute;z-index:10001;padding:10px 10px 10px 10px ; line-height: 105%;
}

Sorry I dont have time to strip all this down and make it more relevant to as an example but I am very busy at the moment. I hope this has served to assist you in some way.

I need help please :(

It's very good, I can do it, thank you!


Do you know why the link " +2 more" does not work? :(

Community GuidelinesBe kind and respectful, give credit to the original source of content, and search for duplicates before posting.