Oct 22

We’re all eagerly awaiting APEX 4.0 and the new functionality, i.e. including tabular form validations. However some of us just can’t wait to see what we are going to get, so we end up doing/writing our own version first, which will probably be superseded and a total waste of time but that’s what we as developers do!

In our integration framework we found it very easy to enable the functionality in the tabular form since we’d already worked out the column mapping of DOM id’s/names for the tabular form input elements to their corresponding column (see our previous tabular form post), all we needed to do was put some additional information in our JSON meta data object in the page header to reference in our javascript transform routine which converts the normal input items e.g. select lists, date pickers etc into our ext equivalent ones.

To recap from previous posts, we create a tabular form using the APEX IDE and simply change the region & report template to our Ext custom templates which we have created. The templates render the APEX tabular form in an Ext grid. Each time the grid (re)loads we fire off an onload event which takes the DIV container ID of our Ext grid and uses an Ext.select to grab all the input items within it to transform into Ext equivalents. This transform function uses metadata in the page header to work out what transform to perform, i.e. Date, Textfield, Select List, Popup LOV etc. it also uses this meta information to set a number of Ext config parameters for the widget. This means we can easily set a vtype config parameter and enable a dynamic validation.

So our JSON meta data object in the page header contains the following fragment, which pulls the required vtype from the comments section of our tabular form column looking for the following syntax: $vtype:[validation] e.g. $vtype:email$

"vtypes" :
{ "f03" : "email" }

And our transform function just does the following (this is an example for a textfield):

try {
   var lvType = eval("pMetaObj.vtypes." + el.dom.name);
} catch(e) {
   var lvType = null;
}
var tf = new Ext.form.TextField({
   "id": el.dom.id,
   "vtype": lvType,
   "allowBlank": lAllowBlank,
   "applyTo": el.dom.id
});
tf.render();

The end result then looks like this:

APEX Tabular Form - Dynamic Validatons using vtypes

In summary we think we’re on the right track with our framework design approach, simply because it doesn’t take us very long to implement new functionality, and its easy to maintain and update since it uses a single source and write once approach. We’d recommend a similar design if you’re looking to replicate the functionality or develop improved solutions. In essence its really just replicating the design of APEX client side, store everything in metadata and build everything on the fly through reusable api’s. i.e. store your meta data in JSON and reference it in your javascript when you build or transform page components… simple and a recipe for success because that’s what APEX is!

Oct 07

Following on from a previous post where we highlighted the fact that we couldn’t use APEXLIB for cascading LOV’s on our Ext transformed select lists, we wrote our own version to do the job but it didn’t include support for Tabular forms in our Ext grid… well it didn’t until now!

The implementation was quite tricky and we’re interested if there’s a better way to do it, but with our approach we wanted to ensure the following:

  1. We wanted to reuse as much code as possible from our simple form cascading LOV solution, which reused code from Patrick Wolfs APEXLIB framework
  2. We wanted the implementation to be a generic write once approach
  3. We wanted to reuse the LOV definitions for the report columns and not from any specific hidden items on the page, as using hidden items on a page would require some setup each time
  4. We wanted to support additional “Ext vtype” dynamic validations using the comments section

So essentially we had some pretty complex requirements. The main problem we had to work around was the fact that the INPUT items generated by APEX for the Tabular Form had id’s like “f01_0001″ and a name of “f01″, so basically the “name” represented the column and the “id” represented the column row number. What we needed to do was work out the translation of the id/name into the report column name…. the following query does just that!

SELECT to_number(v('APP_ID'))                       application_id
,      to_number(v('APP_PAGE_ID'))                  page_id
,      region_id                                    region_id
,      'F'||lpad(html_item_sequence,2,'0')          name
,      display_sequence                             display_sequence
,      display_as                                   display_as
,      lov_definition                               lov_query
,      lov_display_null                             lov_display_null
,      lov_null_text                                lov_null_text
,      lov_null_value                               lov_null_value
FROM
(SELECT v.column_alias
 ,      LTRIM(v.inline_list_of_values) lov_definition
 ,      upper(v.lov_show_nulls)        lov_display_null
 ,      v.lov_null_text
 ,      v.lov_null_value
 ,      v.display_as
 ,      v.display_sequence
 ,      v.region_id
 ,      v.format_mask
 ,      row_number() over (
           partition by v.region_id
           order by to_number(decode(v.display_as
                                    , 'Display as Text (based on LOV, does not save state)', null
                                    , 'Display as Text (escape special characters, does not save state)', null
                                    , 'Standard Report Column', null
                                    , t.query_column_id)) nulls last
      ) html_item_sequence
FROM   apex_application_page_rpt_cols v
,      wwv_flow_region_report_column  t
,      apex_application_page_regions  r
WHERE  r.source_type = 'Tabular Form'
AND    v.application_id    = t.flow_id
AND    v.region_id         = t.region_id
AND    v.column_alias      = t.column_alias
AND    v.region_id         = r.region_id
AND    v.application_id    = v('APP_ID')
AND    v.page_id           = v('APP_PAGE_ID')

In order to support holding the bind item values in session state (as we are not using any page items and we are reusing code from APEXLIB) we needed to create application level items F01 to F49 as the code looks to update session state based on the bind item name. This is then what our LOV queries look like under the Tabular Form column attributes:

select company_name, company_id
from   company
where  division_id = :F04
order by 1

So now that we have this translation mapping for our column names we then use it to build our JSON meta data objects in the page header which define the bind variables in use for each of the LOV’s dependencies (i.e. children) and the list of LOV’s that have dependencies (i.e. parents) e.g.

var extLovChildren = {
   "F05": ["F04"],
   "F06": ["F05"],
   "F07": ["F06"],
   "F08": ["F04", "F05", "F07"]
}
var extLovParents = {
   "F04": ["F05", "F08"],
   "F05": ["F06", "F08"],
   "F06": ["F07"],
   "F07": ["F08"]
}

We then use/query the above JSON meta data when we perform the Ext transform of our select lists, within the Ext grid region, to identify which ones to add onclick events for, i.e. the parent LOV’s, and which ones need a JSON store i.e. the children, as we will reload the data in the store for the children. Remember when we interrogate our INPUT items they will have the name of “f01″ or “f02″ etc. so we simply use the INPUT html elements name to reference our JSON object e.g.

eval("extLovParents." + select.dom.name.toUpperCase())

The next tricky bit is that when our “select” event fires we need to load the returning JSON object which will have a list of our child LOV’s and their data which needs to be loaded into their Ext stores. This is what the returning JSON object looks like:

{
   "F05": {
      "rows": [{
         "display_value": "Please Select...",
         "return_value": -1
      },
      {
         "display_value": "Option 1",
         "return_value": "160"
      },
      {
         "display_value": "Option 2",
         "return_value": "161"
      },
      {
         "display_value": "Option 3",
         "return_value": "4"
      }]
   },
   "F08": {
      "rows": [{
         "display_value": "Please Select...",
         "return_value": -1
      }]
   }
}

And here’s what the store looks like for the child LOV

var lovStore = new Ext.data.Store({
   id: 'store' + comboid,
   reader: new Ext.data.JsonReader({
      root: pIsTabForm ? comboid.substring(0, 3).toUpperCase() + ".rows" : comboid + ".rows"
   },
   [{
      name: 'display_value'
   },
   {
      name: 'return_value'
   }])
});

Here’s a code fragment which adds the onclick event for the AJAX post & combo reload (we essentially loop through each of the indexes in the returning JSON object and reload the combo JSON stores based on the nested object) and set the value of the combo to the first item in the returned list:

combo.on("select", function (combobox, record, index) {
  var bindItems = lBinds.join(';;');
  var jsonObj = Ext.util.JSON.decode(Ext.app.apexHttpPost('Ext.lov.getLovJSON', new Array('EXT_REFERENCE_ID', comboid.substring(0,3).toUpperCase()), new Array(bindItems, combobox.getValue())));
  for (var idx in jsonObj) {
    Ext.getCmp(idx.toLowerCase()+comboid.substring(3,comboid.length)).getStore().loadData(jsonObj);
    Ext.getCmp(idx.toLowerCase()+comboid.substring(3,comboid.length)).setValue(eval("jsonObj."+idx+".rows[0].return_value"));
  }
});

In the above we pass through a delimited list of the items (f04,f05 etc.) which reference the current item (e.g. f01) and this will stored in session state in the application item “EXT_REFERENCE_ID”. We also pass through the current item (e.g. f01) and its value which updates the application item “F01″ in session state. We then use server side PLSQL (mostly APEXLIB) to (re)execute the LOV queries for the delimited list of the items (f04,f05 etc.) and return them in a single JSON object (our customized code) which is then processed and used to reload dependent LOV stores as mentioned earlier in the post.

The end result is a write once approach and enabled out of the box just by using bind names in your LOV queries, and whilst we have to create a significant number of application items F01-F49 we negate this setup cost by building these into our base template application. The combination of JSON, PLSQL, and Ext really reduces the development overheads and presents solutions for once thought of impossibilities.

APEX Tabular Form - Cascading LOV

Jun 22

I’m a fan of Apexlib, and it’s geen a great utility for providing some of the missing functionality APEX was lacking. One of those features was dynamic validations for “not null” and “date format” form fields. With the integration kit we are building we are looking to offer an Ext equivalent of everything Apexlib provides “and more” so this is one of the tasks we needed to deliver. The effort required to achieve this? Not much at all… since we transform our (tabular) form elements (one of our earlier posts) into Ext equivalent fields we get the benefit of using config settings such as “allowBlank” and “format” which do the validations for us. All thats required is that we set these config values accordingly.

When a validation failure occurs we simply disable the grid top toolbar so the page can’t be submitted and thus resulting in a reduction of required server side validations (but of course it doesn’t hurt to have them in case of an unexpected page submission, especially when not null ones are auto generated by the APEX engine for you). It’s just a nicer experience for the end user when they don’t have to submit pages all the time, not to mention less server side processing.

Here’s our function to transform our form date fields into an Ext equivalent

// convert inputs with class "ext-form-date-picker" to date fields
function extGridDateFields(pID, pRptMetaObj, pGridValidate, pGridValCollection) {
   var els = Ext.get(pID).select("[class=ext-form-date-picker]");
   els.each(function(el) {
      if (el.dom.className.indexOf("aext-form-date") == -1) {
         try {
            var lFormat = eval("pRptMetaObj." + el.dom.name + ".dateFormat");
            var lAllowBlank = eval("pRptMetaObj." + el.dom.name + ".nullable");
         } catch(e) {
            var lFormat = 'd-M-Y';
            var lAllowBlank = true;
         }
         el.dom.className += " " + "aext-form-date";
         var df = new Ext.form.DateField({
            "allowBlank": lAllowBlank,
            "applyTo": el.dom.id,
            "format": lFormat,
            "altFormats": 'j|j/n|j/n/y|j/n/Y|j-M|j-M-y|j-M-Y'
         });
         df.render();
         if (pGridValidate) {
            df.on({
               "invalid": function(field, msg) {
                  gridFieldInvalid(pID, pGridValCollection, this, msg);
               },
               "valid": function(field, msg) {
                  gridFieldValid(pID, pGridValCollection, this, msg);
               }
            });
         }
      }
   })
}

And here’s the supporting validation code (we use an Ext Mixed collection to keep track of the validation error, when none exist the top toolbar is enabled, when one or more exist the top toolbar is disabled).

function gridFieldInvalid(pGridID, pCollection, pField, pMsg) {
   if (!pCollection.key(pField.id)) {
      pCollection.add(pField.id, pField);
   }
   Ext.getCmp(pGridID).getTopToolbar().disable();
}
function gridFieldValid(pGridID, pCollection, pField, pMsg) {
   if (pCollection.key(pField.id)) {
      pCollection.removeKey(pField.id);
   }
   var isEmpty = true;
   pCollection.each(function() {
      isEmpty = false;
   });
   isEmpty ? Ext.getCmp(pGridID).getTopToolbar().enable() : Ext.getCmp(pGridID).getTopToolbar().disable();
}

How do we know what is nullable and what requires date formats? We query the database data dictionary using the defined values under the column attributes for each column in the report. We then add the details for any columns which require validations to our existing JSON report meta data object which we print out in our page header. We get the benefit of two metadata dictionaries with APEX, the application and the database. I think the emerging trend of “thick databases” will start to take hold when more people realize the main benefits are reduced development time and costs…. two of the most important things in today’s climate!

APEX ExtJS - Tabular Form Dynamic Validation

Jun 17

In a previous post we talked about how we integrated the tabular form into an Ext Grid purely by the Region and Report templates and that in doing so we were restricted from using a Popup LOV. This was due to the fact that the APEX engine generates the javascript inline in the report which broke our template. Our intention with the APEX ExtJS integration is to not give up any functionality we’re looking to enhance it, hence why we are focused on providing an alternative if we can’t support some existing APEX functionality!

This post is dedicated to working around that problem by simply using a class setting in the “Form Element Options” section under the report definition to build our own Ext flavoured Popup LOV.

APEX ExtJS - Poup LOV Attributes

Remember we’re not going to tell you step by step how to do it, it would take a few blog pages (and I don’t have the time) besides what do you learn from spoon feeding! We’ll simply give you the high level approach and give you any gotchya’s to watch out for.

First step for the popup LOV replacement, we need to build the javascript function, similar to our date and text area enhancement, which will take a normal text field and transform it into a nice search popup with the added bonus of pagination all via AJAX. See the following Ext demo for a working model and javascript widget source. Here’s an extract of ours:

function extPopupLOV(pID, pRegionID) {

   // define widget URL
   var u = (window.location.href.indexOf("?") > 0) ? window.location.href.substring(0, window.location.href.indexOf("?")) : window.location.href;
   var baseURL = u.substring(0, u.lastIndexOf("/"));
   var l_RegionID = new String(pRegionID);
   l_RegionID = l_RegionID.substr(1, l_RegionID.length);
   var widgetURL = baseURL + '/wwv_flow.show?p_flow_id=' + Ext.getDom('pFlowId').value + '&p_flow_step_id=' + $v('pFlowStepId') + '&p_instance=' + Ext.getDom('pInstance').value + '&p_request=APPLICATION_PROCESS=Ext.widget';

   var els = Ext.get(pID).select("[class=ext-form-popup-lov]");
   els.each(function(el) {
      if (el.dom.className.indexOf("aext-form-popup-lov") == -1) {
         el.dom.className += " " + "aext-form-popup-lov";
         var ds = new Ext.data.Store({
            proxy: new Ext.data.HttpProxy({
               url: widgetURL
            }),
............

         var search = new Ext.form.ComboBox({
            store: ds,
            displayField: 'title',
            typeAhead: false,
            loadingText: 'Searching...',
            queryParam: 'x03',
etc.

The following overrides were required to ensure we changed our parameter names when the AJAX post was performed. Note: we use the same parameter name mapping in the Grid paging so they won’t be affected by this override.

/*
   Paging Toolbar Override for parameter names

http://extjs.com/forum/showthread.php?t=17403

*/
Ext.override(Ext.PagingToolbar, {
   paramNames: {
      start: 'x01',
      limit: 'x02'
   }
});
Ext.form.ComboBox.override({
    getParams : function(q){
        var p = {};
        if(this.pageSize){
            p['x01'] = 0;
            p['x02'] = this.pageSize;
        }
        return p;
    }
});

Secondly we need to write a little PLSQL behind the scenes to grab our list of values query from the column definition and allow filtering upon it, execute it, and return the data back in a JSON object. This uses the same Widget Application process previously described in an earlier post. This again highlights the beauty of your APEX application being just metadata, all we have for the item is a name e.g. “f03″ but we can use this to query the data dictionary (along with the region id defined in the grid) to determine which column it is and what the defined LOV query is which we will execute and return back in a JSON object. Here’s the main APEX/Oracle elements that are required to achieve it!

  • apex_application_page_rpt_cols – we need to locate the LOV query
  • wwv_flow_region_upd_report_column – we need to use this table to work out which column the HTML item belongs to, i.e. query_column_id
  • dbms_sql.describe_columns – we need to describe our LOV query to work out the column to perform our filter on
  • apex_util.json_from_sql – we need to build a JSON object from the query

APEX ExtJS - Poup LOV Attributes LOV

We then added the calls “extPopupLOV(‘grid’+pRegionID,pRegionID);” to our “Add Rows” javascript function on the tabular form, and also to the “pStore.on({load” function when the grid renders. That’s it!

I’d like to highlight that this has taken roughly 6 hours to complete for a 100% reusable design which simply requires you to set a class attribute in the “Form Element Options” field to enable the functionality, so when you’re thinking about weighing up the options to use ExtJS or not, bottom line is it’s going to save you a hell of a lot of time whilst producing a better end result. I think you’ll be making more than just your boss happy! If you think it’s too complicated to use, remember everything new takes a little time to get your head around, when you started with APEX were you a guru from day 1? And once you understand what it means to have you data in JSON objects easily accessible and event based you’ll see that you will reduce round trips to the server and open up interaction with a lot more widgets (not just Ext) and Third Party applications, like Google for instance.

APEX ExtJS - Poup LOV

Jun 11

As previously stated we are looking to integrate ExtJS with as many of the apex wizard driven components and templates as possible. This post focuses on the tabular form and how we integrated it into the Ext grid whilst retaining the APEX form processing functionality e.g. “ApplyMRU” and “ApplyMRD”.

APEX ExtJS - APEX Form Component Selection

In the previous posts we have highlighted that we have created a region and report template that takes a standard SQL report and turns it into and Ext grid. We are reusing the same templates on the tabular form. What this highlights is code reusability and centralization, we don’t need to create another set of templates for the tabular form. That said it does come with some restrictions as we can’t support a Popup LOV or APEX Date Picker (we’ll explain why shortly). We will also be reusing the button template that previously posted about to add the buttons to the grid toolbar and the delete button will reuse the Modal window popup for confirm deletion prompt and simply post the page using “doSubmit”. It’s funny how nicely all the pieces start fitting together, design is key, remember that!

Since we are simply using a template to enable the Ext grid functionality whatever is defined under the report definition will be displayed in the Ext grid. So if our columns are select lists, then select lists will be displayed, same for text fields, and textareas. This also means that they will be generated with the right name and id’s which are parameters for APEX wwv_flow.accept package call e.g. “f01″. This is how we can still use the APEX MRU,MRD processing functionality.

TheProblems we Encountered:

  1. First Problem: Popup LOV’s and APEX Date Picker cannot be supported (it broke our report template as we generate a JSON object in javascript) as previously stated, this is because the APEX engine generates some additional javascript into the report row entries which is outside of our control so columns cannot be defined with these two types. We will however provide an Ext equivalent (which is better) simply by defining the item as a text field but using the CSS class to determine what Ext widget to override it with, i.e. ext-date, ext-popuplov.
  2. Next Problem: our region template broke, this was because the APEX engine automatically appends some javascript code to the #BODY# subsititution tag for tabular forms to control row highlighting on selection. e.g.

            <script type="text/javascript">
            <!--
                var rowStyle      = new Array(5);
                var rowActive     = new Array(5);
                var rowStyleHover = new Array(5);
    
                rowStyle[1]='';
                rowStyleHover[1]='';
                rowActive[1]='N';
                rowStyle[2]='';
                rowStyleHover[2]='';
                rowActive[2]='N';
                rowStyle[3]='';
                rowStyleHover[3]='';
                rowActive[3]='N';
                rowStyle[4]='';
                rowStyleHover[4]='';
                rowActive[4]='N';
                rowStyle[5]='';
                rowStyleHover[5]='';
                rowActive[5]='N';
    
                function checkAll(masterCheckbox) {
                    if (masterCheckbox.checked) {
                        for (var i = 0; i<document.wwv_flow.f01.length; i++) {
                            if (document.wwv_flow.f01[i].checked==false) {
                              document.wwv_flow.f01[i].checked=true;
                              highlight_row(document.wwv_flow.f01[i],i);
                            }
                        }
                    } else {
                        rowsNotChecked=0;
                        for (var i = 0; i<document.wwv_flow.f01.length; i++) {
                           if (document.wwv_flow.f01[i].checked!=true) {
                               rowsNotChecked=rowsNotChecked+1;
                           }
                        }
                        if (rowsNotChecked==0) {
                            for (var i = 0; i<document.wwv_flow.f01.length; i++) {
                                if (document.wwv_flow.f01[i].checked==true) {
                                  document.wwv_flow.f01[i].checked=false;
                                  highlight_row(document.wwv_flow.f01[i],i);
                                }
                            }
                        }
                    }
                }
    
                function highlight_row(checkBoxElemement,currentRowNum) {
                    if(checkBoxElemement.checked==true) {
                        for( var i = 0; i < checkBoxElemement.parentNode.parentNode.childNodes.length; i++ ) {
                            if (checkBoxElemement.parentNode.parentNode.childNodes[i].tagName=='TD') {
                                if(rowActive=='Y') {
                                    rowStyle[currentRowNum] = rowStyleHover[currentRowNum];
                                } else {
                                    rowStyle[currentRowNum] = checkBoxElemement.parentNode.parentNode.childNodes[i].style.backgroundColor;
                                }
                                checkBoxElemement.parentNode.parentNode.childNodes[i].style.backgroundColor = '';
                            }
                        }
                        rowStyleHover[currentRowNum] =  '';
                    } else {
                          for( var i = 0; i < checkBoxElemement.parentNode.parentNode.childNodes.length; i++ ) {
                              if (checkBoxElemement.parentNode.parentNode.childNodes[i].tagName=='TD') {
                                  checkBoxElemement.parentNode.parentNode.childNodes[i].style.backgroundColor =  rowStyle[currentRowNum];
                                  rowStyleHover[currentRowNum] =  rowStyle[currentRowNum];
                                  document.wwv_flow.x02.checked=false;
                              }
                          }
                    }
                }
            // -->
            </script>
    

    The solution was to put in some extra start and end script tags in the report template and region template (remember we need two templates to work together to achieve the Ext grid). Originally our region template had an open script tag at the top and a close script tag after the #BODY# substitution string.We simply put a close script tag at the end of the report template and a new script start tag immediately after the #BODY# tag in the region template.

  3. Next Problem: Add Rows via page submission and the application process “Add Rows” would not work as it was not output by the report template. The apex engine auto generated this code so this was a big problem. Our solution (trust me there’s always one with anything, it can just take a little time to figure out) was to create a javascript function to add the new row to the grid. Its achieved by copying the last row in the Ext store and modifying the row HTML data before adding the extra row back to the store (note the actual row HTML has been generated by APEX not Ext). The HTML modification required incrementing input id’s and nulling values. I’d recommend that you familiarize yourself with regular expressions as it makes this task achievable in just a few lines of code.
    lRowArray[i] = lRowArray[i].replace(/(id=\"f\d+_).*?\"/g, '$1' + PadDigits(lastIndex,0,4) + '"');
    

    As for working out the right ID to increment the HTML input elements to i.e. “f1_0011″ we simply counted the records in the Ext store.

    That’s the great thing about Ext is that your report data is held in an accessible JSON object, and in this example our report data is not just data it also happens to be HTML, i.e. input fields and select lists so this means we don’t need to query the server for adding another row, which is more “end user” friendly.

    This gave us the benefit of being able to add more than one row at a time before committing the changes, as multiple “Add Row” clicks on a normal tabular form saves the new record each time.

  4. Next Problem: When copying the Ext store row we needed to make sure that the “fcs” hidden item, which is used for a row checksum to compare if the data had changed, contained a dummy value, as when we initially erased the value via a regex global replace the rows weren’t saved e.g.
    <input type="hidden" value="" name="fcs"/>

    was a problem. Solution: we simply changed it to

    <input type="hidden" value="12345" name="fcs"/>

    and the rows were then saved.

  5. Next Problem: we had to disable Ext grid filtering for dates and numbers. This was due to us defining the data type of the column in the grid column model and the Ext renderer replacing our HTML with “NaN” which is basically a return error identifier.

As we were missing the date picker field type we simply added an onload event to the Store which converted text items into Ext date pickers based on the items having a class setting of “ext-form-date-picker”. The class setting was set in the “Element Attributes” in the “Tabular Form Element” section. We also allowed all textareas to be manually resized e.g.

pStore.on({load: function() { extDateFields('grid'+pRegionID);extResizableByID('grid'+pRegionID); } });

// convert inputs with class "ext-form-date-picker" to date fields
function extDateFields(pID){
  var els=Ext.get(pID).select("[class=ext-form-date-picker]");
  els.each(function(el){
    if (el.dom.className.indexOf("aext-form-date") == -1) {
       el.dom.className += " "+"aext-form-date";
       var df = new Ext.form.DateField({"applyTo": el.dom.id, "format":'d-M-Y',"altFormats":'j|j/n|j/n/y|j/n/Y|j-M|j-M-y|j-M-Y'});
       df.render();
    }
  })
}
function extResizableByID(pID){
  var els=Ext.get(pID).select('textarea',true);
  els.each(function(e){
    if (e.dom.className.indexOf("aext-resizeable") == -1) {
       e.dom.className += " "+"aext-resizeable";
       var resizeMe = new Ext.Resizable(e, {
          wrap:true,
          pinned:true,
          width:e.getWidth(),
          height:e.getHeight(),
          minWidth:e.getWidth(),
          minHeight: e.getHeight()
       });
    }
  })
}

The end result is an APEX tabular form displayed in an Ext grid which supports inserts/updates/deletes, here’s an example from our development environment….

APEX ExtJS - APEX Tabular Form in an Ext Grid

preload preload preload