ADF Calendar Implementation

Gabriel SiderasOracle ADF 2 Comments

One of the new projects that we are currently working on is the implementation of a complete school management solution using Oracle ADF 12c.

As one might imagine, one of the basic requirements of this project was to implement a Scheduling functionality.

The schedule implementation has many different aspects, but in this post we will focus on the ADF Calendar implementation that will be available for both the teachers and students of the school.

A Calendar is a very common, but not always easy to model and implement solution. User experience standards in terms of usability and looks are very high. Especially when the end user is a 15year old that spends most of his time in Facebook.

The ADF Calendar (af:Calendar component) with some minor tweaking can produce a very usable and functional solution with minimum effort.

In our case the tricky parts were:

  1. Having two views of the Calendar displayed at the same time. A week view and a list view. The two views must be synchronized. i.e. when the user changes the time period he is viewing in one view, it should also change in the other view.
  2. Holidays should be marked inside the calendar and displayed in another view as a popup.

 

The Model 

ADF Calendar:

In any schedule, the data model consists of:

  1. StartDateTime
  2. EndDateTime
  3. ActivityId
  4. ActivityDesciption
  5. Resources

Resources are items that need to be planned. In our case the resources are the Classrooms, the Teachers, the Lessons and the Student Groups.

The view object implementation for the calendar is described in detail in the oracle related documentation which is referenced at the end of this post

Holidays:

We wanted the holidays to be displayed grouped under each month. To achieve this we created a static view object with two attributes. The month id and month description (name)

months view object

 

 

monthsViewObject2

The Holidays view object has the following query in order to additionally retrieve a “Month” attribute which will assist in linking the Month view object to the Holidays view object

holidays View Object

SELECT Holidays.HOLIDAYS_ID,
       Holidays.BRANCH_PERIOD_ID,
       Holidays.HOLIDAYS_DATE,
       Holidays.HOLIDAYS_DESCRIPTION,
       TO_NUMBER(TO_CHAR(Holidays.HOLIDAYS_DATE,'MM')) MONTH
  FROM HOLIDAYS Holidays

A View Link was created to join Months and Holidays, using the Month number attribute. And the nested structure was added to the application module.

The View

A page fragment was created and the Calendar View Object was dropped from the data controls panel in order to create the first calendar component and create the necessary bindings.

A tree binding was additionally created for the holiday’s functionality, holding the Months view object with the nested Holidays view object.

months Binding

After these actions, the page definition looks like this:

calendar Page Definition

—– After a request I am also adding the page definition code (06/07/2017) ———

<executables>
<variableIterator id=”variables”/>
<iterator Binds=”Calendar” RangeSize=”-1″ DataControl=”BaseAppModuleDataControl” id=”CalendarIterator”
Refresh=”ifNeeded”/>
<iterator Binds=”Months” RangeSize=”25″ DataControl=”BaseAppModuleDataControl” id=”MonthsIterator”/>
</executables>
<bindings>
<action IterBinding=”CalendarIterator” id=”ExecuteWithParams” RequiresUpdateModel=”true” Action=”executeWithParams”>
<NamedData NDName=”StartDayTime” NDValue=”#{bindings.Calendar.startDate}” NDType=”java.sql.Timestamp”/>
<NamedData NDName=”EndDayTime” NDValue=”#{bindings.Calendar.endDate}” NDType=”java.sql.Timestamp”/>
<NamedData NDName=”Tz” NDValue=”#{bindings.Calendar.timeZoneId}” NDType=”java.lang.String”/>
</action>
<calendar IterBinding=”CalendarIterator” id=”Calendar” xmlns=”http://xmlns.oracle.com/adf/faces/binding”
ActionBindingName=”ExecuteWithParams” ChangeEventPolicy=”ppr”>
<nodeDefinition DefName=”gr.endynamei.model.queries.Calendar”>
<AttrNames>
<Item Type=”id” Value=”Id”/>
<Item Type=”providerId” Value=”ClassroomId”/>
<Item Type=”title” Value=”ActivityTitle”/>
<Item Type=”location” Value=”BranchName”/>
<Item Type=”startTime” Value=”StartDate”/>
<Item Type=”endTime” Value=”EndDate”/>
<Item Type=”custom” Value=”Assessed”/>
<Item Type=”custom” Value=”BranchId”/>
<Item Type=”custom” Value=”BranchPeriodId”/>
<Item Type=”custom” Value=”ClassroomId”/>
<Item Type=”custom” Value=”CoursesId”/>
<Item Type=”custom” Value=”ExtendedCourseStatus”/>
<Item Type=”custom” Value=”ExtendedCourseType”/>
<Item Type=”custom” Value=”Id”/>
<Item Type=”custom” Value=”PeriodId”/>
<Item Type=”custom” Value=”StudyGroupCoursesId”/>
<Item Type=”custom” Value=”StudyGroupId”/>
<Item Type=”custom” Value=”TutorId”/>
</AttrNames>
</nodeDefinition>
</calendar>
<tree IterBinding=”MonthsIterator” id=”Months”>
<nodeDefinition DefName=”gr.endynamei.model.queries.Months” Name=”Months0″>
<AttrNames>
<Item Value=”MonthId”/>
<Item Value=”MonthDescription”/>
</AttrNames>
<Accessors>
<Item Value=”CalendarHolidaysVO”/>
</Accessors>
</nodeDefinition>
<nodeDefinition DefName=”gr.endynamei.model.queries.CalendarHolidaysVO” Name=”Months1″>
<AttrNames>
<Item Value=”HolidaysId”/>
<Item Value=”HolidaysDate”/>
<Item Value=”HolidaysDescription”/>
</AttrNames>
</nodeDefinition>
</tree>

 

——————————————————————————–

The Page:

  • The second calendar component is created manually in the page, using the same binding.
  • A partial trigger is added from the first calendar to the second calendar component.
  • Both Calendar component properties are modified in order to display the correct views and toolbars in each calendar
  • An activityListener is added in the first calendar in order to set the current row in the Calendar iterator when a calendar cell is clicked (this is not required in this implementation, but it is useful when edit/delete operations are needed)
  • A calendarDisplayChangeListener is added to the first component in order to synchronize the two components.
  • A dataCustomizer is added to the first component in order to help “mark” dates when holidays occur.
  • A popup is created to display the holidays belonging to each month
<?xml version='1.0' encoding='UTF-8'?>
<ui:composition xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:af="http://xmlns.oracle.com/adf/faces/rich"
                xmlns:f="http://java.sun.com/jsf/core" xmlns:c="http://java.sun.com/jsp/jstl/core"
                xmlns:h="http://java.sun.com/jsf/html">
    <c:set var="viewcontrollerBundle" value="#{adfBundle['gr.endynamei.view.bundles.UIResourceBundle']}"/>
    <af:pageTemplate viewId="/fragments/template/FragmentTemplate.jsf" id="pt1">
        <f:facet name="Main">
            <af:panelBox text="#{viewcontrollerBundle['buttonText.Calendar']}" id="pb1" styleClass="noBorder"
                         showDisclosure="false">
                <f:facet name="toolbar">
                    <af:toolbar id="t5">
                    <af:button text="#{viewcontrollerBundle['buttonText.Return']}" id="b14"
                        icon="/images/icons/16x16/return.png" action="returnTaskFlow"/>
                    </af:toolbar>
                </f:facet>
                <af:panelStretchLayout id="psl1">
                    <f:facet name="bottom"/>
                    <f:facet name="center">
                    <af:panelSplitter id="ps1" orientation="horizontal" positionedFromEnd="true"
                          splitterPosition="500">
                        <f:facet name="first">
                        <af:panelStretchLayout id="psl2">
                            <f:facet name="center">
                            <af:calendar value="#{bindings.Calendar.calendarModel}" id="c1" startHour="9"
                                         toolboxLayout="all holidaysView" startDayOfWeek="mon"
                                         dateCustomizer="#{pageFlowScope.Calendar.customDates}"
                                         timeSlotsPerHour="auto" hourZoom="auto" listType="week"
                                         availableViews="day week month" styleClass="AFStretchWidth"
                                         calendarActivityListener="#{pageFlowScope.Calendar.activityListener}"
                                         calendarDisplayChangeListener="#{pageFlowScope.Calendar.calendarDisplayListener}"
                                         view="week">
                              <f:facet name="holidaysView">
                                <af:button id="b11" text="#{viewcontrollerBundle['tabTitle.HOLIDAYS']}">
                                    <af:showPopupBehavior popupId="::p1"/>
                                </af:button>
                              </f:facet>
                             </af:calendar>
                             </f:facet>
                         </af:panelStretchLayout>
                         </f:facet>
                         <f:facet name="second">
                         <af:calendar value="#{bindings.Calendar.calendarModel}" id="c11" startHour="9"
                                    startDayOfWeek="mon" listType="week" availableViews="list"
                                    partialTriggers="::c1"
                                    calendarActivityListener="#{pageFlowScope.Calendar.activityListener}"
                                    binding="#{pageFlowScope.Calendar.calendarListComp}"
                                    view="#{pageFlowScope.calendarListDuration ne null ? pageFlowScope.calendarListDuration : 'month'}"
                                    toolboxLayout="ListToolbar">
                             <f:facet name="ListToolbar">
                                <h:outputFormat value="#{viewcontrollerBundle['label.extendedTimetableCourses']}" id="ol1"
                                    style="font-weight:bold;"/>
                              </f:facet>
                          </af:calendar>
                        </f:facet>
                    </af:panelSplitter>
                 </f:facet>
              </af:panelStretchLayout>
            </af:panelBox>
            <af:popup childCreation="deferred" autoCancel="enabled" id="p1" contentDelivery="immediate">
                <af:dialog id="d1" type="ok" title="#{viewcontrollerBundle['label.yearHolidays']}" resize="on" stretchChildren="first"
                           contentWidth="500" contentHeight="500">
                    <f:facet name="buttonBar"/>
                    <af:panelDashboard id="pd1" rowHeight="150px" columns="4" dimensionsFrom="parent">
                        <af:iterator var="month" value="#{bindings.Months.treeModel}" id="ite1">
                            <af:panelBox text="#{month.bindings.MonthDescription.inputValue}" id="pb2"
                                         showDisclosure="false">
                                <f:facet name="toolbar"/>
                                <af:listView value="#{month.CalendarHolidaysVO}" var="item" id="lv1">
                                    <af:listItem id="li1">
                                        <af:panelGroupLayout layout="horizontal" id="pgl2">
                                            <f:facet name="separator">
                                                <af:spacer width="10" id="s1"/>
                                            </f:facet>
                                            <af:outputFormatted value="#{item.bindings.HolidaysDate.inputValue}"
                                                                id="of1">
                                                <af:convertDateTime pattern="#{item.bindings.HolidaysDate.hints.HolidaysDate.format}"/>
                                            </af:outputFormatted>
                                            <af:outputFormatted value="#{item.bindings.HolidaysDescription.inputValue}"
                                                                id="of2"/>
                                        </af:panelGroupLayout>
                                    </af:listItem>
                                </af:listView>
                            </af:panelBox>
                        </af:iterator>
                    </af:panelDashboard>
                </af:dialog>
            </af:popup>
        </f:facet>
    </af:pageTemplate>
</ui:composition>

The Backing Bean:

  • activityListener method to set the iterator current row on cell selection
  • calendarDisplayChangeListener method to synchronize the second calendar when the first is changed.
  • dateCustomizer method to modify the appearance dates with holidays through CSS

import gr.endynamei.view.utils.ADFUtils;
import java.util.HashMap;
import oracle.adf.view.rich.component.rich.data.RichCalendar;
import oracle.adf.view.rich.event.CalendarDisplayChangeEvent;
import oracle.adf.view.rich.component.rich.layout.RichPanelFormLayout;
import oracle.adf.view.rich.event.CalendarActivityEvent;
import oracle.adf.view.rich.model.CalendarActivity;
import java.util.Date;

import java.util.Locale;
import java.util.TimeZone;
import oracle.adf.view.rich.util.DateCustomizer;
import oracle.jbo.Key;
import oracle.jbo.Row;
import oracle.jbo.RowIterator;
import oracle.jbo.RowSetIterator;

public class CustomCalendar{
 private RichPanelFormLayout createPanelFormLayout;
 private RichPanelFormLayout updatePanelFormLayout;
 private RichCalendar calendarListComp;
 private MyDateCustomizer customDates;

 public CustomCalendar() {
 super();
 }

 public void activityListener(CalendarActivityEvent calendarActivityEvent) {
 CalendarActivity activity = calendarActivityEvent.getCalendarActivity();
 if (activity != null) {
 RowSetIterator rsi = ADFUtils.findIterator("CalendarIterator").getRowSetIterator();
 Key key = new Key(new Object[] { activity.getId() });
 Row row = rsi.findByKey(key, 1)[0];
 rsi.setCurrentRow(row);
 }
 }

public void calendarDisplayListener(CalendarDisplayChangeEvent calendarDisplayChangeEvent) {
 java.util.Date newDate = calendarDisplayChangeEvent.getNewActiveDay();
 if(calendarDisplayChangeEvent.getNewView()!=null){
 calendarListComp.setListType(calendarDisplayChangeEvent.getNewView());
 }
 if(newDate!=null){
 calendarListComp.setActiveDay(calendarDisplayChangeEvent.getNewActiveDay());
 }
 }

 public MyDateCustomizer getCustomDates() {
 if(customDates==null){
 customDates = new MyDateCustomizer();
 for (Row r : ADFUtils.findIterator("MonthsIterator").getAllRowsInRange()) {
 Row []rows = ((RowIterator)r.getAttribute("CalendarHolidaysVO")).getAllRowsInRange();
 for(int i=0;i<rows.length;i++){
 oracle.jbo.domain.Date hDate = (oracle.jbo.domain.Date)rows[i].getAttribute("HolidaysDate");
 customDates.holidays.put(hDate.getValue(),rows[i].getAttribute("HolidaysDescription"));
 }

 }
 }
 return customDates;
 }

 private class MyDateCustomizer extends DateCustomizer{
 HashMap holidays = new HashMap();
 public String format(Date date, String key, Locale locale, TimeZone tz)
 {
 if ("af|calendar::month-grid-cell-header-misc".equals(key)||
 "af|calendar::day-header-row-misc".equals(key)||
 "af|calendar::week-header-cell-misc".equals(key)){
 return holidays.get(date)!=null?holidays.get(date).toString():null;
 }
 return null;
 }
 }

 public void setCreatePanelFormLayout(RichPanelFormLayout createPanelFormLayout) {
 this.createPanelFormLayout = createPanelFormLayout;
 }

public RichPanelFormLayout getCreatePanelFormLayout() {
 return createPanelFormLayout;
 }

public void setUpdatePanelFormLayout(RichPanelFormLayout updatePanelFormLayout) {
 this.updatePanelFormLayout = updatePanelFormLayout;
 }

public RichPanelFormLayout getUpdatePanelFormLayout() {
 return updatePanelFormLayout;
 }

public void setCalendarListComp(RichCalendar calendarListComp) {
 this.calendarListComp = calendarListComp;
 }

public RichCalendar getCalendarListComp() {
 return calendarListComp;
 }

}

The CSS:


/**CALENDAR CSS**/
af|calendar::month-grid-cell-header-misc
{
color: Red;
}
af|calendar::day-header-row-misc
{
color: Red;
}

af|calendar::week-header-cell-misc
{
color: Red;
}

The resulting layout can be seen in the following screeshots:

ADF Calendar

ADF Calendar

 

Holidays

Holidays Popup

 

Most af:Calendar related aspects are documented by oracle here:

Creating a Calendar Application

Working with ADF Faces Calendar Component

ADF RichClient Demo Calendar

A very nice blog post about af:Calendar can be found here: http://www.gebs.ro/blog/oracle/oracle-adf-calendar-step-by-step-implementation/

Subscribe
Notify of
2 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
dileep
dileep
July 5, 2017 09:31

hi can you share pagedefiniton of the calendar view page