Sunday, February 16, 2020

Drag and Drop Regions in Oracle Apex Application



Recently we have worked on a project and we needed to implement drag and drop functionality into a classic report region to enable our users to drag some items and drop them in a drop region. The objective was to give the users a good User Experience when they work on the Application. At that time, we implemented the functionality using some JavaScript and CSS tricks. It worked fine, but it was not the best practice solution to use in Apex. By the time at working in Apex, we have learned some cool features which will make our drag and drop regions more efficient and it was worth to rebuild the regions again.

Now we would like to share our solution with you and show you how we have implemented this cool feature into our Application.

You will need a Workspace to follow with us. If you don't have one, go to apex.oracle.com and request a free Workspace.

To demonstrate the Idea we will work on a simple Project. Suppose we have a To-do list, and we want to change the To-do's status by dragging the Finished To-do from the Open To-do's Region and drop it into the Finished To-do's Region.

Here is the Table which we will work on:

CREATE TABLE TB_TODOS (
ID NUMBER GENERATED ALWAYS AS IDENTITY(START with 1 INCREMENT by 1),
TITLE VARCHAR2(50),
STATUS VARCHAR2(10)
);

Let's insert some dummy data to work with:

INSERT INTO TB_TODOS (TITLE, STATUS) values ('Buy bananas', 'open');
INSERT INTO TB_TODOS (TITLE, STATUS) values ('Buy apple', 'open');
INSERT INTO TB_TODOS (TITLE, STATUS) values ('Go to doctor', 'open');
INSERT INTO TB_TODOS (TITLE, STATUS) values ('Call Mohamad', 'open');
INSERT INTO TB_TODOS (TITLE, STATUS) values ('Go to Bank', 'open');
INSERT INTO TB_TODOS (TITLE, STATUS) values ('Buy orange', 'open');

So with this done let's create a new Project in Apex, navigate to the Project, go to Shared Component, choose Templates then click Create.

We will start by creating a custom template, the template type we need is a Report type and we will create it from scratch.



make sure to choose "Named Column (row template)"



In this template, we will define the structure of our Report.

Each row in the Report will have the structure:

<div>
<li data-id='#ID#' data-status='#STATUS#' class='todo-el' draggable=true>#TITLE#</li>
</div>

but we have to open an Html unordered list tag before all the list elements and close the tag after we finish rendering all the list items, we can achieve that by adding a ul open tag in Before Rows and ul close tag in After Rows like this:


Notice that all the list elements will have the class todo-el, and the unordered list element will have the class todos so we can target those elements in CSS. Also, each list element has the following attributes:
  • data-id: to store custom data in each li element. In this case, we are storing an ID that will be replaced by the ID value of the Todo from our table. we will use this attribute later in Javascript.
  • data-status: to store the To-do's status.
  • draggable: specifies whether an element is draggable or not (Html5).
The unordered list element ul has ondragstart Event attribute that fires when the user starts dragging an element, we will handle this event later in Javascript using handleDrag function.

We can now create a classic report for the Open Todos based on our template.
Navigate to Page1 in our Application and add a new Classic Report region.
For the Report source choose the Table which we have already created:


Notice the Where Clause, we are querying only the Todos that have the status open.
Then go to the Report attributes, under the appearance options choose the template which we have already created:


(optional) Add the following CSS code in the page Inline Css:

.todos{
list-style-type: none;
}
.todo-el {
padding: 10px;
}

the CSS code is simply removing the default list style of the unordered list and it adds some padding to each list element.


Now we create the Finished Todo's Region, to do that we follow the same steps we did to create the Open Todo's Region but for the new Region we want only to show the Finished Todos:



We want to give the Region static Id finished and do some changes in the Layout so the two regions will be displayed next to each other: 



We should now listen to the drop event, to achieve that we will add ondragover and ondrop event attributes for both Region, select both regions, go to Advanced > Custome Attributes and add the code below:
ondragover='handleDragover(event)'
ondrop='handleDrop(event)'

  • ondragover: fires when a draggable element is being dragged over a valid drop target.
  • ondrop: occurs when a draggable element is dropped on a valid drop target.


With this done, we can now start handling the drag and drop events...

First, let's handle the drag functionality by defining the handledrag function at the page level. Copy the code below into the Global Functions in your page:
function handleDrag(event) {
var data = {id: event.target.dataset.id, status: event.target.dataset.status};
var json_data = JSON.stringify(data);
event.dataTransfer.setData('data', json_data);
}
in this function we are simply fetching our data from the event object, creating a JSON object from this data and converting the JSON object to a string, then we are setting the drag operation's data using setData method.

Before we handle the drop event we should create an Ajax callback process which we will call when we drop a Todo item. The process then will update the Todo's status in the Database.
Go to the page processes and create an Ajax Process:
Add the code below:
IF apex_application.g_x01 = 'open' THEN
UPDATE TB_TODOS SET STATUS = 'finished' WHERE ID = apex_application.g_x02;
ELSE
UPDATE TB_TODOS SET STATUS = 'open' WHERE ID = apex_application.g_x02;
END IF;
the g_x01,g_x02 are a Global variable and they will be passed from the Javascript.

Now go back to the page's global function and add the code below to handle the drop event:
function handleDragover(event){
event.preventDefault();
}
function handleDrop(event){
var id = JSON.parse(event.dataTransfer.getData('data')).id;
var status = JSON.parse(event.dataTransfer.getData('data')).status;
apex.server.process(
'Update Todo',
{
x01: status,
x02: id
},
{
success: function(){
apex.event.trigger('#open', 'apexrefresh');
apex.event.trigger('#finished', 'apexrefresh')
},
dataType: 'text'
});
}
in handleDrop function we are parsing the string data that we have sent back into JSON object and then accessing the id and status values. In the next step we are making an Ajax request to update the Todo's status, after the Ajax request is successfully finished we are refreshing the regions to get the updated data.

So that's it :), we hope you liked it. In the next post, we will configure the functionality to make it works on the Touch screen devices.
Here is a demo application if you want to test the functionality demo.
You can find the source code on Github.

we also have published a video on Youtube while we are creating a demo application, you can also watch it:



Some References:

Labels: , ,

7 Comments:

At February 17, 2020 at 10:12 AM , Blogger BoneCracker said...

thank you for the great tutorial :)
I am looking forward to more!

 
At February 17, 2020 at 3:12 PM , Blogger goran said...

This comment has been removed by the author.

 
At February 17, 2020 at 3:12 PM , Blogger goran said...

Thanks for this tutorial.
I encountered one problem. Every drag and drop no matter where you drop it results with update. If dragged and dropped in the same div, it will always execute update.

 
At February 17, 2020 at 10:32 PM , Blogger Mohamad Bouchi said...

Hi Goran,
Thank you for reporting the problem. The solution is simply to add a different handleDrop function for each Region and check the value of the todo's status when you drop it.
This will be the drop handler when you drop an item into the finished region:

function handleDropFinished(event){

var id = JSON.parse(event.dataTransfer.getData('data')).id;
var status = JSON.parse(event.dataTransfer.getData('data')).status;

if(status === 'open'){
apex.server.process(
'Update Todo',
{
x01: status,
x02: id
},
{
success: function(pData){
apex.event.trigger('#open', 'apexrefresh');
apex.event.trigger('#finished', 'apexrefresh')
},
dataType: 'text'
}
);
}
}

Don't forget to change the ondrop handler in the region attribute. And do the same for the Open Region.

 
At March 13, 2020 at 6:14 PM , Anonymous Anonymous said...

Thanks alot for this tutorial , how to implement order in this scenario.

 
At May 13, 2020 at 2:28 PM , Blogger FranciscoG said...

Thank you very much for sharing, they are very good and very useful.

Cheers

 
At February 27, 2023 at 3:28 AM , Anonymous Anonymous said...

Many Thanks, All working fine.
Dear, I need your support why this drag and drop not working in Touch Punch. I need your guidance ASAP.

 

Post a Comment

Note: Only a member of this blog may post a comment.

Subscribe to Post Comments [Atom]

<< Home