Primefaces Calendar Customization

Goal

Customize a primefaces calendar to present holidays differently

Description

A more or less common requirement in the projects I have been working on is to present a calendar with some specific days shown differently, namely holidays and weekends.

This recipe explains how to do that using a Primefaces calendar component.

How to

This recipe, which was extracted from the code developed in Linkare’s timetracking system – Clockare – is about a calendar that displays holidays and weekends in pink, and days containing hours reported in bold.

The recipe consists basically on the following steps:

  1. Create a Javascript function to be invoked by the calendar component that will return the days that need to be presented differently
  2. A backing bean method that calculates if the day is a special type of day or not
  3. Invoke the Javascript function on the calendar component, in the beforeShowDay event
  4. Create the corresponding CSS classes that will display the days differently

The javascript function that will be called by the calendar component returns an array with two elements:

  1. true or false to tell if the day should be or not displayed in the calendar
  2. empty or a specific CSS class name so that the day may be displayed differently
<!-- 
     Unfortunately, this code cannot be extracted to a .js 
     file because it needs the call to the function join! 
-->
<script type="text/javascript">
  function markHolidaysAndReportedDays(date) {

    var holidays = new Array(#{fn:join(consultantBean.getHolidays(), ',')});
    for (var i = 0; i < holidays.length; i++) {
      if(date.getTime() == new Date(holidays[i]).getTime()) {
        return [true, 'holiday'];
      }
    }
    var reportedDays = new Array(#{fn:join(consultantBean.getReportedDays(), ',')});
    for (var i = 0; i < reportedDays.length; i++) {
      if(date.getTime() == new Date(reportedDays[i]).getTime()) {
        return [true, 'reported-day'];
      }
    }
    return [true, ''];
  };
</script>

Notice that the character ‘<‘ needs to be written as ‘&#60 ;’ because the JS function is defined inside a Facelet page.

The bean component that contains the methods that are invoked from the Javascript function, namely the methods getHolidays() (the logic to check for holidays is delegated to the CalendarHelper class that gets its information from an integration with the open source project Jollydays and getReportedDays(), to check if the day is a “special” type of day or not:

@Named("consultantBean")
@Stateful
@LocalBean
@ViewAccessScoped
@TransactionAttribute(TransactionAttributeType.SUPPORTS)
public class ConsultantBean extends AbstractTimetrackBean {
  ...
  private static final String QUOTE = "'";
  private static final String TIME_FORMAT = "T00:00:00";
  private static final String YEAR_MONTH_DAY_FORMAT = "yyyy-MM-dd";

  public String[] getHolidays() {
    if (this.holidays == null) {
        final Set<LocalDate> monthWorkingDays = CalendarHelper.
                                                getWorkingDays(user, getMonth());
        DateTime current = month.getStart();
        final List<String> result = new ArrayList<>(monthWorkingDays.size());
        do {
        final LocalDate currentDate = current.toDateMidnight().toLocalDate();
        if (!monthWorkingDays.contains(currentDate)) {
            addDayToResult(currentDate, result);
        }
        current = current.plusDays(1);
        } while (!current.isAfter(month.getEnd()));
        this.holidays = (String[]) result.toArray(new String[result.size()]);
    }
    return holidays;
  }

  public String[] getReportedDays() {
    if (this.reportedDays == null) {
        final List<Date> monthEntries = getTimesheetEntryDAO().
                                        findTimesheetEntryDatesIn(user, 
                                        month.getStart().toDate(), 
                                        month.getEnd().toDate());
        final List<String> result = new ArrayList<>(monthEntries.size());
        for (final Date entry : monthEntries) {
        addDayToResult(new LocalDate(entry), result);
        }
        this.reportedDays = (String[]) result.toArray(new String[result.size()]);
    }
    return reportedDays;
  }

  private void addDayToResult(final LocalDate date, final List<String> result) {
    result.add(QUOTE + date.toString(YEAR_MONTH_DAY_FORMAT) + TIME_FORMAT + QUOTE);
  }
  ...
}

The calendar component that displays the calendar:

<p:calendar value="#{consultantBean.currentDay}" mode="inline"
  pattern="#{applicationConfigurations.datePattern}"
  navigator="true" effect="slide"
  locale="#{efragmentLocaleBean.locale}"
  timeZone="#{applicationConfigurations.timezone}"
  readonlyInput="true" beforeShowDay="markHolidaysAndReportedDays"
  yearRange="#{applicationConfigurations.yearRange}"
  id="monthCalendar" widgetVar="monthCalendar"
  showOtherMonths="true">
  <p:ajax event="dateSelect" update="activityPanel,calendarPanel" global="true"
    oncomplete="Efragment.scrollTo('day' + monthCalendar.getDate().getTime());
                Efragment.affixWithOffsetOnTop('.span3.floating-panel', 165)"
    process="@this" listener="#{consultantBean.refreshData()}" />
</p:calendar>

The backing bean applicationConfigurations contains general application settings, namely date format, the application timezone, etc. – the code is not shown because it is not relevant for this recipe.

The JS methods in Efragment are also not relevant to the recipe so, they are not shown. However, they simply scroll the page to the selected day after clicking in any day on the calendar.

And finally, we specify the corresponding CSS classes in the page’s css stylesheet:

.ui-datepicker td.reported-day a {
  font-weight: bold;
}
.holiday {
  color: red;
}

Explanations

I guess no further explanations are necessary here as they were made in place in the previous section… However, here’s the resulting page (in bold the days where there are registered activity and in pink the days that are weekend or holidays):

new-clockare-printscreen

Advertisements

13 comments

  1. hi, in this part var holidays = new Array(#{fn:join(consultantBean.getHolidays(), ‘,’)});, my first date in array is ’2014-02-07′ but i put alert to show de de hoidays[o] print 2005 = 2014-02-07…why this happen?? tank you.

      1. this is my js in xhtml
        ============================================================

        //<![CDATA[
        function FuncaoJavascript(date) {

        var arrayDeDatasComAnexos = new Array(#{fn:join(importCplanBean.stringDates,',')});

        for (var i = 0; i

        =================================================================
        this is the value in array
        var arrayDeDatasComAnexos = new Array(‘2013/11/07 T00:00:00′,’2013/10/21 T00:00:00’,….

        in the alert show ‘NaN’ …

        ============================================================
        this is the classe DAO

        here return the String….in this format ‘yyyy-MM-DD’.

        while(rs.next()){
        String data = “”;
        String teste =””;
        data = rs.getString(“DT_IMPORT”);
        data = QUOTE + data + TIME_FORMAT + QUOTE;
        data = data.replace(“-“, “/”);
        list.add(data);

        }
        ==============================================================

    1. var arrayDeDatasComAnexos = new Array(#{fn:join(importCplanBean.stringDates,’,’)});

      for (var i = 0; i < arrayDeDatasComAnexos.length; i++) {

      var d = new Date(1390701600000);
      alert(d.constructor);

      if(date.getTime() === new Date(arrayDeDatasComAnexos[i]).getTime()) {
      return [true, 'TemBackLog'];
      }else{
      return [true, 'naoTemBackLog'];
      }
      }

    1. My script:

      function markHolidaysAndReportedDays(date) {

      var holidays = new Array(#{fn:join(posicionAbc.afterDialogOpenDisableDates(), ‘,’)});
      for (var i = 0; i < holidays.length; i++) {
      if (date.getTime() === new Date(holidays[i]).getTime()) {
      return [true, ‘holiday’];
      }
      }
      return [true, ”];
      };

      1. You need to include the JSTL functions taglib at the beginning of the page, like this: xmlns:fn=”http://java.sun.com/jsp/jstl/functions”

  2. I like the example but can you customize it to change calendar date from 5/1/2015 to 121 (day of year)?

    1. Hello Bruce. I am not sure about what your requirement is but I believe you could do the other way, i.e., instead of adapting the example to use a date such as the 121th day of the year, convert that 121th day of the year to a date such as dd/MM/yyyy (I think you have to use that kind of format in order for this to work – didn’t test it, though…). Hope it helps.

      Best,

      PZ

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s