Ultimate Guide to Reorder & Hide Columns with a Custom APEX Report (with Drag & Drop + PDF Export)

Have you ever wished your APEX reports were as flexible as moving JIRA tickets across your board?
Well, now they can be! In this post, I’ll walk you through creating a drag-and-drop column reordering and hiding system that exports exactly what you see into a polished PDF. Fancy? Yes. Complex? Not anymore.

Reorder & Hide Columns within a Custom APEX Report (with Drag & Drop + PDF Export)

While this blog post explains the concepts and code step by step, the YouTube video gives you a complete visual walkthrough. You'll see how each method works in a real APEX application, how to test it live, and understand the logic better with practical examples. It's perfect if you prefer learning by seeing it in action!

🧩 Problem Statement

We needed a way to:

  • Rearrange report columns dynamically
  • Hide/show specific columns as needed
  • Export the visible columns only in the same order to a PDF
  • All without refreshing the page or jumping into configuration popups

✅ Solution Overview

We will:

  1. Create a RESTful GET endpoint to fetch data
  2. Use SortableJS to manage drag-and-drop zones
  3. Render the table dynamically with JavaScript
  4. Export visible columns into a PDF using jsPDF and autoTable

🔧 Step 1: Create the RESTful Service

Module: Reorder Columns
Resource Template: /resourceTemplate

GET SQL:

SELECT 
  SEQUENCE_ORDER AS "Sequence Order",
  STATUS_ID AS "Status Id",
  PRIORITY AS "Priority",
  ID,
  PROJECT_SPRINT_ID AS "Project Sprint ID",
  PROJECT_UNIQUE_ID AS "Project Unique ID",
  SHORT_TITLE AS "Short Title",
  DESCRIPTION AS "Description",
  ESTIMATION,
  ASSIGNED_TO,
  REPORTER
FROM TMS_TASKS

💡 Step 2: Required Libraries

https://cdn.jsdelivr.net/npm/sortablejs@1.15.0/Sortable.min.js
https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js
https://cdnjs.cloudflare.com/ajax/libs/jspdf-autotable/3.5.28/jspdf.plugin.autotable.min.js

🎨 Step 3: Drag-and-Drop CSS Styling

This CSS makes your chips and dropzones look 🔥🔥
.dropzone {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
  padding: 10px;
  margin-bottom: 15px;
  border: 2px dashed #ccc;
  border-radius: 10px;
}

#column-dropzone {
  background: #f4f6f8;
}

#hidden-dropzone {
  background: #eee6e4;
}

.draggable-chip {
  color: white;
  padding: 6px 12px;
  border-radius: 20px;
  font-weight: bold;
  text-transform: uppercase;
  cursor: grab;
  user-select: none;
  transition: transform 0.2s;
}

#column-dropzone .draggable-chip {
  background: linear-gradient(to right, #6dd5ed, #2193b0);
}

#hidden-dropzone .draggable-chip {
  background: linear-gradient(to right, #f2709c, #ff9472);
}

.draggable-chip:hover {
  transform: scale(1.05);
}

🧠 Step 4: JavaScript Logic – Brains of the Operation

This JavaScript handles the rendering, dragging, hiding, reordering, and exporting. Don't forget to replace the 'your restful url here' with your actuat GET endpoint:

let originalData = [];
let columnOrder = [];
let hiddenColumns = [];

function createChip(col) {
    const chip = document.createElement('div');
    chip.className = 'draggable-chip';
    chip.dataset.column = col;
    chip.textContent = col.toUpperCase();
    return chip;
}

function renderDropzone() {
    const visibleZone = document.getElementById('column-dropzone');
    const hiddenZone = document.getElementById('hidden-dropzone');

    if (!visibleZone || !hiddenZone) return;

    visibleZone.innerHTML = '';
    hiddenZone.innerHTML = '';

    columnOrder.forEach(col => visibleZone.appendChild(createChip(col)));
    hiddenColumns.forEach(col => hiddenZone.appendChild(createChip(col)));

    makeSortable(visibleZone, hiddenZone);
}

function makeSortable(visibleZone, hiddenZone) {
    const commonOptions = {
        group: 'columns',
        animation: 150,
        onEnd: () => {
            columnOrder = Array.from(visibleZone.children).map(chip => chip.dataset.column);
            hiddenColumns = Array.from(hiddenZone.children).map(chip => chip.dataset.column);
            renderTable();
        }
    };

    Sortable.create(visibleZone, commonOptions);
    Sortable.create(hiddenZone, commonOptions);
}

function renderTable() {
    const table = document.getElementById('dynamicTable');
    if (!table) return;

    const thead = table.querySelector('thead tr');
    const tbody = table.querySelector('tbody');

    if (!thead || !tbody) return;

    thead.innerHTML = '';
    tbody.innerHTML = '';

    columnOrder.forEach(col => {
        const th = document.createElement('th');
        th.textContent = col.toUpperCase();
        thead.appendChild(th);
    });

    originalData.forEach(row => {
        const tr = document.createElement('tr');
        columnOrder.forEach(col => {
            const td = document.createElement('td');
            td.textContent = row[col] ?? '';
            tr.appendChild(td);
        });
        tbody.appendChild(tr);
    });
}

async function fetchData() {
    try {
        const response = await fetch('your restful url here');
        const json = await response.json();

        originalData = json.items;
        if (originalData.length > 0) {
            const allCols = Object.keys(originalData[0]);
            columnOrder = [...allCols];
            hiddenColumns = [];
            renderDropzone();
            renderTable();
        }

    } catch (err) {
        console.error('Error fetching data:', err);
    }
}

document.getElementById('downloadBtn')?.addEventListener('click', async function () {
    await new Promise(resolve => setTimeout(resolve, 0));

    const { jsPDF } = window.jspdf;
    const doc = new jsPDF({ orientation: 'landscape' });

    const tableData = originalData.map(row => columnOrder.map(col => row[col] ?? ''));
    const tableHeader = columnOrder.map(c => c.toUpperCase());

    const columnStyles = {};
    columnOrder.forEach((_, i) => {
        columnStyles[i] = {
            cellWidth: 'wrap',
            overflow: 'linebreak'
        };
    });

    doc.autoTable({
        head: [tableHeader],
        body: tableData,
        styles: {
            fontSize: 8,
            overflow: 'linebreak',
            cellPadding: 2,
            textColor: 20
        },
        headStyles: {
            fillColor: [40, 40, 40],
            textColor: 255,
            fontStyle: 'bold'
        },
        columnStyles: columnStyles,
        startY: 20,
        margin: { top: 20, left: 10, right: 10 },
        tableWidth: 'auto'
    });

    doc.save('column-order-report.pdf');
});

fetchData();

📦 Step 5: HTML Markup

<div style="display: flex; gap: 20px; flex-wrap: wrap;">
  <div style="flex: 1;">
    <h3>Visible Columns</h3>
    <div id="column-dropzone" class="dropzone"></div>
  </div>
  <div style="flex: 1;">
    <h3>Hidden Columns</h3>
    <div id="hidden-dropzone" class="dropzone"></div>
  </div>
</div>

<input type="button" id="downloadBtn" class="t-Button t-Button--hot" value="Download PDF"/>

<table id="dynamicTable">
  <thead><tr></tr></thead>
  <tbody></tbody>
</table>

🚀 Wrapping Up

You’ve now got a smart, snappy, and modern interface that empowers your users to control how they view and export data. Feels good, right? 😄
More content like this coming soon at Into The Oracle Verse!

data:post.title

Ultimate Guide to Reorder & Hide Columns with a Custom APEX Report (with Drag & Drop + PDF Export)

Written by JENISH JOYAL J

Published on June 08, 2025

No comments:

Powered by Blogger.