211
Views
32
Comments
Solved
[FullCalendar Reactive] FullCalendar reactive icons in events
fullcalendar-reactive
Reactive icon
Forge asset by Mediaweb
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);
        
    }
UserImage.jpg
Rob Marshall
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
UserImage.jpg
Rob Marshall

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);
                }
}
2020-04-07 11-21-13
Stive Sebastião

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

UserImage.jpg
Rob Marshall

You can add it into the advanced options as text.

UserImage.jpg
Rob Marshall

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

2020-04-07 11-21-13
Stive Sebastião

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

Screenshot_2.png
2020-04-07 11-21-13
Stive Sebastião
Screenshot_3.png
UserImage.jpg
Rob Marshall

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
2020-04-07 11-21-13
Stive Sebastião

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

Screenshot_5.png
UserImage.jpg
Rob Marshall

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

UserImage.jpg
Rob Marshall

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

2020-04-07 11-21-13
Stive Sebastião

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

Screenshot_4.png
UserImage.jpg
Rob Marshall

This image refers to the above in the fullCalender module.

eventRender.PNG
UserImage.jpg
Rob Marshall

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.

2020-04-07 11-21-13
Stive Sebastião

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

UserImage.jpg
Rob Marshall

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

2020-04-07 11-21-13
Stive Sebastião
2020-04-07 11-21-13
Stive Sebastião

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

UserImage.jpg
Rob Marshall

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
2020-04-07 11-21-13
Stive Sebastião

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 :\


UserImage.jpg
Rob Marshall
2020-04-07 11-21-13
Stive Sebastião

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

UserImage.jpg
Rob Marshall
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
2020-04-07 11-21-13
Stive Sebastião

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?

UserImage.jpg
Rob Marshall

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.

2020-04-07 11-21-13
Stive Sebastião

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.

UserImage.jpg
Rob Marshall

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.

UserImage.jpg
Rob Marshall

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);

           

          }

           

      }

    

    


UserImage.jpg
Rob Marshall

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();
    });


UserImage.jpg
Rob Marshall

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%;
}
UserImage.jpg
Rob Marshall

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.

2020-04-07 11-21-13
Stive Sebastião

I need help please :(

2020-04-07 11-21-13
Stive Sebastião

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.