Declarative ReFlow Table Reports in Theme 25
A while ago, at KScope 14 in New Orleans, I presented on Responsive Web Design with APEX Theme 25. One of the topics of my presentation was the lack of responsiveness of standard reports in Oracle Application Express. Meanwhile there were several attempts/solutions to make Theme 25 reports more responsive.
One approach is to add custom CSS code for those columns to hide on specific device sizes, using @media queries:
Major disadvantage of this approach is, that the user is getting less information on smaller devices. Instead of just hiding the data, it would be much nicer to present the data in a different, appropriate way, like rearranging the data in a fashion it fit on the small screen and/or displaying it optionally (on user demand). One technique to realize this is called “Reflow Tables”.
A reflow table works by collapsing the table columns into a stacked presentation that looks like blocks of label/data pairs for each row. One particular nice implementation of this technique is the FooTable jQuery plugin.
Recently Dimitri Gielis published a solution/demo based on this jQuery plugin. Using his approach, you will have to add a custom report template for each report you want to responsify. The problem with the FooTable plugin is: the definition of which column to “reflow” on a specific resolution has to be placed in the table header, which is not accessible in APEX by means of declarative parameterization (hu????). Plus, the plugin expects the report table to be a direct child element of a DIV element, which does not apply for standard APEX report templates, where the actual data table is enclosed by another table, that is used for positioning pagination elements and buttons. So I see the point in creating a separate report template, as Dimitri did, but you are losing a lot of the standard APEX functionality like this.
I tried a different approach: modify the FooTable JavaScript and CSS, so it will work with the standard APEX report template. My main goal was, to be able to declaratively configure any (Theme 25) classic report to be responsive, which means:
- add a possibility to use the Report Attributes > Layout and Pagination > Report Attribute Substitution to indicate/trigger that a table uses the reflow mechanism
- possibility to add classes to Report Attributes > Column Attributes > Column Formatting > CSS Class to hide/reflow columns depending on device/screen width (remember: the Theme 25 grid is basically a number of fixed width grid definitions)
- provide/initialize the FooTable plugin with the Theme 25 grid steps and define appropriate class names for these
Here is the result (live demo):
How it works
Warning: this solution does not work with Partial Page Refresh enabled (out-of-the-box). This could be done using an dynamic action to reinitialize the ApexFooTable each time a PPR occurs for the report region, calling the JavaScript function initFoo();
Installation:
- Download the zip containing all necessary resources here
- Unzip the content and place the apexfootable folder somewhere on your server
- add the following URL references to your page, or even better, your page template:
Configuration:
Create your classic report (or take any existing classic report) and configure the reflow functionality by adding “data-reflow” to the report’s Layout and Pagination property:
Add one of the defined “hide-” classes to every column you want to hide for a certain screen size (see reference table below):
Available ApexFooTable hide- classes are (derived from Theme 25 grid @media queries):
Class Name | breakpoints/screensize when column will be hidden |
hide-all | column will always be hidden and information placed in reflow row |
hide-desktop-l | < 1900 px |
hide-desktop-m | < 1660 px |
hide-desktop-s | < 1420 px |
hide-desktop-xs | < 1260 px |
hide-tablet, hide tablet-l | < 1025 px (iPad/tablet Landscape) |
hide-tablet-p | < 960 px (tablet Portrait) |
hide-phone, hide-phone-l | < 768 px (smartphone Landscape) |
hide-phone-p | < 480 px (smartphone Portrait) |
That’s it. Give it a try. If you encounter any problems, let me know.
Thank you this was great.
ReplyDeleteTwo things.
1. It seems like your live example doesn’t work.
2. I downloaded your code and everything works great except that it quit working after sorting the grid. (Clicking on a column heading). Is this a partial refresh issue?
Sincerely
Arild
Hi Arild,
DeleteI fixed the online demo. Everything works now (I think).
Yes, there still is an issue with Partial Page Refresh. You will have to disable PPR to use the reflow functionality for now.
Thanks,
Christian
One more question.
ReplyDeleteHow do you get the + and - signi in front of each row?
Arild
It's done with an additional font and some CSS magic ;-)
Delete
ReplyDeleteThank you for answering.
I was able to get the + and - in front of each row to work.
My problem was the location of the fonts, I have mine in the Workspace Image folder.
But I have another problem. I have one page with several classical reports, and the reflow only works for the first report.
Can you think of anything that is causing the problem?
Arild
Hi I posted a message here last week, but it hasn’t shown up yet.
ReplyDeleteAnyway, got the + and – to work. My problem was the location of the fonts.
Another problem is that the code only seems to work on the first table for each page.
Jquery and Web development is completely new to me. I am an old C# Windows Forms and database developer.
But I played around with the code and made some changes to the initFoo function and it now seems to work.
This is probably not the right way to do it so your comments are appreciated:
function initFoo() {
var myTable = $("table[data-reflow] table.uReport tbody"),
myTableRow;
myTableRow = myTable.find('tr:first td span[class^="hide-"]');
myTableRow.each(function (index, value) {
var dataAtt = '';
if ($(this).hasClass("hide-all")) {
dataAtt = 'all'
} else if ($(this).hasClass("hide-phone-p")) {
dataAtt = 'phonep'
} else if ($(this).hasClass("hide-phone-l")) {
dataAtt = 'phonep,phone'
} else if ($(this).hasClass("hide-phone")) {
dataAtt = 'phonep,phone'
} else if ($(this).hasClass("hide-tablet-p")) {
dataAtt = 'phonep,phone,tabletp'
} else if ($(this).hasClass("hide-tablet-l")) {
dataAtt = 'phonep,phone,tabletp,tablet'
} else if ($(this).hasClass("hide-tablet")) {
dataAtt = 'phonep,phone,tabletp,tablet'
} else if ($(this).hasClass("hide-desktop-xs")) {
dataAtt = 'phonep,phone,tabletp,tablet,desktopxs'
} else if ($(this).hasClass("hide-desktop-s")) {
dataAtt = 'phonep,phone,tabletp,tablet,desktopxs,desktops'
} else if ($(this).hasClass("hide-desktop-m")) {
dataAtt = 'phonep,phone,tabletp,tablet,desktopxs,desktops,desktopm'
} else if ($(this).hasClass("hide-desktop-l")) {
dataAtt = 'phonep,phone,tabletp,tablet,desktopxs,desktops,desktopm,desktopl'
}
if (dataAtt != '') {
$("th#" + $(this).parent().attr("headers")).attr("data-hide", dataAtt);
}
});
$("table[data-reflow] table.uReport").footable({
breakpoints: {
phonep: 479,
phone: 767,
tabletp: 959,
tablet: 1024,
desktopxs: 1259,
desktops: 1419,
desktopm: 1659,
desktopl: 1899
}
});
}
Sincerely
Arild Sunde
Hi Arild,
ReplyDeleteIf it works for you, OK. The problem was: the initFoo() function didn't iterate over more than one table. My general solution would be:
function initFoo() {
$('table[data-reflow] table.uReport').each(function(i,v) {
$(this).find('tbody tr:first td span[class^="hide-"]').each(function (index, value) {
var dataAtt = '';
if ($(this).hasClass("hide-all")) {
dataAtt = 'all'
} else if ($(this).hasClass("hide-phone-p")) {
dataAtt = 'phonep'
} else if ($(this).hasClass("hide-phone-l")) {
dataAtt = 'phonep,phone'
} else if ($(this).hasClass("hide-phone")) {
dataAtt = 'phonep,phone'
} else if ($(this).hasClass("hide-tablet-p")) {
dataAtt = 'phonep,phone,tabletp'
} else if ($(this).hasClass("hide-tablet-l")) {
dataAtt = 'phonep,phone,tabletp,tablet'
} else if ($(this).hasClass("hide-tablet")) {
dataAtt = 'phonep,phone,tabletp,tablet'
} else if ($(this).hasClass("hide-desktop-xs")) {
dataAtt = 'phonep,phone,tabletp,tablet,desktopxs'
} else if ($(this).hasClass("hide-desktop-s")) {
dataAtt = 'phonep,phone,tabletp,tablet,desktopxs,desktops'
} else if ($(this).hasClass("hide-desktop-m")) {
dataAtt = 'phonep,phone,tabletp,tablet,desktopxs,desktops,desktopm'
} else if ($(this).hasClass("hide-desktop-l")) {
dataAtt = 'phonep,phone,tabletp,tablet,desktopxs,desktops,desktopm,desktopl'
}
if (dataAtt != '') {
$("th#" + $(this).parent().attr("headers")).attr("data-hide", dataAtt);
}
})
});
$("table[data-reflow] table.uReport").footable({
breakpoints: {
phonep: 479,
phone: 767,
tabletp: 959,
tablet: 1024,
desktopxs: 1259,
desktops: 1419,
desktopm: 1659,
desktopl: 1899
}
});
}
I'll update the downloadable sources asap. Thanks for the comment and testing.
Regards,
Christian
Thanks that is what I noticed, that it only iterates through the first table.
ReplyDeleteArild
The zip with the updated Javascript is updated. Please download the new version.
Delete