Building & Designing an Interactive Multi-Festival Calendar in Oracle APEX with a Deep Dive into Diwali & Christmas

 Introduction

Oracle APEX is widely used for building business applications, but it can also be leveraged to create visually engaging and interactive user experiences.

In this blog, we explore how to design and build a multi-festival calendar solution in Oracle APEX, along with detailed implementations for individual festivals. We start with a unified multi-festival calendar that combines multiple celebrations into a single dynamic interface, and then dive deeper into specific patterns through a highlight-based Diwali calendar and a countdown-style Christmas calendar.

Using simple technologies like HTML, CSS, JavaScript, and Dynamic Actions, we create rich, interactive UI components without relying on plugins or complex backend logic.

This approach demonstrates how Oracle APEX can go beyond traditional use cases and be used to build modern, user-centric, and visually compelling applications.

Step by Step Approaches 

PART 1 - Implementing the Interactive Multi-Festival Calendar in Oracle APEX

In this part, we integrate Diwali, Christmas, Pongal, and Ramadan into one dynamic interface, allowing users to switch between festivals, view country-specific holidays, and interact with detailed information through tooltips and panels.

This section focuses on creating a scalable, reusable, and fully interactive calendar architecture using Dynamic Actions, JavaScript, and CSS in Oracle APEX.

🧱 Step 1: Create Page Items

Item 1 - P4_FESTIVAL

Create a Select List named P4_FESTIVAL. Set List of Values → Type: Static Values. Add these 5 entries:

  Display Value                Return Value             Calendar Shows
🎄 Christmas 2025  CHRISTMAS                        December 2025
🪔 Diwali 2025          DIWALI                                October + November 2025
🌾 Pongal 2026          PONGAL                       January 2026
☪️ Ramadan 2025  RAMADAN2025               February + March 2025
☪️ Ramadan 2026  RAMADAN2026               February + March 2026

Item 2 - P4_COUNTRY

Create a Select List named P4_COUNTRY. Add these values:

Display Value   Return Value
All Countries            ALL
🇮🇳 India                    IN
🇺🇸 USA                    US
🇬🇧 UK                    GB
🇸🇬 Singapore            SG
🇦🇺 Australia           AU
🇨🇦 Canada           CA
🇲🇾 Malaysia           MY
🇱🇰 Sri Lanka           LK

Item 3 - P4_RAMADAN_YEAR

Create a Select List named P4_RAMADAN_YEAR. This only shows when Ramadan is selected:

Display Value                     Return Value
2025 (Feb 28 - Mar 30)         2025
2026 (Feb 17 - Mar 19)         2026




🧱 Step 2: Add Page Inline CSS

Go to Page → CSS → Inline. This CSS covers the collapsible selector, all 4 festival sidebar themes, the calendar grid, all festival day glows, the hover tooltip, and the detail panel.

Section A - Selector Region + Container + Sidebar

/* Collapsible selector bar */
#festival_selector_reg{background-color:#1a0a2e!important;border:1.5px solid #ff6b1a!important;border-radius:10px}
#festival_selector_reg .t-Region-header{background-color:#1a0a2e!important}
#festival_selector_reg .t-Region-headerTitle,#festival_selector_reg .t-Icon{color:#ffd700!important;font-family:'Philosopher',serif}
/* Main container */
.fc-container{display:flex;min-height:580px;border-radius:14px;overflow:hidden;box-shadow:0 16px 60px rgba(0,0,0,0.5)}
/* Sidebar */
.fc-sidebar{width:32%;padding:44px 28px;position:relative;overflow:hidden;transition:background 0.5s}
.fc-sidebar::before{content:'';position:absolute;inset:0;background-image:radial-gradient(circle,rgba(255,215,0,0.08) 1px,transparent 1px);background-size:24px 24px;pointer-events:none}
/* 4 Festival Sidebar Themes */
.fc-sidebar.theme-christmas{background:linear-gradient(160deg,#0d2818,#1a3d2b,#0a1f12)}
.fc-sidebar.theme-diwali   {background:linear-gradient(160deg,#0a0318,#1a0a2e,#0d0520)}
.fc-sidebar.theme-pongal   {background:linear-gradient(160deg,#1a0f00,#3d2200,#1f1000)}
.fc-sidebar.theme-ramadan  {background:linear-gradient(160deg,#050a18,#0a1530,#060e22)}
.fc-festival-icon{font-size:3rem;display:block;margin-bottom:10px;animation:iconGlow 2s ease-in-out infinite}
@keyframes iconGlow{0%,100%{filter:drop-shadow(0 0 16px rgba(255,165,0,0.8))}50%{filter:drop-shadow(0 0 30px rgba(255,215,0,1))}}
.fc-title{font-family:'Philosopher',serif;font-size:2.2rem;font-weight:700;letter-spacing:5px;color:#ffd700;text-shadow:0 2px 18px rgba(255,215,0,0.4)}
.fc-tagline{font-size:0.75rem;color:rgba(255,255,255,0.4);letter-spacing:3px;text-transform:uppercase;margin-top:4px}
.fc-quote-section{border-left:3px solid #ff6b1a;padding-left:14px;margin-top:24px}
.fc-quote{font-family:'Philosopher',serif;font-size:1rem;color:#ffd700;font-style:italic}
.fc-country-label{font-size:0.8rem;color:#ff9b6b;margin-top:12px;display:block;font-weight:600}
.fc-ornaments{font-size:1.2rem;margin-top:20px;letter-spacing:5px}
/* Calendar right panel */
.fc-calendar-main{width:68%;padding:24px 20px;background:#0f0a1a;overflow-y:auto}
.fc-month-hdr{font-family:'Philosopher',serif;font-size:1.05rem;color:#ff9b6b;margin:14px 0 8px;padding-bottom:5px;border-bottom:1px solid rgba(255,107,26,0.22)}
.fc-weekdays{display:grid;grid-template-columns:repeat(7,1fr);gap:3px;margin-bottom:3px}
.fc-wd{text-align:center;font-family:'JetBrains Mono',monospace;font-size:0.58rem;color:rgba(255,215,0,0.45);text-transform:uppercase;padding:3px 0}
.fc-grid{display:grid;grid-template-columns:repeat(7,1fr);gap:3px;margin-bottom:16px}
.fc-day{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:7px 2px;border-radius:7px;min-height:48px;background:rgba(255,255,255,0.03);border:1px solid rgba(255,255,255,0.05);cursor:default;position:relative;transition:transform 0.15s}
.fc-day.empty{background:transparent;border:none;pointer-events:none}
.fc-day.has-info{cursor:pointer}
.fc-day.has-info:hover{transform:scale(1.08);z-index:10}
.fc-day-num{font-size:0.92rem;font-weight:600;color:rgba(240,220,180,0.85);line-height:1}
.fc-day-lbl{font-size:0.48rem;color:rgba(255,255,255,0.3);margin-top:3px;text-align:center}
.fc-day.day-past{opacity:0.28}
.fc-day.day-today{background:rgba(46,204,113,0.18)!important;border-color:#2ecc71!important;box-shadow:0 0 10px rgba(46,204,113,0.3)}
.fc-day.day-today .fc-day-num{color:#2ecc71}

Section B - Festival Day Highlights (all 4 festivals)

/* Christmas */

.fc-day.xmas-main{background:linear-gradient(135deg,rgba(192,57,43,0.28),rgba(231,76,60,0.18))!important;border-color:#f1c40f!important;border-width:2px!important;animation:xmasGlow 2s ease-in-out infinite}

.fc-day.xmas-main .fc-day-num{color:#f1c40f}

@keyframes xmasGlow{0%,100%{box-shadow:0 0 20px rgba(241,196,15,0.55)}50%{box-shadow:0 0 38px rgba(241,196,15,0.9)}}

/* Diwali */

.fc-day.dw-main {background:linear-gradient(135deg,rgba(255,69,0,0.28),rgba(255,107,26,0.18))!important;border-color:#ff4500!important;border-width:2px!important;animation:diwaliGlow 1.8s ease-in-out infinite}

.fc-day.dw-main .fc-day-num{color:#ff6b1a}

@keyframes diwaliGlow{0%,100%{box-shadow:0 0 18px rgba(255,69,0,0.6)}50%{box-shadow:0 0 36px rgba(255,107,26,0.95)}}

.fc-day.dw-dhant{background:rgba(255,215,0,0.14)!important;border-color:#ffd700!important;box-shadow:0 0 12px rgba(255,215,0,0.4)}

.fc-day.dw-dhant .fc-day-num{color:#ffd700}

.fc-day.dw-narak{background:rgba(255,170,0,0.14)!important;border-color:#ffaa00!important;box-shadow:0 0 10px rgba(255,170,0,0.4)}

.fc-day.dw-narak .fc-day-num{color:#ffaa00}

.fc-day.dw-govar{background:rgba(255,107,26,0.14)!important;border-color:#ff6b1a!important;box-shadow:0 0 10px rgba(255,107,26,0.35)}

.fc-day.dw-govar .fc-day-num{color:#ff9b6b}

.fc-day.dw-bhai {background:rgba(155,77,255,0.14)!important;border-color:#9b4dff!important;box-shadow:0 0 10px rgba(155,77,255,0.35)}

.fc-day.dw-bhai .fc-day-num{color:#c084ff}

/* Pongal */

.fc-day.pg-main  {background:linear-gradient(135deg,rgba(255,200,0,0.28),rgba(255,150,0,0.18))!important;border-color:#ffc800!important;border-width:2px!important;animation:pongalGlow 2s ease-in-out infinite}

.fc-day.pg-main .fc-day-num{color:#ffc800}

@keyframes pongalGlow{0%,100%{box-shadow:0 0 18px rgba(255,200,0,0.55)}50%{box-shadow:0 0 36px rgba(255,180,0,0.9)}}

.fc-day.pg-bhogi {background:rgba(255,120,0,0.18)!important;border-color:#ff7800!important;box-shadow:0 0 12px rgba(255,120,0,0.45)}

.fc-day.pg-bhogi .fc-day-num{color:#ff9840}

.fc-day.pg-mattu {background:rgba(80,160,40,0.18)!important;border-color:#50a028!important;box-shadow:0 0 10px rgba(80,160,40,0.4)}

.fc-day.pg-mattu .fc-day-num{color:#80d050}

.fc-day.pg-kaanum{background:rgba(0,180,160,0.16)!important;border-color:#00b4a0!important;box-shadow:0 0 10px rgba(0,180,160,0.35)}

.fc-day.pg-kaanum .fc-day-num{color:#40d4c0}

/* Ramadan */

.fc-day.rm-start{background:rgba(30,80,180,0.20)!important;border-color:#4a90d9!important;box-shadow:0 0 12px rgba(74,144,217,0.45)}

.fc-day.rm-start .fc-day-num{color:#7ab8f0}

.fc-day.rm-qadr {background:linear-gradient(135deg,rgba(255,215,0,0.22),rgba(200,160,0,0.14))!important;border-color:#ffd700!important;border-width:2px!important;animation:qadrGlow 2s ease-in-out infinite}

.fc-day.rm-qadr .fc-day-num{color:#ffd700}

@keyframes qadrGlow{0%,100%{box-shadow:0 0 18px rgba(255,215,0,0.5)}50%{box-shadow:0 0 36px rgba(255,215,0,0.9)}}

.fc-day.rm-eid  {background:linear-gradient(135deg,rgba(0,180,80,0.25),rgba(0,140,60,0.15))!important;border-color:#00c860!important;border-width:2px!important;animation:eidGlow 2s ease-in-out infinite}

.fc-day.rm-eid .fc-day-num{color:#00e870}

@keyframes eidGlow{0%,100%{box-shadow:0 0 18px rgba(0,180,80,0.5)}50%{box-shadow:0 0 36px rgba(0,200,90,0.9)}}

Section C - Tooltip + Detail Panel + Badges

* Hover Tooltip */
.fc-tooltip{position:fixed;z-index:9999;background:#1a1030;border:1px solid #ff6b1a;border-radius:10px;padding:14px 16px;min-width:240px;max-width:300px;box-shadow:0 8px 32px rgba(255,107,26,0.3);pointer-events:none;display:none;font-family:'Poppins',sans-serif;font-size:0.82rem}
.fc-tooltip.visible{display:block}
.fc-tooltip-title{font-family:'Philosopher',serif;font-size:0.95rem;color:#ffd700;margin-bottom:10px;border-bottom:1px solid rgba(255,107,26,0.3);padding-bottom:6px}
.fc-tooltip-row{display:flex;align-items:center;justify-content:space-between;padding:4px 0;border-bottom:1px solid rgba(255,255,255,0.05)}
.fc-tooltip-row:last-child{border-bottom:none}
.fc-tooltip-country{display:flex;align-items:center;gap:6px;color:#e8dfc8;font-size:0.8rem}
.fc-tip-footer{margin-top:8px;font-size:0.7rem;color:rgba(255,165,0,0.6);text-align:center;font-style:italic}

/* Leave Badges */
.badge{font-size:0.62rem;padding:2px 7px;border-radius:10px;font-weight:600;white-space:nowrap}
.badge-public    {background:rgba(46,204,113,0.2);color:#2ecc71;border:1px solid rgba(46,204,113,0.4)}
.badge-restricted{background:rgba(241,196,15,0.2);color:#f1c40f;border:1px solid rgba(241,196,15,0.4)}
.badge-optional  {background:rgba(93,173,226,0.2);color:#5dade2;border:1px solid rgba(93,173,226,0.4)}

/* Detail Panel */
#fc-detail-panel{margin-top:20px;border-radius:12px;background:#120820;border:1px solid rgba(255,107,26,0.3);overflow:hidden;display:none}
#fc-detail-panel.visible{display:block}
.dp-header{background:linear-gradient(90deg,#1a0a2e,#2d1054);padding:14px 20px;display:flex;align-items:center;justify-content:space-between}
.dp-title{font-family:'Philosopher',serif;font-size:1.1rem;color:#ffd700;font-weight:700}
.dp-close{background:none;border:none;color:rgba(255,255,255,0.4);font-size:1.2rem;cursor:pointer;padding:0 4px}
.dp-body{padding:16px 20px}
.dp-row{display:grid;grid-template-columns:1.8fr 2fr 1fr 0.8fr;align-items:center;gap:10px;padding:10px 0;border-bottom:1px solid rgba(255,255,255,0.05);font-size:0.86rem}
.dp-row:last-child{border-bottom:none}
.dp-row-hdr{display:grid;grid-template-columns:1.8fr 2fr 1fr 0.8fr;gap:10px;padding:0 0 8px;border-bottom:1px solid rgba(255,165,0,0.2);margin-bottom:4px}
.dp-row-hdr span{font-family:'JetBrains Mono',monospace;font-size:0.62rem;letter-spacing:1.5px;text-transform:uppercase;color:rgba(255,165,0,0.55)}
.dp-country-name{display:flex;align-items:center;gap:7px;color:#e8dfc8;font-weight:500}
.dp-holiday-name{color:rgba(240,220,180,0.7);font-size:0.82rem}
.dp-days{color:#ff9b6b;font-weight:600;text-align:center}

🧱 Step 3: Add the Static HTML Region

Create a Static Content region. Set Template: Blank with Attributes. Paste this HTML into Source:

<!-- Floating hover tooltip (hidden by default) -->

<div id="fc-tooltip" class="fc-tooltip"></div>

<!-- Main calendar container -->

<div class="fc-container">

  <!-- Sidebar -->

  <div class="fc-sidebar theme-diwali" id="fc-sidebar">

    <span class="fc-festival-icon" id="fc-icon">🪔</span>

    <h1 class="fc-title" id="fc-title">DIWALI</h1>

    <div class="fc-tagline" id="fc-tagline">Festival of Lights 2025</div>

    <div class="fc-quote-section">

      <p class="fc-quote" id="fc-quote">

        दीपावली की शुभकामनाएं

      </p>

      <span class="fc-country-label" id="fc-country-label">

        🌍 Select festival above

      </span>

    </div>

    <div class="fc-ornaments" id="fc-ornaments">🪔 ✨ 🎆</div>

  </div>

  <!-- Calendar grids (JS fills these) -->

  <div class="fc-calendar-main">

    <div id="fc-months"></div>

  </div>

</div>

<!-- Click Detail Panel (hidden until a festival day is clicked) -->

<div id="fc-detail-panel">

  <div class="dp-header">

    <div class="dp-title" id="dp-title">Country Leave Details</div>

    <button class="dp-close"

      onclick="document.getElementById('fc-detail-panel').className=''">✕</button>

  </div>

  <div class="dp-body" id="dp-body"></div>

</div>

🧱 Step 4: Create the 4 Dynamic Actions

All 4 DAs go on P4_FESTIVAL → Change with Fire on Initialization: YES and Action type Execute JavaScript Code.

DA1 - Festival Data | Sequence 10

Stores all country arrays as window globals so the other DAs can access them:

window.XMAS_DATA = [

  ["🇮🇳","India","Christmas Day","Restricted","1 day"],

  ["🇺🇸","USA","Christmas Day","Public","1 day"],

  ["🇬🇧","UK","Christmas Day + Boxing","Public","2 days"],

  ["🇸🇬","Singapore","Christmas Day","Public","1 day"],

  ["🇦🇺","Australia","Christmas Day + Boxing","Public","2 days"],

  ["🇨🇦","Canada","Christmas Day + Boxing","Public","2 days"],

  ["🇩🇪","Germany","1st & 2nd Christmas Day","Public","2 days"],

  ["🇵🇭","Philippines","Christmas Day","Public","1 day"]

];

window.DIWALI_DATA = [

  ["🇮🇳","India","Diwali / Deepawali","Public","1 day"],

  ["🇸🇬","Singapore","Deepavali","Public","1 day"],

  ["🇬🇧","UK","Diwali","Optional","1 day"],

  ["🇺🇸","USA","Diwali (recognized)","Optional","1 day"],

  ["🇨🇦","Canada","Diwali","Optional","1 day"],

  ["🇦🇺","Australia","Diwali","Optional","1 day"]

];

window.PONGAL_DATA = [

  ["🇮🇳","India (Tamil Nadu)","Thai Pongal","Public","4 days"],

  ["🇱🇰","Sri Lanka","Thai Pongal","Public","1 day"],

  ["🇸🇬","Singapore","Pongal","Optional","1 day"],

  ["🇲🇾","Malaysia","Thai Pongal","Public","1 day"],

  ["🇬🇧","UK","Pongal","Optional","1 day"],

  ["🇺🇸","USA","Pongal","Optional","1 day"]

];

window.RAMADAN_DATA = [

  ["🇮🇳","India","Ramadan / Ramzan","Public","1 day"],

  ["🇸🇦","Saudi Arabia","Ramadan","Public","30 days"],

  ["🇦🇪","UAE","Ramadan","Public","30 days"],

  ["🇵🇰","Pakistan","Ramadan","Public","1 day"],

  ["🇬🇧","UK","Ramadan","Optional","1 day"],

  ["🇺🇸","USA","Ramadan","Optional","1 day"],

  ["🇸🇬","Singapore","Ramadan / Hari Raya","Public","1 day"],

  ["🇲🇾","Malaysia","Ramadan / Hari Raya","Public","1 day"]

];

DA2 - Festival Config | Sequence 20

Builds window.FC config object based on selected festival. Updates sidebar text:

var fv=apex.item("P4_FESTIVAL").getValue();

var mEl=document.getElementById("fc-months");

if(!mEl||!fv){return;}

mEl.innerHTML="";

var XD=window.XMAS_DATA||[];

var DD=window.DIWALI_DATA||[];

var PD=window.PONGAL_DATA||[];

var RD=window.RAMADAN_DATA||[];

var isX=(fv==="CHRISTMAS");

var isDW=(fv==="DIWALI");

var isPG=(fv==="PONGAL");

var isR25=(fv==="RAMADAN2025");

var isR26=(fv==="RAMADAN2026");

if(isX){

  window.FC={

    icon:"🎄",title:"CHRISTMAS",tagline:"Advent Calendar 2025",

    quote:"Peace on earth, goodwill to all.",

    ornaments:"🎁 🔔 ⭐ 🦌 🎅",

    sideClass:"fc-sidebar theme-christmas",

    countries:XD,months:[[2025,11]],

    festDates:{"2025-12-25":{cls:"xmas-main",lbl:"Christmas",data:XD}}

  };

} else if(isDW){

  window.FC={

    icon:"🪔",title:"DIWALI",tagline:"Festival of Lights 2025",

    quote:"Happy Diwali!",

    ornaments:"🪔 ✨ 🎆 🪷 🎇",

    sideClass:"fc-sidebar theme-diwali",

    countries:DD,months:[[2025,9],[2025,10]],

    festDates:{

      "2025-10-18":{cls:"dw-dhant",lbl:"Dhanteras",data:DD},

      "2025-10-19":{cls:"dw-narak",lbl:"Narak Chat.",data:DD},

      "2025-10-20":{cls:"dw-main",lbl:"Diwali",data:DD},

      "2025-10-21":{cls:"dw-govar",lbl:"Govardhan",data:DD},

      "2025-10-22":{cls:"dw-bhai",lbl:"Bhai Dooj",data:DD}

    }

  };

} else if(isPG){

  window.FC={

    icon:"🌾",title:"PONGAL",tagline:"Harvest Festival 2026",

    quote:"Pongalo Pongal!",

    ornaments:"🌾 🐄 ☀️ 🪔 🌺",

    sideClass:"fc-sidebar theme-pongal",

    countries:PD,months:[[2026,0]],

    festDates:{

      "2026-01-13":{cls:"pg-bhogi",lbl:"Bhogi",data:PD},

      "2026-01-14":{cls:"pg-main",lbl:"Thai Pongal",data:PD},

      "2026-01-15":{cls:"pg-mattu",lbl:"Mattu Pongal",data:PD},

      "2026-01-16":{cls:"pg-kaanum",lbl:"Kaanum Pongal",data:PD}

    }

  };

} else if(isR25){

  window.FC={

    icon:"☪️",title:"RAMADAN",tagline:"Ramadan 1446H - 2025",

    quote:"Ramadan Mubarak!",

    ornaments:"☪️ 🌙 ✨ 🕌 🌟",

    sideClass:"fc-sidebar theme-ramadan",

    countries:RD,months:[[2025,1],[2025,2]],

    festDates:{

      "2025-02-28":{cls:"rm-start",lbl:"Ramadan Start",data:RD},

      "2025-03-27":{cls:"rm-qadr",lbl:"Lailat al-Qadr",data:RD},

      "2025-03-30":{cls:"rm-eid",lbl:"Eid al-Fitr",data:RD}

    }

  };

} else {

  window.FC={

    icon:"☪️",title:"RAMADAN",tagline:"Ramadan 1447H - 2026",

    quote:"Ramadan Kareem!",

    ornaments:"☪️ 🌙 ✨ 🕌 🌟",

    sideClass:"fc-sidebar theme-ramadan",

    countries:RD,months:[[2026,1],[2026,2]],

    festDates:{

      "2026-02-17":{cls:"rm-start",lbl:"Ramadan Start",data:RD},

      "2026-03-15":{cls:"rm-qadr",lbl:"Lailat al-Qadr",data:RD},

      "2026-03-19":{cls:"rm-eid",lbl:"Eid al-Fitr",data:RD}

    }

  };

}

var sb=document.getElementById("fc-sidebar");

if(sb){sb.className=window.FC.sideClass;}

function sT(id,v){var e=document.getElementById(id);if(e){e.textContent=v;}}

sT("fc-icon",window.FC.icon);

sT("fc-title",window.FC.title);

sT("fc-tagline",window.FC.tagline);

sT("fc-quote",window.FC.quote);

sT("fc-ornaments",window.FC.ornaments);

var t=new Date();t.setHours(0,0,0,0);

window.FC_TODAY=t;

window.toKey=function(d){

  return d.getFullYear()+"-"+

  String(d.getMonth()+1).padStart(2,"0")+"-"+

  String(d.getDate()).padStart(2,"0");

};

window.FC_TODAYKEY=window.toKey(t);

var cSel=document.getElementById("P4_COUNTRY");

var cVal=apex.item("P4_COUNTRY").getValue();

var cTxt="";

if(cSel&&cVal&&cVal!=="ALL"){

  cTxt=cSel.options[cSel.selectedIndex].text;

}else{

  cTxt="🌍 "+window.FC.countries.length+" Countries";

}

sT("fc-country-label",cTxt);

window.FC_COUNTRY=cVal;

DA3 - Festival Helpers | Sequence 30

Defines tooltip and detail panel functions on window — shared by DA4:

window.makeBadge=function(t){

  var b=document.createElement("span");

  b.className="badge";

  if(t==="Public"){b.className+=" badge-public";b.textContent="Public";}

  else if(t==="Restricted"){b.className+=" badge-restricted";b.textContent="Restricted";}

  else{b.className+=" badge-optional";b.textContent="Optional";}

  return b;

};

window.showDP=function(lbl,data){

  var p=document.getElementById("fc-detail-panel");

  var b=document.getElementById("dp-body");

  var ti=document.getElementById("dp-title");

  if(!p||!b){return;}

  ti.textContent="Countries: "+lbl;

  b.innerHTML="";

  var h=document.createElement("div");

  h.className="dp-row-hdr";

  ["Country","Holiday","Leave","Days"].forEach(function(x){

    var s=document.createElement("span");s.textContent=x;h.appendChild(s);

  });

  b.appendChild(h);

  data.forEach(function(c){

    var r=document.createElement("div");r.className="dp-row";

    var nd=document.createElement("div");nd.className="dp-country-name";

    var fl=document.createElement("span");fl.textContent=c[0];

    var nm=document.createElement("span");nm.textContent=c[1];

    nd.appendChild(fl);nd.appendChild(nm);

    var hn=document.createElement("div");hn.className="dp-holiday-name";hn.textContent=c[2];

    var lt=document.createElement("div");lt.appendChild(window.makeBadge(c[3]));

    var ld=document.createElement("div");ld.className="dp-days";ld.textContent=c[4];

    r.appendChild(nd);r.appendChild(hn);r.appendChild(lt);r.appendChild(ld);

    b.appendChild(r);

  });

  p.className="visible";

  p.scrollIntoView({behavior:"smooth",block:"nearest"});

};

var tp=document.getElementById("fc-tooltip");

window.posTip=function(e){

  if(!tp){return;}

  var x=e.clientX+14,y=e.clientY-10;

  if(x+310>window.innerWidth){x=e.clientX-320;}

  if(y+200>window.innerHeight){y=e.clientY-210;}

  tp.style.left=x+"px";tp.style.top=y+"px";

};

window.hideTip=function(){if(tp){tp.className="fc-tooltip";}};

window.showTip=function(e,lbl,data){

  if(!tp){return;}

  tp.innerHTML="";

  var td=document.createElement("div");td.className="fc-tooltip-title";

  td.textContent=lbl;tp.appendChild(td);

  data.forEach(function(c){

    var r=document.createElement("div");r.className="fc-tooltip-row";

    var lf=document.createElement("div");lf.className="fc-tooltip-country";

    var fl=document.createElement("span");fl.textContent=c[0];

    var nm=document.createElement("span");nm.textContent=c[1];

    lf.appendChild(fl);lf.appendChild(nm);

    r.appendChild(lf);r.appendChild(window.makeBadge(c[3]));

    tp.appendChild(r);

  });

  tp.className="fc-tooltip visible";

  window.posTip(e);

};

DA4 - Festival Build Calendar | Sequence 40

Reads window.FC and builds the real 7-column calendar grid with all event listeners:

var CF=window.FC;

var mEl=document.getElementById("fc-months");

if(!CF||!mEl){return;}

var MN=["January","February","March","April","May","June","July","August","September","October","November","December"];

var WD=["Sun","Mon","Tue","Wed","Thu","Fri","Sat"];

var tk=window.toKey;

var tdy=window.FC_TODAY;

var tdk=window.FC_TODAYKEY;

function buildMonth(container,year,month){

  var hdr=document.createElement("div");hdr.className="fc-month-hdr";

  hdr.textContent=MN[month]+" "+year;container.appendChild(hdr);

  var wdRow=document.createElement("div");wdRow.className="fc-weekdays";

  WD.forEach(function(w){

    var d=document.createElement("div");d.className="fc-wd";d.textContent=w;wdRow.appendChild(d);

  });

  container.appendChild(wdRow);

  var grid=document.createElement("div");grid.className="fc-grid";

  var first=new Date(year,month,1);

  var last=new Date(year,month+1,0);

  var dow=first.getDay();

  for(var e=0;e<dow;e++){

    var em=document.createElement("div");em.className="fc-day empty";grid.appendChild(em);

  }

  for(var d=1;d<=last.getDate();d++){

    var cd=new Date(year,month,d);

    var ck=tk(cd);

    var fi=CF.festDates[ck];

    var css="fc-day";

    var lb="";

    if(fi){css+=" "+fi.cls+" has-info";lb=fi.lbl;}

    else if(ck===tdk){css+=" day-today";}

    else if(cd<tdy){css+=" day-past";}

    var dd=document.createElement("div");dd.className=css;

    var ne=document.createElement("div");ne.className="fc-day-num";ne.textContent=String(d);dd.appendChild(ne);

    if(lb){var le=document.createElement("div");le.className="fc-day-lbl";le.textContent=lb;dd.appendChild(le);}

    if(fi){

      (function(info){

        dd.addEventListener("mouseenter",function(ev){window.showTip(ev,info.lbl,info.data);});

        dd.addEventListener("mousemove",function(ev){window.posTip(ev);});

        dd.addEventListener("mouseleave",window.hideTip);

        dd.addEventListener("click",function(){window.hideTip();window.showDP(info.lbl,info.data);});

      })(fi);

    }

    grid.appendChild(dd);

  }

  container.appendChild(grid);

}

CF.months.forEach(function(m){buildMonth(mEl,m[0],m[1]);});

var pnl=document.getElementById("fc-detail-panel");

if(pnl){pnl.className="";}

🧱 Step 5: Create the Show/Hide Ramadan Year Item Dynamic Actions

Create 2 separate Dynamic Actions on P4_FESTIVAL Change to show/hide P4_RAMADAN_YEAR:

DA Name:

Show Ramadan Year

Client-side Condition (JS Expression):           

apex.item("P4_FESTIVAL").getValue()==="RAMADAN2025"||apex.item("P4_FESTIVAL").getValue()==="RAMADAN2026"

True Action:

Show → P4_RAMADAN_YEAR

DA Name:

Hide Ramadan Year

Client-side Condition (JS Expression):           

apex.item("P4_FESTIVAL").getValue()!=="RAMADAN2025"&&apex.item("P4_FESTIVAL").getValue()!=="RAMADAN2026"

True Action:

Show → P4_RAMADAN_YEAR

🧱 Step 6: Final Verification Checklist

🎄Select Christmas → December 2025 grid, green sidebar, Dec 25 glows red+gold
🪔Select Diwali → Oct+Nov 2025, purple sidebar, 5 distinct day glows
🌾Select Pongal → January 2026 only, dark amber sidebar, 4 days highlighted
☪️Select Ramadan 2025 → Feb+Mar 2025, navy sidebar, P4_RAMADAN_YEAR appears
☪️Select Ramadan 2026 → Feb+Mar 2026, Eid date updates correctly
🌍Hover any festival date → tooltip shows country + leave badges
📋Click any festival date → detail panel opens with 4-column table
❌Click ✕ on panel → panel closes cleanly
🔄Switch festival → panel resets, calendar rebuilds instantly
🟢Today auto-highlighted green on all calendars

Final Output:

Pongal Festival


Diwali Festival


Christmas Festival


Ramadan Festival


🪔 PART 2 - Diwali Calendar (MULTI-DAY HIGHLIGHT)

In this blog, we explore how to design interactive festival calendars in Oracle APEX by focusing on individual celebrations first.

We begin with Diwali, one of the most vibrant multi-day festivals, followed by Christmas, a globally celebrated event with simpler calendar logic. Each part focuses on a specific festival to demonstrate different design patterns, UI behaviors, and user interactions.

Let’s start with designing the Diwali festival calendar.

🧱 Step 1: Create Page

  • Blank Page: Diwali Calendar

📍 Step 2: Country Selector

  • In Page Designer, create a Select List page item as P3_COUNTRY and select type as Select List
  • Under List of Values → Type choose Static Values

Static Values:

🇮🇳 India
🇬🇧 UK
🇺🇸 USA
🇸🇬 Singapore
🇨🇦 Canada
🇦🇺 Australia

and give return values as 2025-10-20 for all the countries

Step 3: Create the Collapsible Selector Region

  • Create the Collapsible Selector Region
  • Right-click Regions → Create Region
  • Set Template: Collapsible
  • Set Title: Select Your Country
  • Set Static ID: diwali_selector_reg
  • Place P3_COUNTRY select list inside this region

Step 4: Add the Inline CSS

Go to Page → CSS → Inline and paste the full below given CSS code.
Deep purple #1a0a2e background, flame orange #ff6b1a accents, and gold #ffd700 highlights.
Each festival day gets its own glow colour via a dedicated CSS class.

CSS Code :
/* === Purple & Gold Collapsible Bar === */ #diwali_selector_reg { background-color: #1a0a2e !important; border: 1.5px solid #ff6b1a !important; border-radius: 10px; } #diwali_selector_reg .t-Region-header { background-color: #1a0a2e !important; } #diwali_selector_reg .t-Region-headerTitle, #diwali_selector_reg .t-Icon { color: #ffd700 !important; font-family: 'Philosopher', serif; font-size: 1.1rem !important; } /* === Main Container === */ .dw-container { display: flex; min-height: 640px; border-radius: 14px; overflow: hidden; box-shadow: 0 16px 60px rgba(255,107,26,0.2); } /* === Left Sidebar (35%) === */ .dw-sidebar { width: 35%; background: linear-gradient(160deg, #0a0318 0%, #1a0a2e 50%, #0d0520 100%); color: #fff; padding: 48px 32px; position: relative; overflow: hidden; } /* Star pattern on sidebar */ .dw-sidebar::before { content: ''; position: absolute; inset: 0; background-image: radial-gradient(circle, rgba(255,215,0,0.12) 1px, transparent 1px), radial-gradient(circle, rgba(255,107,26,0.08) 1.5px, transparent 1.5px); background-size: 28px 28px, 50px 50px; pointer-events: none; } .dw-diya-icon { font-size: 3.5rem; display: block; margin-bottom: 12px; animation: diyaFlicker 1.8s ease-in-out infinite; filter: drop-shadow(0 0 18px rgba(255,107,26,0.9)); } @keyframes diyaFlicker { 0%,100% { filter: drop-shadow(0 0 18px rgba(255,107,26,0.9)); } 50% { filter: drop-shadow(0 0 35px rgba(255,165,0,1)) drop-shadow(0 0 10px rgba(255,69,0,0.8)); } } .dw-title { font-family: 'Philosopher', serif; font-size: 2.8rem; font-weight: 700; color: #ffd700; letter-spacing: 6px; line-height: 1.1; text-shadow: 0 2px 20px rgba(255,215,0,0.5); } .dw-subtitle { font-family: 'Poppins', sans-serif; font-size: 0.78rem; color: rgba(255,255,255,0.4); letter-spacing: 3px; text-transform: uppercase; margin-top: 4px; } .dw-greeting-section { border-left: 3px solid #ff6b1a; padding-left: 16px; margin-top: 28px; } .dw-hindi { font-family: 'Philosopher', serif; font-size: 1.5rem; color: #ffd700; font-style: italic; line-height: 1.6; } .dw-english { font-size: 0.83rem; color: rgba(255,255,255,0.55); margin-top: 8px; font-style: italic; } .dw-country-label { font-size: 0.83rem; color: #ff9b6b; margin-top: 14px; display: block; font-weight: 600; } .dw-ornaments { font-size: 1.3rem; margin-top: 24px; letter-spacing: 6px; } /* === Right Calendar Area (65%) === */ .dw-calendar-main { width: 65%; padding: 28px 24px; background: #0f0a1a; overflow-y: auto; } .dw-month-header { font-family: 'Philosopher', serif; font-size: 1.15rem; color: #ff9b6b; margin: 16px 0 10px; padding-bottom: 6px; border-bottom: 1px solid rgba(255,107,26,0.25); letter-spacing: 2px; } .dw-month-header:first-child { margin-top: 0; } /* Weekday labels row */ .dw-weekdays { display: grid; grid-template-columns: repeat(7, 1fr); gap: 4px; margin-bottom: 4px; } .dw-wd { text-align: center; font-family: 'JetBrains Mono', monospace; font-size: 0.6rem; letter-spacing: 1px; color: rgba(255,215,0,0.5); text-transform: uppercase; padding: 4px 0; } /* === 7-column calendar grid === */ .dw-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 4px; margin-bottom: 20px; } .dw-day { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 8px 3px; border-radius: 8px; background: rgba(255,255,255,0.03); border: 1px solid rgba(255,255,255,0.06); min-height: 52px; transition: transform 0.15s; cursor: default; } .dw-day:hover { transform: scale(1.06); } .dw-day.empty { background: transparent; border: none; pointer-events: none; } .dw-day-num { font-size: 1rem; font-weight: 600; color: rgba(240,220,180,0.85); line-height: 1; } .dw-day-lbl { font-size: 0.52rem; color: rgba(255,255,255,0.35); margin-top: 3px; text-align: center; line-height: 1.2; } /* Past days */ .dw-day.day-past { opacity: 0.3; } /* Today */ .dw-day.day-today { background: rgba(46,204,113,0.18) !important; border-color: #2ecc71 !important; box-shadow: 0 0 12px rgba(46,204,113,0.35); } .dw-day.day-today .dw-day-num { color: #2ecc71; } /* Dhanteras — Gold */ .dw-day.day-dhanteras { background: rgba(255,215,0,0.14) !important; border-color: #ffd700 !important; box-shadow: 0 0 14px rgba(255,215,0,0.4); animation: goldPulse 2.5s ease-in-out infinite; } .dw-day.day-dhanteras .dw-day-num { color: #ffd700; } .dw-day.day-dhanteras .dw-day-lbl { color: rgba(255,215,0,0.8); } @keyframes goldPulse { 0%,100% { box-shadow: 0 0 14px rgba(255,215,0,0.4); } 50% { box-shadow: 0 0 28px rgba(255,215,0,0.75); } } /* Narak Chaturdashi — Amber */ .dw-day.day-narak { background: rgba(255,170,0,0.14) !important; border-color: #ffaa00 !important; box-shadow: 0 0 14px rgba(255,170,0,0.45); } .dw-day.day-narak .dw-day-num { color: #ffaa00; } .dw-day.day-narak .dw-day-lbl { color: rgba(255,170,0,0.8); } /* Diwali Main — Flame Red (the big one!) */ .dw-day.day-diwali { background: linear-gradient(135deg, rgba(255,69,0,0.25), rgba(255,107,26,0.18)) !important; border-color: #ff4500 !important; border-width: 2px !important; box-shadow: 0 0 22px rgba(255,69,0,0.6); animation: diwaliGlow 1.8s ease-in-out infinite; } .dw-day.day-diwali .dw-day-num { color: #ff6b1a; font-size: 1.1rem; } .dw-day.day-diwali .dw-day-lbl { color: rgba(255,107,26,0.9); } @keyframes diwaliGlow { 0%,100% { box-shadow: 0 0 22px rgba(255,69,0,0.6); } 50% { box-shadow: 0 0 40px rgba(255,107,26,0.95); } } /* Govardhan Puja — Orange */ .dw-day.day-govardhan { background: rgba(255,107,26,0.14) !important; border-color: #ff6b1a !important; box-shadow: 0 0 12px rgba(255,107,26,0.4); } .dw-day.day-govardhan .dw-day-num { color: #ff9b6b; } .dw-day.day-govardhan .dw-day-lbl { color: rgba(255,155,107,0.8); } /* Bhai Dooj — Purple */ .dw-day.day-bhai { background: rgba(155,77,255,0.14) !important; border-color: #9b4dff !important; box-shadow: 0 0 12px rgba(155,77,255,0.4); } .dw-day.day-bhai .dw-day-num { color: #c084ff; } .dw-day.day-bhai .dw-day-lbl { color: rgba(192,132,255,0.8); }

Step 5: Add the Static HTML Region (Calendar Shell)

  • Create a Static Content region.
  • Set Template to Blank with Attributes.
  • Paste this HTML into Source.
  • The two grid divs (dw-grid-oct and dw-grid-nov) are intentionally empty - JavaScript fills them:

HTML Source Code :

<div class="dw-container"> <!-- LEFT: Dark sidebar --> <div class="dw-sidebar"> <span class="dw-diya-icon">🪔</span> <h1 class="dw-title">DIWALI</h1> <div class="dw-subtitle">Festival of Lights · 2025</div> <div class="dw-greeting-section"> <p class="dw-hindi"> दीपावली की<br/>शुभकामनाएं </p> <p class="dw-english"> "May the festival of lights bring joy, prosperity and happiness to your life." </p> <!-- Updated dynamically by JavaScript --> <span class="dw-country-label" id="dw-country-name"> 🌍 Select your country above </span> </div> <div class="dw-ornaments">🪔 ✨ 🎆 🪷 🎇</div> </div> <!-- RIGHT: Calendar (JavaScript fills these grids) --> <div class="dw-calendar-main"> <!-- October 2025 --> <div class="dw-month-header">🍂 October 2025</div> <div class="dw-weekdays"> <div class="dw-wd">Sun</div><div class="dw-wd">Mon</div> <div class="dw-wd">Tue</div><div class="dw-wd">Wed</div> <div class="dw-wd">Thu</div><div class="dw-wd">Fri</div> <div class="dw-wd">Sat</div> </div> <div id="dw-grid-oct" class="dw-grid"></div> <!-- November 2025 --> <div class="dw-month-header">🌸 November 2025</div> <div class="dw-weekdays"> <div class="dw-wd">Sun</div><div class="dw-wd">Mon</div> <div class="dw-wd">Tue</div><div class="dw-wd">Wed</div> <div class="dw-wd">Thu</div><div class="dw-wd">Fri</div> <div class="dw-wd">Sat</div> </div> <div id="dw-grid-nov" class="dw-grid"></div> </div> </div>

Step 6: Create the Dynamic Action + JavaScript

6a - Dynamic Action Setup

Property Value
Name Generate Diwali Calendar Event Change Selection Type Item Item P3_COUNTRY Fire on Initialization ✅ YES True Action Execute JavaScript Code

6b - JavaScript (No Backticks -APEX Safe)

⚠️ This JavaScript uses zero backtick template literals - only document.createElement and .textContent.
This prevents the SyntaxError: Unexpected token ',' that APEX causes when backticks get corrupted
on paste.

JavaScript Code :

var diwaliDateStr = apex.item("P3_COUNTRY").getValue(); var gridOct = document.getElementById("dw-grid-oct"); var gridNov = document.getElementById("dw-grid-nov"); var labelEl = document.getElementById("dw-country-name"); if (!gridOct || !gridNov) { return; } gridOct.innerHTML = ""; gridNov.innerHTML = ""; if (!diwaliDateStr) { return; } // ── Update sidebar country label ────────────────────────── var selectEl = document.getElementById("P3_COUNTRY"); if (selectEl && labelEl) { labelEl.textContent = "📍 " + selectEl.options[selectEl.selectedIndex].text; } // ── Festival dates relative to Diwali main day ──────────── var diwaliMain = new Date(diwaliDateStr); var dhanteras = new Date(diwaliMain); dhanteras.setDate(diwaliMain.getDate() - 2); var narakChat = new Date(diwaliMain); narakChat.setDate(diwaliMain.getDate() - 1); var govardhan = new Date(diwaliMain); govardhan.setDate(diwaliMain.getDate() + 1); var bhaiDooj = new Date(diwaliMain); bhaiDooj.setDate(diwaliMain.getDate() + 2); // Convert to date-only strings for comparison (YYYY-MM-DD) function toKey(d) { return d.getFullYear() + "-" + String(d.getMonth() + 1).padStart(2, "0") + "-" + String(d.getDate()).padStart(2, "0"); } var today = new Date(); today.setHours(0,0,0,0); var todayKey = toKey(today); var diwaliKey = toKey(diwaliMain); var dhanKey = toKey(dhanteras); var narakKey = toKey(narakChat); var govKey = toKey(govardhan); var bhaiKey = toKey(bhaiDooj); // ── Function to build one month's grid ─────────────────── function buildMonth(grid, year, month) { // month is 0-indexed (0=Jan, 9=Oct, 10=Nov) var firstDay = new Date(year, month, 1); var lastDay = new Date(year, month + 1, 0); var startDow = firstDay.getDay(); // 0=Sun // Add empty cells for days before month starts for (var e = 0; e < startDow; e++) { var emptyDiv = document.createElement("div"); emptyDiv.className = "dw-day empty"; grid.appendChild(emptyDiv); } // Add one cell per day for (var d = 1; d <= lastDay.getDate(); d++) { var cellDate = new Date(year, month, d); var cellKey = toKey(cellDate); // Determine state and label var cssClass = "dw-day"; var festLabel = ""; if (cellKey === diwaliKey) { cssClass += " day-diwali"; festLabel = "🪔 Diwali"; } else if (cellKey === dhanKey) { cssClass += " day-dhanteras"; festLabel = "💛 Dhanteras"; } else if (cellKey === narakKey) { cssClass += " day-narak"; festLabel = "🔥 Narak Chat";} else if (cellKey === govKey) { cssClass += " day-govardhan"; festLabel = "🌿 Govardhan"; } else if (cellKey === bhaiKey) { cssClass += " day-bhai"; festLabel = "💜 Bhai Dooj"; } else if (cellKey === todayKey) { cssClass += " day-today"; festLabel = "Today"; } else if (cellDate < today) { cssClass += " day-past"; } // Build cell using createElement (no backticks) var dayDiv = document.createElement("div"); dayDiv.className = cssClass; var numDiv = document.createElement("div"); numDiv.className = "dw-day-num"; numDiv.textContent = String(d); var lblDiv = document.createElement("div"); lblDiv.className = "dw-day-lbl"; lblDiv.textContent = festLabel; dayDiv.appendChild(numDiv); if (festLabel) { dayDiv.appendChild(lblDiv); } grid.appendChild(dayDiv); } } // ── Build October (month=9) and November (month=10) ─────── buildMonth(gridOct, 2025, 9); buildMonth(gridNov, 2025, 10);

Step 7: Save, Run & Verify

🪔 Deep purple collapsible selector region renders with gold text 🪔 Dark sidebar shows DIWALI title in gold Philosopher font with flickering diya 🪔 Hindi greeting "दीपावली की शुभकामनाएं" visible in sidebar 🪔 October 2025 grid renders with correct weekday alignment (starts Wednesday) 🪔 November 2025 grid renders below October 🪔 Oct 18 - Dhanteras glows gold ✨ 🪔 Oct 19 - Narak Chaturdashi glows amber 🔥 🪔 Oct 20 - Diwali Main pulses flame red with animated glow 🪔 🪔 Oct 21 - Govardhan Puja glows orange 🌿 🪔 Oct 22 - Bhai Dooj glows purple 💜 🪔 Today's date auto-highlighted in green 🪔 Past days faded to 30% opacity

Final Output:

India:


Singapore:


🎄 PART 2 — CHRISTMAS CALENDAR (COUNTDOWN)

🧱 Step 1: Create Page

  • Blank Page: Christmas Calender

📍 Step 2: Country Selector

Before writing a single line of CSS, create the country selector that drives the whole calendar. The return value stores the Christmas Day date string for each country - JavaScript parses it directly, no AJAX needed.
  • In Page Designer, create a Select List page item as P2_COUNTRY and select type as Select List
  • Under List of Values → Type choose Static Values

Static Values:

🇮🇳 India
🇺🇸 USA
🇬🇧 UK
🇦🇺 Australia
🇩🇪 Germany
🇵🇭 Philippines

and give return values as 2025-12-25 for all the countries

Step 3: Create the Collapsible Selector Region

  • Create the Collapsible Selector Region
  • Right-click Regions → Create Region
  • Set Template: Collapsible
  • Set Title: Select Your Country
  • Set Static ID: xmas_selector_reg
  • Place P2_COUNTRY select list inside this region

Step 3: Load Google Fonts

Go to App Builder → Your App → Shared Components → User Interface Attributes → CSS tab → File URLs and add:

https://fonts.googleapis.com/css2?family=Mountains+of+Christmas:wght@400;700&family=Nunito:wght@300;400;600;700&display=swap

Font                                          Used For                                                       Effect

Mountains of Christmas CHRISTMAS title, section headings       Festive, rounded, holiday feel
Nunito                              Body text, day labels, dates         Friendly, rounded, highly readable

Step 5: Add the Inline CSS

Go to Page → CSS → Inline and paste the full block code below. The palette uses deep forest green #1a3d2b, festive red #c0392b, and warm gold #f1c40f. The light-string animation runs purely in CSS - no JavaScript required for it.

CSS Code :

* ============================================
   CHRISTMAS CALENDAR — Inline Page CSS
   ============================================ */

/* === Festive Red & Green Collapsible Bar === */
#xmas_selector_reg {
  background-color: #1a3d2b !important;
  border: 1.5px solid #c0392b !important;
  border-radius: 10px;
}
#xmas_selector_reg .t-Region-header {
  background-color: #1a3d2b !important;
}
#xmas_selector_reg .t-Region-headerTitle,
#xmas_selector_reg .t-Icon {
  color: #f1c40f !important;
  font-family: 'Mountains of Christmas', cursive;
  font-size: 1.1rem !important;
}

/* === Festive Light String === */
.xmas-lights {
  display: flex;
  align-items: flex-end;
  gap: 0;
  padding: 4px 0 8px;
  position: relative;
  margin-bottom: 16px;
}
.xmas-lights::before {
  content: '';
  position: absolute;
  top: 6px; left: 0; right: 0;
  height: 2px;
  background: rgba(255,255,255,0.2);
}
.xmas-bulb {
  width: 12px; height: 16px;
  border-radius: 50% 50% 60% 60%;
  margin: 0 16px;
  animation: xmasBlink 2s ease-in-out infinite;
}
.xmas-bulb:nth-child(4n+1) { background: #e74c3c; box-shadow: 0 0 8px 3px #e74c3c; animation-delay: 0s; }
.xmas-bulb:nth-child(4n+2) { background: #27ae60; box-shadow: 0 0 8px 3px #27ae60; animation-delay: 0.5s; }
.xmas-bulb:nth-child(4n+3) { background: #f1c40f; box-shadow: 0 0 8px 3px #f1c40f; animation-delay: 1s; }
.xmas-bulb:nth-child(4n+4) { background: #5dade2; box-shadow: 0 0 8px 3px #5dade2; animation-delay: 1.5s; }
@keyframes xmasBlink {
  0%,100% { opacity: 1; }
  50%      { opacity: 0.3; }
}

/* === Main Layout === */
.xmas-container {
  display: flex;
  min-height: 620px;
  border-radius: 12px;
  overflow: hidden;
  box-shadow: 0 12px 48px rgba(0,0,0,0.4);
}

/* === Left Sidebar (38%) === */
.xmas-sidebar {
  width: 38%;
  background: linear-gradient(160deg, #0d2818 0%, #1a3d2b 50%, #0a1f12 100%);
  color: #fff;
  padding: 50px 36px;
  position: relative;
  overflow: hidden;
}
/* Subtle snowflake pattern on sidebar */
.xmas-sidebar::before {
  content: '';
  position: absolute; inset: 0;
  background-image: radial-gradient(circle, rgba(255,255,255,0.06) 1px, transparent 1px);
  background-size: 24px 24px;
  pointer-events: none;
}
.xmas-main-title {
  font-family: 'Mountains of Christmas', cursive;
  font-size: 3rem;
  font-weight: 700;
  color: #f1c40f;
  letter-spacing: 6px;
  line-height: 1.1;
  text-shadow: 0 2px 16px rgba(241,196,15,0.4);
}
.xmas-year {
  font-family: 'Nunito', sans-serif;
  font-size: 1rem;
  color: rgba(255,255,255,0.5);
  letter-spacing: 4px;
  margin-top: 4px;
  text-transform: uppercase;
}
.xmas-greeting-section {
  border-left: 3px solid #c0392b;
  padding-left: 18px;
  margin-top: 30px;
}
.xmas-quote {
  font-family: 'Nunito', sans-serif;
  font-size: 1.1rem;
  color: #a8e8c0;
  line-height: 1.8;
  font-style: italic;
}
.xmas-country-label {
  font-family: 'Nunito', sans-serif;
  font-size: 0.85rem;
  color: #f1c40f;
  margin-top: 12px;
  display: block;
  font-weight: 700;
}
.xmas-ornaments {
  font-size: 1.4rem;
  margin-top: 28px;
  letter-spacing: 4px;
}

/* === Right Calendar Area (62%) === */
.xmas-calendar-main {
  width: 62%;
  padding: 36px 32px;
  background: #fafcff;
}
.xmas-calendar-header {
  font-family: 'Mountains of Christmas', cursive;
  font-size: 1.5rem;
  color: #1a3d2b;
  margin-bottom: 20px;
  text-align: center;
}
.xmas-calendar-header span { color: #c0392b; }

/* === CSS Grid: 5 columns × 5 rows = 25 days === */
.xmas-grid {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 12px;
  width: 100%;
}
.xmas-day-cell {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 12px 6px;
  border-radius: 10px;
  background: #f0f4ff;
  border: 1.5px solid #dce4f5;
  transition: transform 0.2s, box-shadow 0.2s;
  font-family: 'Nunito', sans-serif;
  cursor: default;
}
.xmas-day-cell:hover {
  transform: translateY(-3px);
  box-shadow: 0 6px 20px rgba(26,107,58,0.2);
}
.xmas-day-num {
  font-size: 1.4rem;
  font-weight: 700;
  color: #1a3d2b;
  line-height: 1;
}
.xmas-day-date {
  font-size: 0.65rem;
  color: #7a90b8;
  margin-top: 4px;
  text-align: center;
  font-weight: 600;
}

/* Christmas Day — the big one! */
.xmas-day {
  background: linear-gradient(135deg, #c0392b, #e74c3c) !important;
  border-color: #f1c40f !important;
  border-width: 2.5px !important;
  box-shadow: 0 0 20px rgba(241,196,15,0.5);
  animation: xmasGlow 2s ease-in-out infinite;
}
.xmas-day .xmas-day-num { color: #f1c40f !important; font-size: 1.1rem; }
.xmas-day .xmas-day-date { color: rgba(255,255,255,0.85) !important; }
@keyframes xmasGlow {
  0%,100% { box-shadow: 0 0 20px rgba(241,196,15,0.5); }
  50%     { box-shadow: 0 0 36px rgba(241,196,15,0.85); }
}

/* Past days — greyed out */
.day-past {
  opacity: 0.45;
  background: #e8ecf5 !important;
}

/* Today's day — green highlight */
.day-today {
  background: linear-gradient(135deg, #1a6b3a, #27ae60) !important;
  border-color: #2ecc71 !important;
}
.day-today .xmas-day-num { color: #fff !important; }
.day-today .xmas-day-date { color: rgba(255,255,255,0.8) !important; }

Step 6: Add the Static HTML Region (Calendar Shell)

  • Create a Static Content region.
  • Set Template to Blank with Attributes.
  • In the Source, paste this HTML shell — the JavaScript in Step 6 fills the xmas-grid div dynamically:

HTML Source Code :

<!-- Outer flex container -->
<div class="xmas-container">

  <!-- LEFT: Forest-green sidebar -->
  <div class="xmas-sidebar">

    <!-- Blinking light string -->
    <div class="xmas-lights">
      <div class="xmas-bulb"></div>
      <div class="xmas-bulb"></div>
      <div class="xmas-bulb"></div>
      <div class="xmas-bulb"></div>
      <div class="xmas-bulb"></div>
      <div class="xmas-bulb"></div>
    </div>

    <h1 class="xmas-main-title">🎄 CHRISTMAS</h1>
    <div class="xmas-year">Advent Calendar 2025</div>

    <div class="xmas-greeting-section">
      <p class="xmas-quote">
        "Peace on earth, goodwill to all —
         count the days to Christmas
         with joy and celebration."
      </p>
      <!-- JS will update this label -->
      <span class="xmas-country-label"
            id="xmas-country-name">
        🌍 Select your country above
      </span>
    </div>

    <div class="xmas-ornaments">🎁 🔔 ❄️ ⭐ 🦌</div>

  </div>

  <!-- RIGHT: Dynamic Advent Grid (JavaScript fills this) -->
  <div class="xmas-calendar-main">
    <div class="xmas-calendar-header">
      Advent: Dec 1 → <span>Dec 25 🎄</span>
    </div>
    <!-- Grid is empty — JavaScript builds it -->
    <div id="xmas-grid" class="xmas-grid"></div>
  </div>

</div> </div>

Step 7: Create the Dynamic Action + JavaScript

6a - Dynamic Action Setup

Property             Value
Name                       Generate Christmas Calendar
Event                       Change
Selection Type         Item
Item                       P2_COUNTRY
Fire on Initialization   ✅ YES 

⚠️ Fire on Initialization must be YES. Without this, the calendar grid will be completely empty when the page first loads. The user would have to change the select list manually before anything appears.

6b - Add the Execute JavaScript Code Action

Delete the default "Show" action. Add Execute JavaScript Code and paste the full script below. This script builds all 25 Advent days, automatically highlights today, greys out past days, and makes Christmas Day glow:

JavaScript Code:

var countryVal = apex.item("P2_COUNTRY").getValue();

var grid = document.getElementById("xmas-grid");
if (!grid) { return; }

grid.innerHTML = "";

if (!countryVal) { return; }

// Update sidebar label
var selectEl = document.getElementById("P2_COUNTRY");
var labelEl  = document.getElementById("xmas-country-name");
if (selectEl && labelEl) {
    labelEl.textContent = "📍 " + selectEl.options[selectEl.selectedIndex].text;
}

// Date setup
var today = new Date();
today.setHours(0, 0, 0, 0);

var adventStart = new Date("2025-12-01");

// Build 25 Advent day cells
for (var i = 0; i < 25; i++) {

    var cellDate = new Date(adventStart.getTime());
    cellDate.setDate(adventStart.getDate() + i);
    cellDate.setHours(0, 0, 0, 0);

    var dayNum = cellDate.getDate();

    var fmtDate = cellDate.toLocaleDateString("en-US", {
        weekday : "short",
        month   : "short",
        day     : "numeric"
    });

    var isChristmas = (dayNum === 25);
    var isToday     = (cellDate.getTime() === today.getTime());
    var isPast      = (cellDate < today && !isToday);

    var cssClass = "xmas-day-cell";
    if      (isChristmas) { cssClass += " xmas-day";  }
    else if (isToday)     { cssClass += " day-today"; }
    else if (isPast)      { cssClass += " day-past";  }

    var icon;
    if      (isChristmas) { icon = "🎄";           }
    else if (isToday)     { icon = "⭐";            }
    else if (isPast)      { icon = "✓";             }
    else                  { icon = String(dayNum);  }

    var label;
    if (isChristmas) { label = "Christmas!"; }
    else             { label = fmtDate;      }

    var dayDiv = document.createElement("div");
    dayDiv.className = cssClass;

    var numSpan  = document.createElement("div");
    numSpan.className   = "xmas-day-num";
    numSpan.textContent = icon;

    var dateSpan = document.createElement("div");
    dateSpan.className   = "xmas-day-date";
    dateSpan.textContent = label;

    dayDiv.appendChild(numSpan);
    dayDiv.appendChild(dateSpan);
    grid.appendChild(dayDiv);
}

Step 7: Save, Run & Verify

Hit Save and Run. Here's your complete verification checklist: ✅Festive red-&-green collapsible region appears at the top with the country selector ✅Split layout renders - dark forest-green sidebar on the left ✅"CHRISTMAS" title in Mountains of Christmas font glows in gold ✅Blinking multi-colour light string animates inside the sidebar ✅25 Advent day cells appear in a 5-column CSS Grid (Dec 1–25) ✅Past days are automatically greyed out based on today's real date ✅Today's cell glows in green with a ⭐ icon ✅December 25 cell glows red & gold with animated pulse and "Christmas!" label ✅Switching country updates the sidebar label instantly -no page reload

Final Output:

India:


USA:

Conclusion

Building engaging calendar experiences in Oracle APEX is not just about presenting dates-it’s about

designing dynamic interfaces that adapt to different use cases and user expectations.

In this blog, we explored a complete journey of calendar design and implementation. We started with a

multi-festival calendar, combining multiple celebrations into a single interactive interface, and then

deep-dived into two distinct implementations: a highlight-based Diwali calendar and a countdown-style

Christmas calendar. Each approach demonstrates a different design pattern-one focusing on multi-day

festival visibility and the other on progressive user engagement.

Despite their differences, all implementations are built using the same core technologies: HTML, CSS,

JavaScript, and Oracle APEX Dynamic Actions. This highlights how flexible and powerful Oracle APEX

can be when combined with thoughtful UI design and client-side logic.

The key takeaway is that by understanding and applying different design patterns, developers can create

scalable, reusable, and visually rich calendar solutions-from single-event experiences to fully dynamic

multi-festival systems-without relying on plugins or complex backend processing.

Comments

Popular posts from this blog

Building Secure RESTful Services in Oracle APEX 24.2 Using ORDS, OAuth2 Client Credentials, and PL/SQL

Building a Portfolio Generator in Oracle APEX: A Step-by-Step Guide to Generate Downloadable Portfolio Documents

Implementing WhatsApp OTP Verification in Oracle APEX Using UltraMsg API