Implementing asmSelect in Oracle APEX

2009-02-11 19:00:00 -0500


I’ve been a big fan of jQuery for some time now, and one of my favorite plug-ins is Ryan Cramer’s asmSelect, which is a really cool alternative implementation of a multiple select control. There’s no swing tables, there’s no explaining to users how they need to hold down control (or command for mac users) while they click their desired options. There’s just a nice manageable list, to which you add selections using a drop down. There’s a nice example here, if you’re unfamiliar with this interface.

In any event, some of our clients were not really digging the Shuttle control available in Oracle’s Application Express. “Shuttle” is really just a pair of swing tables. So I figured, “why not set those back to normal select[multiple] elements and run the asmSelect plugin?” However, it wasn’t that easy, so I’ve written up how to get it going. I’m working in APEX 3.1.2, if you decide to follow along.

First you need to make sure you’ve got the select element set to multiple in the interface:

Apex Select Multiple Item

Next, add the needed jQuery libs to your application. There are a number of ways to do this, but since I’m not going for anything fancy, I went with the latest version of jQuery (1.3), the latest version of the asmSelect plugin, the included CSS, and avoiding any jQuery UI animation options:

  • jquery-1.3.1.min.js
  • jquery.asmselect.js
  • jquery.asmselect.css
  • jquery.noconflict.js

That last one is actually a quick little file I threw together to allow for jquery to be compatible with apex’s built-in use of prototype:

/ jquery no-conflict script, sets $() function to $j() /var $j = jQuery.noConflict();

Not much to it, pretty standard.

I attached them to the application itself (Shared Components → Static Files → Create, then select your app for the association). Once they are uploaded, you need to link them in from your page template. Head over to Shared Components → Templates and select your default page template for editing. You can jam this into the head section of the document template:


<link rel="stylesheet" href="#APP_IMAGES#jquery.asmselect.css" type="text/css" />
<script src="#APP_IMAGES#jquery-1.3.1.min.js" type="text/javascript"></script>
<script src="#APP_IMAGES#jquery.asmselect.js" type="text/javascript"></script>
<script src="#APP_IMAGES#jquery.noconflict.js" type="text/javascript"></script>

These statements will include the javascript and css files we uploaded into our app as static files. Below these, in the header template (or in the html header area of a specific page), we can add a script block and some code to activate the asmSelect plugin on every select[multiple] on our page:


<script type="text/javascript">
$j(document).ready(function() {
// wire up the select multiples to be asmSelects
$j("select[multiple]").asmSelect({ });
});
</script>

At this point, if you reload your application page, having saved your changes and such and there are no errors in the setup, you’ll see:

  1. the original select show up and quickly disappear (asmSelect hides it)
  2. the new asmSelect element

However, if you attempt to submit your form, it will explode on you. You’ll get a pretty heinous 404 error message saying that the page couldn’t be found. What’s happening here?


Not Found
The requested URL /pls/apex/wwv_flow.accept was not found on this server.

Oracle-Application-Server-10g/10.1.3.4.0 Oracle-HTTP-Server Server at apex.example.com Port 80

APEX is at heart a set of complex stored procedures made accessible over the web via mod_plsql running in Apache. If you’ve ever written a stored procedure for mod_plsql (htp.prn and all that), you’ve probably run into this problem before: if you send any params that the procedure doesn’t expect, mod_plsql will bomb out with a 404 error.

What’s happening to us here is that by using asmSelect we’ve added a parameter that’s not being expected by APEX. Every form element on the apex page matches an ITEM that you created on the page (like P1_PRODUCT_TYPE above). In this case, asmSelect hides the element represented by P1_PRODUCT_TYPE and drops select#asmSelect0 right next to it in the DOM.

So what do we do?

What can be added to the DOM can be taken away!

I tried faking out APEX by creating a corresponding page-level item with the same name and that didn’t work, so I just moved on to the next obvious thing: remove the asmSelect element when the form’s submit element fires. Here’s how I first tried it:


<script type="text/javascript">
$j(document).ready(function() {
// wire up the select multiples to be asmSelects
$j("select[multiple]").asmSelect({ });
$j("#wwvFormFlow").submit(function() {
$j(".asmSelect").remove();
});
});
</script>

That didn’t quite work out, because APEX already has its own ideas about the form’s submit event. And honestly, I don’t really want to mess with APEX, I just want to work around it. So I decided on another trick we worked out some time ago, intercepting the submit event and then letting APEX do its thing afterward:


<script type="text/javascript">
$j(document).ready(function() {
// wire up the select multiples to be asmSelects
$j("select[multiple]").asmSelect({ });

// subordinate the submit function in apex so we can supplant it
function submitOverride(event) {
// remove the asmSelects from the DOM so they don't mess up submitted params
$j(".asmSelect").remove();

// execute the original submit
this._submit();
}
document.wwv_flow._submit = document.wwv_flow.submit;
document.wwv_flow.submit = submitOverride;

});
</script>

This really did the trick, removing the asmSelect right after submit fires, sending up the correct form elements. And since asmSelect manages the original selection list as your are making changes, the selected items are preserved.

For one last bit of finesse, hide the original select element (in our case the item P1_PRODUCT_TYPE) on page load by setting style="display:none;" on the element, which makes your page load a bit smoother:

Apex hide selected item

Works like a charm!

Salesperson Criteria