In modern web applications, having interactive elements like drag-and-drop functionality greatly enhances user experience and boosts productivity. In Oracle APEX, implementing this functionality is especially useful for task management systems, where users need to move items between different statuses or categories in a visual interface.
This post will guide you step by step through the process of creating dynamic drag-and-drop elements in Oracle APEX using the powerful Sortable.js library. We'll build a swimlane-style interface that allows users to rearrange tasks across different statuses, with task movements automatically updating both the task status and order using AJAX. You'll also find detailed explanations of the Sortable.js classes used in the process and how they integrate with APEX.
![]() |
Creating Dynamic Drag and Drop Elements in Oracle APEX (With Code and AJAX Updates) |
By the end of this post, you'll have a fully functioning drag-and-drop interface in your APEX application, along with the knowledge to customize it further. If you feel that a video guideline would help you more, then feel free to check my YouTube video that uses this post to create the Drag and Drop feature in Oracle APEX in less than 20 minutes:)
Let's get started!
Now, there are some tables that I'd be using throughout this post. I have attached the DDL and DML statements for the same below:
-- DDL for TMS_TASKS
CREATE TABLE TMS_TASKS (
ID NUMBER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
PROJECT_SPRINT_ID NUMBER NOT NULL,
PROJECT_UNIQUE_ID NUMBER NOT NULL,
SHORT_TITLE VARCHAR2(255),
DESCRIPTION CLOB,
ESTIMATION NUMBER,
ASSIGNED_TO VARCHAR2(100),
REPORTER VARCHAR2(100),
STATUS_ID NUMBER,
PRIORITY VARCHAR2(50),
SEQUENCE_ORDER NUMBER
)
/
-- DML for TMS_TASKS
INSERT INTO TMS_TASKS (PROJECT_SPRINT_ID, PROJECT_UNIQUE_ID, SHORT_TITLE, DESCRIPTION, ESTIMATION, ASSIGNED_TO, REPORTER, STATUS_ID, PRIORITY, SEQUENCE_ORDER)
VALUES (1, 4, 'Fourth Task: Continuing task creation', 'Fourth Task: Continuing task creation', 1, 'SH', 'SH', 2, 'High', 30)
/
INSERT INTO TMS_TASKS (PROJECT_SPRINT_ID, PROJECT_UNIQUE_ID, SHORT_TITLE, DESCRIPTION, ESTIMATION, ASSIGNED_TO, REPORTER, STATUS_ID, PRIORITY, SEQUENCE_ORDER)
VALUES (1, 1, 'Initial Task - Creating a TMS', 'This is the first task.', 4, 'JD', 'JD', 1, 'High', 10)
/
INSERT INTO TMS_TASKS (PROJECT_SPRINT_ID, PROJECT_UNIQUE_ID, SHORT_TITLE, DESCRIPTION, ESTIMATION, ASSIGNED_TO, REPORTER, STATUS_ID, PRIORITY, SEQUENCE_ORDER)
VALUES (1, 2, 'The Second Task', NULL, 3, 'JD', 'SH', 2, 'Low', 20)
/
INSERT INTO TMS_TASKS (PROJECT_SPRINT_ID, PROJECT_UNIQUE_ID, SHORT_TITLE, DESCRIPTION, ESTIMATION, ASSIGNED_TO, REPORTER, STATUS_ID, PRIORITY, SEQUENCE_ORDER)
VALUES (1, 3, 'Third Task: Continuing task creation', 'Third Task: Continuing task creation...', 3.5, 'JD', 'JD', 2, 'Medium', 10)
/
-- DDL for TMS_PROJECT_STATUS
CREATE TABLE TMS_PROJECT_STATUS (
ID NUMBER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
NAME VARCHAR2(100) NOT NULL,
PROJECT_ID NUMBER NOT NULL,
SEQUENCE_ORDER NUMBER NOT NULL
)
/
-- DML for TMS_PROJECT_STATUS
INSERT INTO TMS_PROJECT_STATUS (NAME, PROJECT_ID, SEQUENCE_ORDER) VALUES ('New', 1, 1)
/
INSERT INTO TMS_PROJECT_STATUS (NAME, PROJECT_ID, SEQUENCE_ORDER) VALUES ('Active', 1, 2)
/
INSERT INTO TMS_PROJECT_STATUS (NAME, PROJECT_ID, SEQUENCE_ORDER) VALUES ('On Hold', 1, 3)
/
We'll start with a blank page like the one below.
![]() |
Fig 1 Blank Page in an Application |
Understanding Drag and Drop Functionality in Web Applications
Drag and drop functionality is a key feature in modern web applications, allowing users to interact intuitively with elements on a page.
Making Elements Draggable
To create draggable elements, you need to set the draggable
attribute to true
. This enables the user to move the element by dragging it with the mouse. For example:
<div class="item" draggable="true">Draggable Item</div>
Making Containers Droppable
Containers can accept draggable elements by utilizing the ondrop
and ondragover
event handlers. Here are some key points to remember:
- ondragover: This event handler prevents the default behavior to allow dropping.
- ondrop: This handles the drop event and places the dragged element into the designated container.
An example of a droppable container is:
<div class="container" ondrop="drop(event)" ondragover="allowDrop(event)"><!-- Drop Target --></div>
Complete Example
Here’s a complete example that combines draggable elements and droppable containers:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Drag and Drop Example</title>
<style>
.container { border: 1px solid #ccc; padding: 10px; width: 200px; min-height: 100px; float: left; margin: 10px; }
.item { margin: 5px; padding: 5px; border: 1px solid #000; background: #f0f0f0; cursor: move; }
</style>
</head>
<body>
<div id="container1" class="container" ondrop="drop(event)" ondragover="allowDrop(event)">
<div id="item1" class="item" draggable="true" ondragstart="drag(event)">Item 1</div>
</div>
<div id="container2" class="container" ondrop="drop(event)" ondragover="allowDrop(event)">
<div id="item2" class="item" draggable="true" ondragstart="drag(event)">Item 2</div>
</div>
<script>
function allowDrop(event) {
event.preventDefault(); // Necessary to allow dropping
}
function drag(event) {
event.dataTransfer.setData("text", event.target.id); // Stores the ID of the element being dragged
}
function drop(event) {
event.preventDefault();
const data = event.dataTransfer.getData("text"); // Retrieves the ID of the dragged element
event.target.appendChild(document.getElementById(data)); // Appends the dragged element to the drop target
}
</script>
</body>
</html>
With this setup, elements with the draggable="true"
attribute can be moved around, and containers equipped with ondrop
and ondragover
event handlers can accept these elements.
Utilizing Sortable.js
While the traditional method of implementing drag and drop can be quite verbose, using the Sortable.js library simplifies the process significantly. Sortable.js is a lightweight and flexible library that allows for easy implementation of drag-and-drop features without the need for extensive custom code. It provides a clean and efficient way to manage sortable lists and grids.
Here’s how you can achieve the same functionality using Sortable.js:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Sortable Example</title>
<script src="https://cdn.jsdelivr.net/npm/sortablejs@latest/Sortable.min.js"></script>
<style>
.container { border: 1px solid #ccc; padding: 10px; width: 200px; min-height: 100px; float: left; margin: 10px; }
.item { margin: 5px; padding: 5px; border: 1px solid #000; background: #f0f0f0; cursor: move; }
</style>
</head>
<body>
<div id="container1" class="container">
<div id="item1" class="item">Item 1</div>
</div>
<div id="container2" class="container">
<div id="item2" class="item">Item 2</div>
</div>
<script>
new Sortable(document.getElementById('container1'), { group: 'shared', animation: 150 });
new Sortable(document.getElementById('container2'), { group: 'shared', animation: 150 });
</script>
</body>
</html>
With this implementation, you can easily handle drag-and-drop functionalities in a much more streamlined manner. The Sortable.js library takes care of the heavy lifting, allowing for more focus on building your application's features.
Integrating Drag and Drop Functionality in Oracle APEX
In this guide, we will explore the steps to integrate a drag-and-drop feature in Oracle APEX and create dynamic swimlanes with associated tasks.
Step 1: Add Sortable.js Library
Add the following Sortable.js library link to the JavaScript – File URL section:
![]() |
Fig 2. Adding Sortable JS Library to Page JS File URL |
https://cdn.jsdelivr.net/npm/sortablejs@latest/Sortable.min.js
Step 2: Create a Dashboard Region
Create a “Static Content” region titled Dashboard and paste the following HTML code into it:
<div id="requestdashboard" class="swimlanes"></div>
This <div>
will hold all the swimlanes together. The user interface (UI) should resemble the example shown below:
![]() |
Fig 3. Static Content rendering |
Step 3: Populate the Swimlanes
Next, we need to create the swimlanes dynamically. The number of swimlanes and their associated metadata (such as name) may vary based on different projects or inputs. To achieve this, we will utilize RESTful Data Services to fetch the swimlanes for a given product. This requires creating a REST endpoint to provide the necessary content.
Follow these steps:
- Ensure that your schema is registered with ORDS. For guidance, refer to the Oracle documentation on accessing RESTful services.
- Create a new Module in ORDS. You can modify the parameters as needed, but be mindful that changes will affect the URI structure.
- Create Resource Templates under the newly created module. Select your module in the left pane and click the “Create Template” button.
- Provide a URI Template. I recommend using
getStatus
as the template. Set the Priority to0
and the HTTP Entity Tag Type toSecure Hash
. - Create a Handler or Method. Since we will fetch data without sending any inputs in the body (the Project/Sprint ID filter will be sent via the header), select
GET
. - Click on “Create Handler” and input the following values:
- Source Type: Collection Query
- Pagination Size: 10 (Provide the maximum swimlanes that might be present for a given Project/Sprint)
- SQL Query:
SELECT name, '<div id="'||lower(name)||'swimlane" class="swimlanes__column"> <div class="column_name">'||name||'</div> <ul id="'||lower(name)||'list" class="swimlanes__list jenira_container"></ul> </div>' AS code FROM tms_project_status WHERE project_id = :projectId ORDER BY id
- In the Parameters section, provide the following parameters:
- Name: projectId
- Bind Variable: projectId
- Access Method: IN
- Source Type: HTTP HEADER
- Data Type: INTEGER
- Comments: Gets Project ID to filter Status related to a given project
Now that we have the endpoint ready, you can test it by copying the full URL and applying the projectId
filter in the header.
Note that the SQL Query includes a column called “Code,” which is structured to incorporate classes for creating droppable containers using Sortable.js.
Rendering Swimlanes in the Dashboard
We will now take the “Code” value and insert it into the static element created earlier. Use the following code in the JavaScript Function and Global Declaration:
const printSwimlane = (swimlanes) => {
let swimlanesCount = swimlanes.length;
const dashboardContainer = document.getElementById("requestdashboard");
for(let i = 0; i < swimlanesCount; i++) {
dashboardContainer.innerHTML += swimlanes[i]["code"];
}
};
const createSwimlane = async () => {
const statusUrl = 'replace_this_with_your_url/getStatus?projectId=1';
try {
const response = await fetch(statusUrl);
const data = await response.json();
printSwimlane(data.items);
} catch (error) {
console.error(error);
}
};
createSwimlane();
With this code in place, the UI will now dynamically display the swimlanes as intended.
![]() |
Fig 4. Swimlane Population without UI enhancements |
Enhancing the UI with CSS
To improve the UI further, paste the following inline CSS:
/* Mobile-first layout: Swimlanes are vertically stacked by default */
.swimlanes {
display: flex;
flex-direction: column;
}
/* The swimlanes__column element groups the heading and the list */
.swimlanes__column {
min-width: 15rem;
max-width: fit-content;
flex: 1;
background: #F2F2F3;
padding: 0.5rem;
border-radius: 0.5rem;
margin: 0.5rem;
}
/* Remove ULs inherited margin and padding */
.swimlanes__list {
padding: 0;
margin: 0;
min-height: 90%;
}
/* Style the LIs */
.swimlanes__listItem {
list-style-type: none;
margin: 0.5rem 0;
padding: 1rem;
cursor: move;
background: #FFF;
border: solid blue 0.1px;
border-radius: 0.25rem;
}
/* Change the flex-direction to row for horizontally stacked swimlanes on larger screens */
@media (min-width: 48rem) {
.swimlanes {
flex-direction: row;
overflow-x: scroll;
}
}
/* Style the Swimlane header */
.column_name {
font-weight: bolder;
font-size: large;
}
With these enhancements, your user interface will look more organized and visually appealing.
![]() |
Fig 5. Swimlane Population with UI enhancements |
Step 4: Populate the Tickets
Now, before making the container droppable, let’s populate the tickets as well so that we can test them better. To populate the tickets, let’s follow the same methodology that we used for Swimlane population. We’ll create a new “Resource Template” under the URI Template name – getTickets, and we’ll create a GET handler for the same with the below values.
SQL Query:
SELECT
jd.project_unique_id,
jd.id,
jd.short_title,
jd.priority,
jd.assigned_to,
js.name,
lower(js.name) || 'list' AS target,
'<li id="ticket'||jd.id||'" class="swimlanes__listItem draggable">
<div class="ticket_number" onclick="">'||jd.project_unique_id||'</div>
<div class="ticket_name">'||jd.short_title||'</div>
<div class="item_footer">
<span class="ticket_priority">'||jd.priority||'</span>
<span class="ticket_points">'||jd.estimation||'</span>
<span class="ticket_assignee">'||jd.assigned_to||'</span>
</div>
</li>' AS code
FROM
TMS_TASKS jd
LEFT JOIN
TMS_PROJECT_STATUS js ON jd.status_id = js.id
WHERE
project_sprint_id = :sprintId
ORDER BY
jd.sequence_order
Parameters:
- Name: sprintId
- Bind Variable: sprintId
- Access Method: IN
- Source Type: HTTP HEADER
- Data Type: INTEGER
- Comments: Gets Project ID to filter Status related to a given project
Now, we have the endpoint ready. So, let’s populate ticket data within the Swimlanes. Let’s also replace the createSwimlane()
function with the below code to ensure that we start populating tasks after the swimlanes are created:
Updated JavaScript Functions:
const printSwimlane = (swimlanes) => {
let swimlanesCount = swimlanes.length;
const dashboardContainer = document.getElementById("requestdashboard");
for (let i = 0; i < swimlanesCount; i++) {
dashboardContainer.innerHTML += swimlanes[i]["code"];
}
};
const createSwimlane = async () => {
const statusUrl = 'replace_this_with_your_url/getStatus?projectId=1';
// Get the Swimlane templates in Ticket Details container
try {
const response = await fetch(statusUrl);
const data = await response.json();
printSwimlane(data.items);
} catch (error) {
console.error(error);
}
};
const populateSwimlane = (ticketData) => {
let ticketCount = ticketData.length;
for (let i = 0; i < ticketCount; i++) {
document.getElementById(ticketData[i]["target"]).innerHTML += ticketData[i]["code"];
}
};
const populateTickets = async () => {
const ticketUrl = 'replace_this_with_your_url/getTickets?sprintId=1';
// Get Ticket data to populate in the Swimlanes
try {
const response = await fetch(ticketUrl);
const data = await response.json();
populateSwimlane(data.items);
} catch (error) {
console.error(error);
}
};
Promise.all([createSwimlane()])
.then(() => {
// This will be executed only after createSwimlane has resolved
return populateTickets();
})
.catch((error) => {
console.error(error);
});
UI Preview:
Now, the UI would look like below:
![]() |
Fig 6. Static Swimlane with Tasks |
Now that we have the setup ready, there are only two steps remaining to complete our implementation.
Step 5: Make Tasks Draggable and Containers Droppable
This can be achieved with a small JavaScript function that initializes Sortable.js to make the Swimlane containers "Droppable" and create templates to update tickets dropped into them.
const createDropcontainers = (element) => {
new Sortable(element, {
group: 'shared',
animation: 150,
onEnd: function (evt) {
console.log('Ticket Moved');
},
});
};
When the container becomes “droppable” through the above Sortable.js initialization, all children within it become “draggable” by default. Let’s ensure all containers created are passed into this function. Below is the updated JavaScript function and global variable declaration:
const createDropcontainers = (element) => {
new Sortable(element, {
group: 'shared',
animation: 150,
onEnd: function (evt) {
console.log('Ticket Moved');
},
});
};
//This prints the Swimlanes in UI
const printSwimlane = (swimlanes) => {
let swimlanesCount = swimlanes.length;
const dashboardContainer = document.getElementById("requestdashboard");
for (let i = 0; i < swimlanesCount; i++) {
dashboardContainer.innerHTML += swimlanes[i]["code"];
}
let containers = document.getElementsByClassName("jenira_container");
Array.from(containers).forEach(createDropcontainers);
};
//This Fetches Swimlanes for a given project/sprint
const createSwimlane = async () => {
const statusUrl = 'replace_this_with_your_url/getStatus?projectId=1';
// Get the Swimlane templates in Ticket Details container
try {
const response = await fetch(statusUrl);
const data = await response.json();
printSwimlane(data.items);
} catch (error) {
console.error(error);
}
};
//This populates Swimlanes with Tasks/Tickets
const populateSwimlane = (ticketData) => {
let ticketCount = ticketData.length;
for (let i = 0; i < ticketCount; i++) {
document.getElementById(ticketData[i]["target"]).innerHTML += ticketData[i]["code"];
}
};
//This gets the Ticket details for a given project
const populateTickets = async () => {
const ticketUrl = 'replace_this_with_your_url/getTickets?sprintId=1';
// Get Ticket data to populate in the Swimlanes
try {
const response = await fetch(ticketUrl);
const data = await response.json();
populateSwimlane(data.items);
} catch (error) {
console.error(error);
}
};
//We will create swimlanes first and then populate it with tasks next
Promise.all([createSwimlane()])
.then(() => {
return populateTickets();
})
.catch((error) => {
console.error(error);
});
Now, we have the working drag and drop feature ready! 😊
Step 6: Update Database on Task Movement
Currently, changes made when moving a task are not saved after a page refresh. To enhance user experience, we need to implement AJAX functions to update the database whenever a task is moved across lanes. We will create an Application Process for updating ticket status and a page-level AJAX process for updating ticket sequences.
Note: It's recommended to create AJAX processes at the page level instead of in the Application process. This example follows the Application process for exploration purposes.
Create the Application Process
Navigate to Shared Components > Application Process and click “Create.” Provide the following values:
- Sequence: 1
- Process Point: Ajax Callback: Run this application process when requested by a page process.
- Name: UPDATE TICKET STATUS
- Type: PL/SQL
- Execute Code:
BEGIN
UPDATE tms_tasks
SET status_id = (SELECT id FROM tms_project_status WHERE lower(name) = APEX_APPLICATION.g_x01)
WHERE id = APEX_APPLICATION.g_x02;
END;
Create a Page-Level AJAX Process
Navigate to the page designer of the dashboard page and go to “Process” to create an “AJAX Process” with the following values:
Name: UPDATE TICKET SEQUENCE
DECLARE
v_status_id tms_project_status.id%TYPE;
TYPE t_ticket IS RECORD (
id tms_tasks.id%TYPE,
sequence tms_tasks.sequence_order%TYPE
);
l_ticket t_ticket;
l_ticket_ids APEX_APPLICATION_GLOBAL.VC_ARR2;
l_sequences APEX_APPLICATION_GLOBAL.VC_ARR2;
BEGIN
SELECT id INTO v_status_id
FROM tms_project_status
WHERE lower(name) = APEX_APPLICATION.g_x01;
l_ticket_ids := APEX_APPLICATION.G_F01;
l_sequences := APEX_APPLICATION.G_F02;
FOR i IN 1..l_ticket_ids.COUNT LOOP
l_ticket.id := TO_NUMBER(l_ticket_ids(i));
l_ticket.sequence := TO_NUMBER(l_sequences(i));
UPDATE tms_tasks
SET sequence_order = l_ticket.sequence
WHERE id = l_ticket.id;
END LOOP;
END;
Now, let’s implement the JavaScript functions to update the status and sequence when tasks are moved:
// This function updates the ticket status once it is moved across lanes
const updateStatus = (element, currStatus) => {
let id = element.replace('ticket', '');
let status = currStatus.replace('list', '');
apex.server.process("UPDATE TICKET STATUS", {
x01: status,
x02: id
}, {
dataType: 'text',
success: function() {
// Status updated successfully
}
});
}
// This function updates the sequence of all tickets within a lane
const updateSequence = (currentSeq, currStatus) => {
const status = currStatus.replace('list', '');
const sequenceData = currentSeq.map(item => ({
id: item.id.replace('ticket', ''),
sequence: (item.index + 1) * 10
}));
apex.server.process("UPDATE TICKET SEQUENCE", {
x01: status,
f01: sequenceData.map(item => item.id),
f02: sequenceData.map(item => item.sequence)
}, {
dataType: 'text',
success: function() {
// Sequence updated successfully
}
});
};
Finally, let’s combine everything in the drop container function:
const createDropcontainers = (element) => {
new Sortable(element, {
group: 'shared',
animation: 150,
onEnd: function (evt) {
const currentSeq = Array.from(evt.to.children).map((item, index) => ({ index, id: item.id }));
updateStatus(evt.item.id, evt.item.parentNode.id);
updateSequence(currentSeq, evt.item.parentNode.id);
},
});
};
The final code in the Function and Global Value declaration would look as follows:
//This function is used to update Status of the tasks when moved between lanes
const updateStatus = (element, currStatus) => {
let id = element.replace('ticket','');
let status = currStatus.replace('list','');
apex.server.process("UPDATE TICKET STATUS", {
x01: status,
x02: id
}, {
dataType: 'text',
success: function() {
//console.log('Status Updated!');
}
});
}
// This function is used to update the Sequence of all tickets within a lane
const updateSequence = (currentSeq, currStatus) => {
const status = currStatus.replace('list', '');
const sequenceData = currentSeq.map(item => ({
id: item.id.replace('ticket', ''),
sequence: (item.index + 1) * 10
}));
apex.server.process("UPDATE TICKET SEQUENCE", {
x01: status,
f01: sequenceData.map(item => item.id),
f02: sequenceData.map(item => item.sequence)
}, {
dataType: 'text',
success: function() {
//console.log('Sequence Updated!');
}
});
};
// This function is used to make the Swimlane containers "Droppable"
const createDropcontainers = (element) => {
new Sortable(element, {
group: 'shared',
animation: 150,
onEnd: function (evt) {
const currentSeq = Array.from(evt.to.children).map((item, index) => ({ index, id: item.id }));
updateStatus(evt.item.id, evt.item.parentNode.id);
updateSequence(currentSeq, evt.item.parentNode.id);
},
});
};
const printSwimlane = (swimlanes) => {
let swimlanesCount = swimlanes.length;
const dashboardContainer = document.getElementById("requestdashboard");
for(let i=0;i<swimlanesCount;i++){
dashboardContainer.innerHTML += swimlanes[i]["code"];
}
let containers = document.getElementsByClassName("jenira_container");
Array.from(containers).forEach(createDropcontainers);
};
const createSwimlane = async () => {
const statusUrl = 'replace_this_with_your_url/getStatus?projectId=1';
try {
const response=await fetch(statusUrl);
const data=await response.json();
printSwimlane(data.items);
}
catch(error) {
console.error(error);
}
};
const populateSwimlane = (ticketData) => {
let ticketCount = ticketData.length;
for(let i=0;i<ticketCount;i++){
document.getElementById(ticketData[i]["target"]).innerHTML += ticketData[i]["code"];
}
};
const populateTickets = async () => {
const ticketUrl = 'replace_this_with_your_url/getTickets?sprintId=1';
try {
const response=await fetch(ticketUrl);
const data=await response.json();
populateSwimlane(data.items);
}
catch(error) {
console.error(error);
}
};
Promise.all([createSwimlane()])
.then(() => {
return populateTickets();
})
.catch((error) => {
console.error(error);
});
Now, the drag-and-drop feature is fully operational!
Additional CSS for Enhanced UI
/* mobile-first layout: Swimlanes are vertically stacked to begin with. */
.swimlanes {
display: flex;
flex-direction: column;
}
/* The swimlanes__column element groups the heading and the list */
.swimlanes__column {
min-width: 15rem; /* this is what makes the concept legible when there are many swimlanes */
max-width: fit-content;
flex: 1; /* to keep everything equally sized */
background: #F2F2F3; /* replace with casper color variable */
padding: 0.5rem;
border-radius: 0.5rem;
margin: 0.5rem;
}
/* nuke the ULs inherited margin and padding */
.swimlanes__list {
padding: 0;
margin: 0;
min-height: 90%;
}
/* overwrite the LIs inherited list-style-type, margin and padding and add background and border-radius */
.swimlanes__listItem {
list-style-type: none;
margin: 0.5rem 0;
padding: 1rem;
cursor: move;
background: #FFF;
border: solid blue 0.1px;
border-radius: 0.25rem;
}
/* From 48rem and up we switch the flex-direction to row for horizontally stacked swimlanes */
@media (min-width: 48rem) {
.swimlanes {
flex-direction: row;
overflow-x: scroll;
}
}
/*This is to style the Swimlane's header*/
.column_name{
font-weight: bolder;
font-size: large;
}
/* Style for the container div */
.ticket-details-container {
border: 1px solid #ccc;
padding: 10px;
margin: 10px;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
/* Style for the ticket number */
.ticket_number {
font-size: 18px;
font-weight: bold;
color: #1422dd;
cursor: pointer;
text-decoration: underline;
}
/* Style for the ticket name */
.ticket_name {
font-size: 16px;
margin-top: 5px;
color: #555;
}
/* Style for the item footer */
.item_footer {
margin-top: 10px;
display: flex;
justify-content: space-between;
align-items: center;
}
/* Style for ticket priority, points, and assignee */
.ticket_priority,
.ticket_points,
.ticket_assignee {
padding: 5px 10px;
border-radius: 5px;
}
.ticket_priority {
background-color: #e74c3c; /* Red for priority */
color: #fff;
}
.ticket_points {
background-color: #3498db; /* Blue for points */
color: #fff;
}
.ticket_assignee {
background-color: #2ecc71; /* Green for assignee */
color: #fff;
}
Now, your UI should look like this:
And that’s it! You have successfully created dynamic drag-and-drop functionality in your Oracle APEX application using Sortable.js. Feel free to customize the design further as needed.
If you've made it this far, I truly hope you found this content helpful and informative. It takes a lot of time and effort to create these guides, and your support would mean the world to me. Please consider subscribing to my YouTube channel – Into the Oracle Verse – where I share even more in-depth tutorials and insights on Oracle APEX, SQL, and PL/SQL. Your suggestions and feedback are always welcome to help improve future content. Thank you, and I look forward to connecting with you again on our next topic!
Good luck!
data:post.title

Written by
Published on October 19, 2024
No comments: