dinsdag 27 november 2012

AJAX inside a tabular form

Ok, this I found to be a nice APEX puzzle. Consider the following use-case:


An order input form is considered. It is a tabular form containing two functionally related colums. One is a selectlist, listing available articles, the other is showing the default price of the item, as defined in a lookup table.

The goal is, when the user selects an article, the price is updated via an AJAX call, hence without a complete page refresh. Sounds like a fair requirement!


here you'll find a working demo.


The problem is, that one cannot simply define a dynamic action solving the above(that is, I could'nt think of one). To solve it however, it is really a matter of identifying the source element on the page (the article) and the target element (the price) which should be updated. When working on a `static' page this is not too hard, but a tabular form should be considered more dynamic.

In APEX tabular-form input fields have the following format:

<input type="text" name="f04" size="16" 
       maxlength="2000" value="30"  id="f04_0001" autocomplete="off">

One could imagine a tabular form to be like a mathematical matrix, that is an ordered object with rows and colums that can be numbered in a countable fashion. On the DOM the columns are identified by the name attribute, and the rows are numbered in order of appearance. The element of interest is, ofcourse, identified by the value of the id attribute and is situated at the intersection of row and column. In the above example id = f04_0001, hence column 4 row 1. The id attribute is exactly in this format:

 fxx_yyyy, that is column_row
Now that we can identify the source element, we should identify the target element. Remeber that like the source element, the target element is also of the format fxx_yyyyy, where at most the x's differ in value. What we can do is add the html attribute class with value `price' (report attributes > column attributes) to the elements concerned, giving means to determine the column value fxx of the target element.

<input type="text" name="f06" size="16" class="price"
       maxlength="2000" value="30"  id="f06_0001" autocomplete="off">

Next, onchange of the source element, we want to update the target element. This is easy. Go to: Column attributes > element attributes of the atricle field in the tabular form and type:

onchange="updatePrice(this.id)"

So, onchange we call the JavaScript function updatePrice with argument this.id. `this' is the object in context, hence, here the element that is changed. Now we almost have all the ingredients we need. Next is defining an AJAX call, taking the id as input, and returning the price. In APEX we define two objects. The first is application item g_art_id, the second is an application process getPrice.


getPrice has the following listing:
declare 
 l_prijs artikelen.prijs%type;
begin
 select 
       art.prijs into l_prijs 
 from  artikelen art 
 where art.id = :g_art_id;
 --
 sys.htp.p( l_prijs );
exception when no_data_found then
 sys.htp.p( 'Article not found' );
end;

Please observe that the name of the application process is case sensitive and do take care of trailing blanks... The following JavaScript should be placed in the header of the page:
<script type="text/javascript">
//
// updatePrice takes a source_id (article), executes an AJAX call and 
// updates the DOM.
//
function updatePrice(source_id)
{
//
var c_target_class = 'price';
var l_target_id    = getTargetRowId(source_id, c_target_class);
var l_art_id       = getValue(source_id); 
//
setPrice(l_art_id, l_target_id);
//
return null;
};
//
// getTargetRowId determines the target_row_id 
//
function getTargetRowId(source_row_id, target_class){
//
var l_name;
var l_regexp;
var l_targetrowid;
//
// given the class attribute, determine which fxx column to use
// Result of the query below is the name of the inputfield in 
// the target class
//
l_name        = $('.' + target_class).first().attr('name');
//
// source_row_id has the format fxx_yyyyy, where xx refers to the column.
// The target_id is computed by replacing the xx with the relevant value.
// We use a regular expression
//
l_regexp      = /f[0-9]+_/;
l_targetrowid = source_row_id.replace(l_regexp, l_name + '_');
//
// Result is the id of the target input-field
//
return l_targetrowid;
//
};
//
function getValue(id){
return $('#' + id ).val();
};
//
// setPrice takes a primary key value of an article
// and determines the price via an AJAX call.
//
// Second argument is the id on the DOM of the inputfield which should 
// be updated.
//
function setPrice(art_id, target_el_id){
// 
// First initialize the object which set's up the AJAX call
//
var get = new htmldb_Get(null,html_GetElement('pFlowId').value,
                         'APPLICATION_PROCESS=getPrice',0);

//
// Add an attribute and value to the call.
//
get.add('G_ART_ID',art_id);
//
// Use the get method to execute the AJAX call
//
var gReturn = get.get();
//
// Update the DOM. $x is an APEX javascript API.
//
$x(target_el_id).value = gReturn;
//
// clean up...
//
get     = null;
gReturn = null;
//
return null;
};
</script>

Done! Give it a try here . To sum up: it all boils down to identifying a source element, give it a triggering condition, do something, identifying and finallly updating the target element.

Wow, sounds almost like a dynamic action!

6 opmerkingen:

  1. Hello!
    In the article you mentioned


    input type = " text " name = " f06 " size = " 16 " class = " price " maxlength = " 2000 " value = " 30 " id = " f06_0001 " autocomplete = " off "

    However in my FireBug, when I want to see the class, there is no class...

    input type = " text " name = " f05 " size = " 16 " maxlength = " 2000 " value = " 1.00 " onchange = " calculateTotal(this); " id = " f05_0001 " autocomplete = " off "

    But there is only..

    label for="f05_0001" class="hideMeButHearMe" PRICE < / label >

    Does this mean that the class target in JavaScript is 'hideMeButHearMe'... By the way all the columns in tabular form have this class name.

    Please help me out.

    Thank you in advance.

    BeantwoordenVerwijderen
  2. In firebug you'll see the following:

    &lt. td headers="SOMENAME" &gt.
    &lt.label class="hideMeButHearMe" for="f02_0001"&gt.LABELNAME&lt./label&gt.
    &lt.input id="f02_0001" type="text" value="29213" maxlength="2000" size="16" name="f02" autocomplete="off"&gt.
    &lt./td&gt.

    The trick is to add the class to the INPUT element, NOT the LABEL element. Goto report attributes > column attributes of the column concerned.

    Does this help?

    BeantwoordenVerwijderen
  3. Deze reactie is verwijderd door een blogbeheerder.

    BeantwoordenVerwijderen
  4. Life saving tip. I don't know why such a basic functionality like lookup value isn't integrated in Apaex.

    BeantwoordenVerwijderen
  5. Hi,
    i tried to run demo app on https://apex.ordina.nl/pls/apexont41/f?p=144

    but i get error

    404 Not Found

    The requested URL /pls/apexont41/f was not found on this server.

    Can i view demo app?

    Can you send me a copy?

    Many thanks.
    km

    BeantwoordenVerwijderen