Thursday, February 14, 2013

Tabular form: select elements with jQuery

One of the most frequent issues i see on the Apex forums is having to deal with jQuery selectors. Honestly, I don't believe this is hard at all.
By far the most important thing to grasp is understanding HTML. What element represents what i see on my screen? This is simply finding out what HTML apex has generated for your page.

First of all though, this is not a total tutorial on how to get anything you want. Go and read this jQuery tutorial to start you off!
http://docs.jquery.com/Tutorials:How_to_Get_Anything_You_Want_2
To better understand selectors and what is possible or what i use, refer to the api documentation which will do a far better job of explaining and demonstrating than i would.
http://api.jquery.com/category/selectors/

I'll be using Firefox and Firebug, and that's what you'll see in the couple of screenshots. Look, if you are serious about developing for the web and you'd like to understand what you're doing, then having a browser with decent tools is a requirement. For me, that is Firefox with the Firebug extension. I'm sure Chrome can work great for you too, but here I'm using Firefox. As for IE: I'll only ever open up the debug tools there when I really can't fix or reproduce in another browser.
Every Apex developer who wants to step into some javascript or jQuery development or debug his dynamic actions, investigate mark-up, investigate applied CSS, inspect AJAX calls,... needs to get this stuff sorted. Don't put it besides, beneath or above you.
You can find more information about Firebug here: http://getfirebug.com/whatisfirebug

Now to take a common problem as an example: targetting items in a tabular form. I created a tabular form on EMP with this SQL:
select 
"EMPNO",
"EMPNO" EMPNO_DISPLAY,
"ENAME",
"HIREDATE",
"SAL",
"DEPTNO",
NULL CHECKME
from "#OWNER#"."EMP"

tabform_overview
  • EMPNO: hidden
  • ENAME: popup lov, with query based lov:
    SELECT ename d, ename r FROM emp ORDER BY ename;
  • HIREDATE: date field
  • DEPTNO: select list based on a query:
    SELECT dname d, deptno r FROM dept ORDER BY dname;
  • CHECKME: simple checkbox, with values "Y,"
(This form isn't meant to do anything but serve a demonstration purpose.) What i want to do is to construct a jQuery selector which will match the text items in the "Salary" column. To be able to do that, I need to know what the markup looks like, and where in the markup these items are situated. Right-clicking the first SAL-item in the form and selecting "Inspect element with Firebug" will open up Firebug (if it wasn't already) and show the HTML tab. The selected (right-clicked) element will be highlighted in the HTML.
tabform_sal_select
This is a piece of the HTML here, and the input element will be highlighted:
<tr class="highlight-row">
   <td class="data" headers="CHECK$01">
   <td class="data" headers="EMPNO_DISPLAY">7839</td>
   <td class="data" headers="ENAME">
   <td class="data" headers="HIREDATE">
   <td class="data" headers="SAL">
      <label class="hideMeButHearMe" for="f05_0001">Sal</label>
      <input id="f05_0001" type="text" value="5001" maxlength="2000" size="16" name="f05" autocomplete="off">
   </td>
   <td class="data" headers="DEPTNO">
   <td class="data" headers="CHECKME">
</tr>
When you hover over an element in this HTML section, the element will be highlighted on the actual page too!
tabform_sal_select_firebug
So that is one element. an INPUT in a TD element in a TR element. A TD element is a table CELL, a TR element is a table ROW. To take this a bit further, i expanded a second TR element (thus: another row).
<table class="report-standard" cellspacing="0" cellpadding="0" border="0" summary="">
   <tbody>
      <tr>
      <tr class="highlight-row">
      <tr class="highlight-row">
      <tr class="highlight-row">
         <td class="data" headers="CHECK$01">
         <td class="data" headers="EMPNO_DISPLAY">7782</td>
         <td class="data" headers="ENAME">
         <td class="data" headers="HIREDATE">
         <td class="data" headers="SAL">
            <label class="hideMeButHearMe" for="f05_0003">Sal</label>
            <input id="f05_0003" type="text" value="2450" maxlength="2000" size="16" name="f05" autocomplete="off">
         </td>
         <td class="data" headers="DEPTNO">
         <td class="data" headers="CHECKME">
      </tr>
      <tr class="highlight-row">
         <td class="data" headers="CHECK$01">
         <td class="data" headers="EMPNO_DISPLAY">7566</td>
         <td class="data" headers="ENAME">
         <td class="data" headers="HIREDATE">
         <td class="data" headers="SAL">
            <label class="hideMeButHearMe" for="f05_0004">Sal</label>
            <input id="f05_0004" type="text" value="2975" maxlength="2000" size="16" name="f05" autocomplete="off">
         </td>
         <td class="data" headers="DEPTNO">
         <td class="data" headers="CHECKME">
      </tr>
      <tr class="highlight-row">
How about constructing a selector then. Well, tabular forms will generate their markup wtih the column header specified in the headers attribute of each data cell. Since there is no real way to select a certain type of column (there are row elements, but no column elements evidently) we can gather all the cells which have a matching headers attribute to the header I'm looking for.
To check a selector (or run any piece of javascript code) you have to switch to the "Console" tab in Firebug. The first time you go there, you will have the command editor in single line mode. I prefer to have it in multi-line myself, especially when running a block of code.
To target the cells I want I can use this selector:
td[headers='SAL']
You don't just run this selector of course. The selector is used in jQuery, so wrap it accordingly:
$("td[headers='SAL']")
This will tell jQuery to fetch all elements who match this selector. It does not have to be a single element, as multiple matching elements will result in an array. After hitting run you will get the output in the console output. You can tell something is an array when it is between brackets ("[...]"). Now when you hover over each element in the array each will also be highlighted on the page; or when hovering over the array without targetting a specific element, all elements in the array will be highlighted on the page.
tabform_select_array_firebug
And again, I want to stress how important it is to know your HTML. The SAL field was a simple text-type input item. A checkbox is also an input item, but is type=checkbox. And a select list is a SELECT element. This is important for your selectors of course. In the next code excerpt you can see the items for column DEPTNO and CHECKME, respectively a select list and a checkbox input.
<td class="data" headers="DEPTNO">
   <label class="hideMeButHearMe" for="f06_0001">Deptno</label>
   <select id="f06_0001" name="f06" autocomplete="off">
</td>
<td class="data" headers="CHECKME">
   <label class="hideMeButHearMe" for="f07_0001">Checkme</label>
   <input id="f07_0001_01" type="checkbox" onclick="if (this.checked) {apex.jQuery('#f07_0001').val('Y');} else {apex.jQuery('#f07_0001').val('');}" value="Y" name="f07_NOSUBMIT" autocomplete="off">
A last thing to mention is that apex will append all hidden items to the last visible input element (or select list). If you hide columns in a tabular form, the elements will be rendered to the page, albeit as a hidden input element. For example, take my column EMPNO which is hidden. This is the item in array f02 below. But the item is appended in column CHECKME, it does NOT have an own column!
<td class="data" headers="CHECKME">
   <label class="hideMeButHearMe" for="f07_0001">Checkme</label>
   <input id="f07_0001_01" type="checkbox" onclick="if (this.checked) {apex.jQuery('#f07_0001').val('Y');} else {apex.jQuery('#f07_0001').val('');}" value="Y" name="f07_NOSUBMIT" autocomplete="off">
   <input id="f07_0001" type="hidden" value="" name="f07" autocomplete="off">
   <input id="f02_0001" type="hidden" value="7839" name="f02" autocomplete="off">
   <input id="fcs_0001" type="hidden" value="8FA19154DC35EFF7C74623AE376FD8E9" name="fcs" autocomplete="off">
   <input id="frowid_0001" type="hidden" value="AAAWFmAAjAAAGA7AAA" name="frowid" autocomplete="off">
   <input id="fcud_0001" type="hidden" value="U" name="fcud" autocomplete="off">
</td>
Once you're familiar with the HTML markup that is generated, you won't go back to check each and every time of course. But keep in mind that different themes and templates can affect your selectors, as in: not working anymore. That is a good time to inspect your HTML once again and figure out what is wrong, not in the least by running your selector from the console.

Tuesday, February 12, 2013

Checkbox in tabular form column header

Some javascript code to add a checkbox in the header(s) of a tabular form, which will check/uncheck all checkboxes in a column.
/**
  * Adds a checkbox in the column header of the column identified by pColumnHeader
  * Checking and unchecking this checkbox will check or uncheck all visible 
  * checkboxes in the column
 **/
function addHeaderCheckbox(pColumnHeader){
   $("<input type='checkbox' id='"+pColumnHeader+"_chk_all_rows' />")
   .bind("click", function(){
      $("td[headers='"+pColumnHeader+"'] input[type='checkbox']:visible")
      .prop("checked", $(this).prop('checked'));
   })
   .prependTo("th#"+pColumnHeader+"");
};
Example situation:
Tabular form based on SQL:
select 
"EMPNO",
"EMPNO" EMPNO_DISPLAY,
"ENAME",
"HIREDATE",
"SAL",
"DEPTNO",
NULL checkme
from "#OWNER#"."EMP"
Where the CHECKME column is displayed as a "Simple Checkbox", values "Y,"
Since the headers are also replaced when the region is refreshed or paginated, the checkbox will have to be added to the form after each refresh. Dynamic action, "After refresh", on tabular form region.
As true action select "Execute Javascript Code", and make sure to check "Fire on Page Load".
addHeaderCheckbox("CHECKME");
As discussed in OTN thead all Check box check / uncheck in tabular form.