Right of the bat: this will only work with wizard generated tabular forms and not with manual tabular forms.
My goal was to lessen dependance on the f## arrays in the tabular forms, which especially in javascript can be outright frustrating. Until i used this code, i usually declared some global params and commented a lot about how each array maps to which column. Only to then afterward having to switch the column around with another one, or add one in, and find the code just doesn't work anymore because of the f-arrays.
var gEmpno = 'f01', gEname = 'f05', gSal = 'f06';
$('input[name="f04"][type="text"]:visible')Oh no. A column had to be added between 3 and 4! Renumber all javascript code.:-( Switched column order in the report attributes? Renumber all code :-(
My code works with the mapping which apex generates when it renders a tabular form. As an example, i have tabular form based on EMP:
select "EMPNO", "EMPNO" EMPNO_DISPLAY, "ENAME", "JOB", "MGR", "HIREDATE", "SAL", "COMM", "DEPTNO" from "#OWNER#"."EMP"This the query generated by apex when selecting the EMP table as source and indicating EMPNO is the primary key (no rowid used).
I only altered column DEPTNO to be a select list instead of a textfield, so it would show the actual department names and so i would have select list in my tabular form.
When the page is runned, then below the tabular form HTML an additional mapping will be rendered:
<input type="hidden" id="fmap_001" value="EMPNO" name="fmap"> <input type="hidden" id="fhdr_001" value="Empno" name="fhdr"> <input type="hidden" id="fmap_002" value="ENAME" name="fmap"> <input type="hidden" id="fhdr_002" value="Ename" name="fhdr"> <input type="hidden" id="fmap_003" value="JOB" name="fmap"> <input type="hidden" id="fhdr_003" value="Job" name="fhdr"> <input type="hidden" id="fmap_004" value="MGR" name="fmap"> <input type="hidden" id="fhdr_004" value="Mgr" name="fhdr"> <input type="hidden" id="fmap_005" value="HIREDATE" name="fmap"> <input type="hidden" id="fhdr_005" value="Hiredate" name="fhdr"> <input type="hidden" id="fmap_006" value="SAL" name="fmap"> <input type="hidden" id="fhdr_006" value="Sal" name="fhdr"> <input type="hidden" id="fmap_007" value="COMM" name="fmap"> <input type="hidden" id="fhdr_007" value="Comm" name="fhdr"> <input type="hidden" id="fmap_008" value="DEPTNO" name="fmap"> <input type="hidden" id="fhdr_008" value="Deptno" name="fhdr">This is what i based my mapping on. I only look at the fmap array. And all items are always in the same order as they are in the report attributes. This order also determines which f##-array has to be assigned. A hidden column for example could be the last element in the mapping and thus be f09, while the inputs for array f09 are rendered in the same column as the inputs for array f05.
I've tested through playing with elements in the tabular form, hide them, reorder them, remove and add. The mapping would always reflect the actual correct values.
Place this code in the javascript section of the page:
var gaInputMapping = new Array(); $().ready(function(){ apex.debug('Initializing input mapping array...'); $("input[name='fmap']").each(function(index){ var lsHeader = $(this).val(), lsName = 'f'+('0'+(index+1)).slice(-2), lsNName = $("[name='"+lsName+"']")[0].nodeName, lMap = {"header":lsHeader,"name":lsName,"nodeName":lsNName}; apex.debug('Header: '+lsHeader+' - Array name: '+lsName+' - Node type: '+lsNName); gaInputMapping.push(lMap); }); }); function getNameWithHeader(pHeader){ var lsName; $.each(gaInputMapping, function(index){ if(gaInputMapping[index].header==pHeader.toUpperCase()){ lsName= gaInputMapping[index].name; }; }); return lsName; }; function getHeaderWithName(pName){ var lsHeader; $.each(gaInputMapping, function(index){ if(gaInputMapping[index].name.toUpperCase()==pName.toUpperCase()){ lsHeader= gaInputMapping[index].header; }; }); return lsHeader; }; function getMapEntry(pHeader){ var lRet; $.each(gaInputMapping, function(index){ if(gaInputMapping[index].header==pHeader.toUpperCase()){ lRet= gaInputMapping[index]; }; }); return lRet; }; function getSelector(pHeader){ var lsSel; $.each(gaInputMapping, function(index){ if(gaInputMapping[index].header==pHeader.toUpperCase()){ lsSel= gaInputMapping[index].nodeName + "[name='" + gaInputMapping[index].name + "']"; }; }); return lsSel; }; function getObjectInSameRow(pHeaderFind, pCurrentItem){ return $(getSelector(pHeaderFind), $(pCurrentItem).closest("tr")); };With these, i've executed some lines in the Firebug console:
>>> gaInputMapping [Object { header="EMPNO", name="f01", nodeName="INPUT"}, Object { header="ENAME", name="f02", nodeName="INPUT"}, Object { header="JOB", name="f03", nodeName="INPUT"}, Object { header="MGR", name="f04", nodeName="INPUT"}, Object { header="HIREDATE", name="f05", nodeName="INPUT"}, Object { header="SAL", name="f06", nodeName="INPUT"}, Object { header="COMM", name="f07", nodeName="INPUT"}, Object { header="DEPTNO", name="f08", nodeName="SELECT"}] >>> getNameWithHeader("EMPNO") "f01" >>> getNameWithHeader("MGR") "f04" >>> getHeaderWithName("f03") "JOB" >>> getMapEntry("SAL") Object { header="SAL", name="f06", nodeName="INPUT"} >>> getMapEntry("DEPTNO") Object { header="DEPTNO", name="f08", nodeName="SELECT"} >>> getSelector("HIREDATE") "INPUT[name='f05']" >>> getSelector("DEPTNO") "SELECT[name='f08']" $(getSelector("SAL"),"#rEmp").change(function(){ var lRow = $(this).closest("tr"), lSal = $(this).val(), lComm = $(getSelector("COMM"), lRow).val(), lEmpno = $(getSelector("EMPNO"), lRow).val(), lDeptno = $(getSelector("DEPTNO"), lRow).val(); console.log(lEmpno+' - '+lDeptno+' - '+lSal+' - '+lComm); }); 7369 - 20 - 1804 - 5 (after changing the first row sal value from 1803 to 1804)Another example: get the EMPNO item on the same row as the current item. The getObjectInSameRow function is just a wrap-up of a frequently used technique to target an item on the same row as the current item, and allows you to do so via headers. There is no need to play with rowids and substrings.
$(getSelector("SAL"),"#rEmp").each(function(){ console.log(getObjectInSameRow('EMPNO', $(this))); });This system is a lot more robust to me. I can now safely use the headers and don't have to use a coded f##-array anywhere anymore. I can switch columns and adjust their properties.
There are limits of course: removing columns while functionality relies on them would give you errors, obviously. Changing an item so no input/select is generated anymore, but display only will also break your functionality, as a non-submitting item will not generate an array. In short though, using headers is more intuitive and makes the code more maintainable, and that's all there really is to it.