added double-click action to template builder selector items to see details. other...
authorerickson <erickson@dcc99617-32d9-48b4-a31d-7c20da2025e4>
Wed, 17 Jan 2007 18:07:00 +0000 (18:07 +0000)
committererickson <erickson@dcc99617-32d9-48b4-a31d-7c20da2025e4>
Wed, 17 Jan 2007 18:07:00 +0000 (18:07 +0000)
git-svn-id: svn://svn.open-ils.org/ILS/branches/rel_1_0@6779 dcc99617-32d9-48b4-a31d-7c20da2025e4

Open-ILS/web/reports/oils_rpt.css
Open-ILS/web/reports/oils_rpt.js
Open-ILS/web/reports/oils_rpt_builder.js
Open-ILS/web/reports/oils_rpt_builder.xhtml
Open-ILS/web/reports/oils_rpt_filters.js
Open-ILS/web/reports/oils_rpt_param_editor.js
Open-ILS/web/reports/oils_rpt_report_editor.js
Open-ILS/web/reports/oils_rpt_tforms.js
Open-ILS/web/reports/oils_rpt_tree.js
Open-ILS/web/reports/oils_rpt_widget.js

index c470124..d4db1f2 100644 (file)
@@ -64,15 +64,15 @@ table { border-collapse: collapse; }
        background: #E0F0F0; 
        min-height: 150px;
        max-height: 290px;
-       overflow: scroll;
+       overflow: auto;
 }
 
 .oils_rpt_small_info_selector {
        border: 1px solid blue; 
        background: #E0F0F0; 
        height: 80px;
-       overflow: scroll;
        width: 12em;
+       overflow: auto;
 }
 
 
@@ -110,6 +110,12 @@ button:active {
        border-style: inset;
 }
 
+button:disabled {
+       color: #000;
+       background-color: #EEE;
+       border: 2px solid #888;
+}
+
 .floaty {
        position: absolute;     
        text-align: center;
index 7754cfb..223bd20 100644 (file)
@@ -13,6 +13,8 @@ function oilsInitReports() {
        fetchUser(cgi.param('ses'));
        DOM.oils_rpt_user.appendChild(text(USER.usrname()));
 
+       if( cgi.param('dbg') ) oilsRptDebugEnabled = true;
+
        fetchHighestPermOrgs(SESSION, USER.id(), perms);
        if( PERMS.RUN_REPORTS == -1 ) {
                unHideMe(DOM.oils_rpt_permission_denied);
index e1e3b86..14b0f97 100644 (file)
@@ -1,6 +1,9 @@
 /** initializes reports, some basid display settings, 
   * grabs and builds the IDL tree
   */
+
+var __dblclicks = {}; /* retain a cache of the selector value doubleclick event handlers */
+
 function oilsInitReportBuilder() {
        if(!oilsInitReports()) return false;
        oilsReportBuilderReset();
@@ -116,8 +119,16 @@ function oilsReportBuilderSave() {
 
 /* adds an item to the display window */
 function oilsAddRptDisplayItem(path, name, tform, params) {
-       if( ! oilsAddSelectorItem(oilsRptDisplaySelector, path, name) ) 
+
+       if( ! oilsAddSelectorItem( 
+               {  selector : oilsRptDisplaySelector, 
+                       val : path, 
+                       label : name, 
+                       path : path, 
+                       type : 'display',
+                       transform : tform }) ) {
                return;
+       }
 
        /* add the selected columns to the report output */
        name = (name) ? name : oilsRptPathCol(path);
@@ -167,7 +178,15 @@ function oilsRptAddSelectList(obj, tform) {
                        iterate(oilsRpt.def.select,
                                function(item) {
                                        _debug('re-inserting display item ' + item.path);
-                                       oilsAddSelectorItem(oilsRptDisplaySelector, item.path, item.alias) });
+                                       oilsAddSelectorItem(
+                                               {  selector: oilsRptDisplaySelector, 
+                                                       val:item.path, 
+                                                       label:item.alias, 
+                                                       path:item.path,
+                                                       transform : item.column.transform,
+                                                       type : 'display'
+                                               }
+                                       ) });
                }
 
        } else {
@@ -255,8 +274,23 @@ function oilsMoveUpDisplayItems() {
        var opt = sel.options[idx];
        sel.options[idx] = null;
        idx--;
+
        var val = opt.getAttribute('value');
-       insertSelectorVal(sel, idx, opt.innerHTML, val);
+   var label = opt.getAttribute('label');
+   var path = opt.getAttribute('path');
+   var evt_id = opt.getAttribute('listener');
+
+       opt = insertSelectorVal(sel, idx, label, val);
+
+       opt.setAttribute('path', path);
+       opt.setAttribute('title', label);
+   opt.setAttribute('label', label);
+   opt.setAttribute('value', val);
+   opt.setAttribute('listener', evt_id);
+
+   /* re-attach the double-click event */
+       opt.addEventListener('dblclick', __dblclicks[evt_id], true );
+
        sel.options[idx].selected = true;
 
        var arr = oilsRpt.def.select;
@@ -278,8 +312,27 @@ function oilsMoveDownDisplayItems() {
        var opt = sel.options[idx];
        sel.options[idx] = null;
        idx++;
+
+       //var val = opt.getAttribute('value');
+       //insertSelectorVal(sel, idx, opt.innerHTML, val);
+       //insertSelectorVal(sel, idx, opt.getAttribute('label'), val);
+
        var val = opt.getAttribute('value');
-       insertSelectorVal(sel, idx, opt.innerHTML, val);
+   var label = opt.getAttribute('label');
+   var path = opt.getAttribute('path');
+   var evt_id = opt.getAttribute('listener');
+
+       opt = insertSelectorVal(sel, idx, label, val);
+
+   opt.setAttribute('value', val);
+       opt.setAttribute('path', path);
+       opt.setAttribute('title', label);
+   opt.setAttribute('label', label);
+   opt.setAttribute('listener', evt_id);
+
+   /* re-attach the double-click event */
+       opt.addEventListener('dblclick', __dblclicks[evt_id], true );
+
        sel.options[idx].selected = true;
 
        var arr = oilsRpt.def.select;
@@ -423,7 +476,15 @@ function oilsAddRptFilterItem(path, tform, filter) {
        var epath = name[1];
        name = name[0];
 
-       if( ! oilsAddSelectorItem(oilsRptFilterSelector, epath, name) )
+       if( ! oilsAddSelectorItem(
+               {  selector : oilsRptFilterSelector, 
+                       val : epath, 
+                       label : name, 
+                       path : path,
+                       transform : tform,
+                       operation : filter,
+                       type : 'filter'
+               }) )
                return;
 
        var where = {
@@ -453,7 +514,16 @@ function oilsAddRptHavingItem(path, tform, filter) {
        var epath = name[1];
        name = name[0];
 
-       if( ! oilsAddSelectorItem(oilsRptHavingSelector, epath, name) )
+       if( ! oilsAddSelectorItem(
+               {  selector: oilsRptHavingSelector, 
+                       val:epath, 
+                       label:name, 
+                       path:path,
+                       transform : tform,
+                       operation : filter,
+                       type : 'agg_filter' /* XXX */
+               }) )
+
                return;
 
        var having = {
@@ -530,13 +600,6 @@ function oilsRptFilterDataMatches(filter, path, operation, tform) {
        var rel = hex_md5(oilsRptPathRel(path));
        var col = oilsRptPathCol(path);
 
-       /*
-       _debug("oilsRptFilterDataMatches(): " + col + " : " + rel + " : " + tform + " : " + operation);
-       _debug("oilsRptFilterDataMatches(): " + filter.column.colname + " : " + filter.relation + " : " + 
-               filter.column.transform + " : " + oilsRptObjectKeys(filter.condition)[0]);
-       _debug("------------------");
-       */
-
        if(     col == filter.column.colname &&
                        rel == filter.relation &&       
                        tform == filter.column.transform &&
@@ -545,83 +608,53 @@ function oilsRptFilterDataMatches(filter, path, operation, tform) {
        return false;
 }
 
-/*
-function oilsRptFilterGrep(flist, filter) {
-
-       for( var j = 0; j < flist.length; j++ ) {
-
-               var fil = flist[j];
-               var col = filter.column;
-               var frel = hex_md5(oilsRptPathRel(fil.path));
-               var fcol = oilsRptPathCol(fil.path);
-
-               var op = oilsRptObjectKeys(filter.condition)[0];
-
-               if(     frel == filter.relation && 
-                               fcol == col.colname && 
-                               fil.operation == op &&
-                               fil.tform == col.transform ) {
-                               return false;
-               }
-       }
-       return true;
-}
-*/
-
 /* adds an item to the display window */
+/*
 function oilsAddRptAggFilterItem(val) {
-       oilsAddSelectorItem(oilsRptHavingFilterSelector, val);
+       oilsAddSelectorItem({selector:oilsRptHavingFilterSelector, val:val, label:val, path:val});
 }
+*/
 
 /* removes a specific item from the display window */
+/*
 function oilsDelAggFilterItem(val) {
        oilsDelSelectorItem(oilsRptHavingFilterSelector, val);
 }
+*/
 
 
+/* adds an item to the display window */
+//function oilsAddSelectorItem(sel, val, name, path) {
+function oilsAddSelectorItem(args) {
 
-/*
-function ___oilsDelSelectedAggFilterItems() {
-       var list = oilsDelSelectedItems(oilsRptHavingFilterSelector);
-       oilsRpt.def.having = grep( oilsRpt.def.having, 
-               function(i) {
-                       for( var j = 0; j < list.length; j++ ) {
-                               var d = list[j];
-                               var col = i.column;
+       var sel = args.selector;
+       var label = args.label;
+       var path = args.path;
+       var val = args.val;
 
-                               if( typeof col != 'string' ) 
-                                       for( var c in col ) col = col[c];
+       label = (label) ? label : oilsRptMakeLabel(val);
 
-                               if( typeof col != 'string' ) col = col[0];
+       var i;
+       for( i = 0; i < sel.options.length; i++ ) {
+               var opt = sel.options[i];
+               if( opt.value == val ) return false;
+       }
 
-                               if( oilsRptPathRel(d) == i.relation && oilsRptPathCol(d) == col ) {
-                               */
-                               //      var param = (i.alias) ? i.alias.match(/::P\d*/) : null;
-                                       /*
-                                       if( param ) delete oilsRpt.params[param];
-                                       return false;
-                               }
-                       }
-                       return true;
-               }
-       );
+       var opt = insertSelectorVal( sel, -1, label, val );
+       opt.setAttribute('title', label);
+       opt.setAttribute('path', path);
+   opt.setAttribute('label', label);
 
-       if(!oilsRpt.def.having) oilsRpt.def.having = [];
-       oilsRptPruneFromList(list);
-       oilsRptDebug();
-}
-*/
+   var evt_id = oilsNextNumericId();
+   opt.setAttribute('listener', evt_id);
 
+       args.index = i;
+       delete args.selector;
+   var listener = function(){ oilsRptDrawDataWindow(path, args);};
+       opt.addEventListener('dblclick', listener, true );
+   __dblclicks[evt_id] = listener;
+               //function(){ oilsRptDrawDataWindow(path, args);} , true);
 
-/* adds an item to the display window */
-function oilsAddSelectorItem(sel, val, name) {
-       name = (name) ? name : oilsRptMakeLabel(val);
-       for( var i = 0; i < sel.options.length; i++ ) {
-               var opt = sel.options[i];
-               if( opt.value == val ) return false;
-       }
-       var opt = insertSelectorVal( sel, -1, name, val );
-       opt.setAttribute('title', name);
        return true;
 }
 
@@ -666,7 +699,13 @@ function oilsRptHideEditorDivs() {
   This draws the 3-tabbed window containing the transform,
   filter, and aggregate filter picker window
   */
-function oilsRptDrawDataWindow(path) {
+function oilsRptDrawDataWindow(path, args) {
+
+       args = (args) ? args : {};
+
+       for( var x in args ) 
+               _debug("oilsRptDrawDataWindow(): args -> " + x + " = " + args[x]);
+
        var col = oilsRptPathCol(path);
        var cls = oilsRptPathClass(path);
        var field = oilsRptFindField(oilsIDL[cls], col);
@@ -689,9 +728,9 @@ function oilsRptDrawDataWindow(path) {
        /* don't let them see the floating div until the position is fully determined */
        div.style.visibility='hidden'; 
 
-       oilsRptDrawTransformWindow(path, col, cls, field);
-       oilsRptDrawFilterWindow(path, col, cls, field);
-       oilsRptDrawHavingWindow(path, col, cls, field);
+       oilsRptDrawTransformWindow(path, col, cls, field, args);
+       oilsRptDrawFilterWindow(path, col, cls, field, args);
+       oilsRptDrawHavingWindow(path, col, cls, field, args);
        //oilsRptDrawOrderByWindow(path, col, cls, field);
 
        //buildFloatingDiv(div, 600);
@@ -701,14 +740,14 @@ function oilsRptDrawDataWindow(path) {
 
        /* now let them see it */
        div.style.visibility='visible';
-       oilsRptSetDataWindowActions(div);
+       //args.type = (args.type) ? args.type : 'display';
+       oilsRptSetDataWindowActions(div, args);
 }
 
 
-function oilsRptSetDataWindowActions(div) {
+function oilsRptSetDataWindowActions(div, args) {
        /* give the tab links behavior */
 
-
        DOM.oils_rpt_tform_tab.onclick = 
                function(){
                        oilsRptHideEditorDivs();
@@ -722,6 +761,7 @@ function oilsRptSetDataWindowActions(div) {
                        unHideMe(DOM.oils_rpt_filter_div)
                        addCSSClass(DOM.oils_rpt_filter_tab.parentNode, 'oils_rpt_tab_picker_selected');
                };
+
        DOM.oils_rpt_agg_filter_tab.onclick = 
                function(){
                        oilsRptHideEditorDivs();
@@ -738,23 +778,46 @@ function oilsRptSetDataWindowActions(div) {
                        };
                        */
 
-       DOM.oils_rpt_tform_tab.onclick();
+       DOM.oils_rpt_tform_submit.disabled = false;
+       DOM.oils_rpt_filter_submit.disabled = false;
+       DOM.oils_rpt_agg_filter_submit.disabled = false;
+
+       if( !args.type ) {
+               DOM.oils_rpt_tform_tab.onclick();
+       } else {
+               /* disable editing on existing items for now */
+               if( args.type == 'display' ) {
+                       DOM.oils_rpt_tform_tab.onclick();
+                       DOM.oils_rpt_tform_submit.disabled = true;
+               }
+               if( args.type == 'filter' ) {
+                       DOM.oils_rpt_filter_tab.onclick();
+                       DOM.oils_rpt_filter_submit.disabled = true;
+               }
+               if( args.type == 'agg_filter' ) {
+                       DOM.oils_rpt_agg_filter_tab.onclick();
+                       DOM.oils_rpt_agg_filter_submit.disabled = true;
+               }
+       }
+
        DOM.oils_rpt_column_editor_close_button.onclick = function(){hideMe(div);};
 }
 
 
-function oilsRptDrawFilterWindow(path, col, cls, field) {
+function oilsRptDrawFilterWindow(path, col, cls, field, args) {
 
        var tformPicker = new oilsRptTformPicker( {     
                        node : DOM.oils_rpt_filter_tform_table,
                        datatype : field.datatype,
-                       non_aggregate : true
+                       non_aggregate : true,
+                       select : args.transform
                }
        );
 
        var filterPicker = new oilsRptFilterPicker({
                        node : DOM.oils_rpt_filter_op_table,
-                       datatype : field.datatype
+                       datatype : field.datatype,
+                       select : args.operation
                }
        );
 
@@ -765,17 +828,19 @@ function oilsRptDrawFilterWindow(path, col, cls, field) {
 }
 
 
-function oilsRptDrawHavingWindow(path, col, cls, field) {
+function oilsRptDrawHavingWindow(path, col, cls, field, args) {
        var tformPicker = new oilsRptTformPicker( {     
                        node : DOM.oils_rpt_agg_filter_tform_table,
                        datatype : field.datatype,
-                       aggregate : true
+                       aggregate : true,
+                       select : args.transform
                }
        );
 
        var filterPicker = new oilsRptFilterPicker({
                        node : DOM.oils_rpt_agg_filter_op_table,
-                       datatype : field.datatype
+                       datatype : field.datatype,
+                       select : args.operation
                }
        );
 
@@ -786,15 +851,20 @@ function oilsRptDrawHavingWindow(path, col, cls, field) {
 }
 
 /* draws the transform window */
-function oilsRptDrawTransformWindow(path, col, cls, field) {
-       DOM.oils_rpt_tform_label_input.value = oilsRptMakeLabel(path);
+function oilsRptDrawTransformWindow(path, col, cls, field, args) {
+       args = (args) ? args : {};
+
+       DOM.oils_rpt_tform_label_input.value = 
+               (args.label) ? args.label : oilsRptMakeLabel(path);
+
        var dtype = field.datatype;
 
        var tformPicker = new oilsRptTformPicker( {     
                        node : DOM.oils_rpt_tform_table,
                        datatype : field.datatype,
                        non_aggregate : true,
-                       aggregate : true
+                       aggregate : true,
+                       select : args.transform
                }
        );
 
index d145589..8054e00 100644 (file)
                                                        </tr>
                                                </tbody></table>
 
+
                                                <div id='oils_rpt_tree_div'>
                                                </div>
+
+                                               <br/>
+                                               <div>
+                                                       <b>**</b>
+                                                       <span style='padding-left: 6px;'>
+                                                               Indicates that when filtering on the item, a list of named choices will be generated.
+                                                       </span>
+                                               </div>
+
                                        </td>
                                        <td id='oils_rpt_table_right_td' align='right'>
                                                <div class='oils_rpt_info_div'>
@@ -72,6 +82,9 @@
                                                        <select id='oils_rpt_agg_filter_selector' class='oils_rpt_info_item oils_rpt_info_selector' multiple='multiple'/>
                                                        <button onclick='oilsDelSelectedAggFilterItems();'><u>X</u> Remove Selected</button>
                                                </div>
+                                               <div class='oils_rpt_info_div'>
+                                                       <span style='color:red;font-weight:bold;'>Hint: </span> Double-click on an item to see the item details.
+                                               </div>
                                        </td>   
                                </tr>
                        </tbody>
                                (<span id='oils_rpt_editor_window_datatype'/>)
                        </div>
                        <div id='oils_rpt_tform_div'>
-                               <input size='28' id='oils_rpt_tform_label_input'/>
+                               <input size='42' id='oils_rpt_tform_label_input'/>
                                <div class='oils_rpt_field_editor_window'>
                                        <div style='margin-bottom: 10px;'>Select how this field should be displayed:</div>
                                        <div id='oils_rpt_tform_table'/>
index 87bee9d..13f4ae9 100644 (file)
@@ -63,15 +63,16 @@ function oilsRptFilterPicker(args) {
        this.dtype = args.datatype;
        this.selector = elem('select');
        for( var key in OILS_RPT_FILTERS ) 
-               this.addOpt(key);
+               this.addOpt(key, key == args.select );
        appendClear(this.node, this.selector);
 }
 
 
-oilsRptFilterPicker.prototype.addOpt = function(key) {
+oilsRptFilterPicker.prototype.addOpt = function(key, select) {
        var filter = OILS_RPT_FILTERS[key];
        var label = filter.label;
-       insertSelectorVal( this.selector, -1, label, key);
+       var opt = insertSelectorVal( this.selector, -1, label, key);
+       if( select ) opt.selected = true;
        if( filter.labels && filter.labels[this.dtype] ) 
                insertSelectorVal( this.selector, -1, filter.labels[this.dtype], key);
 }
index 7f522d3..9ffa374 100644 (file)
@@ -139,6 +139,10 @@ oilsRptParamEditor.prototype.buildWidget = function(param, node) {
                        atomicWidget = oilsRptNumberWidget
                        break;
 
+               case 'substring':
+                       atomicWidget = oilsRptSubstrWidget
+                       break;
+
 
        }
 
index 62bef6b..98d484c 100644 (file)
@@ -30,6 +30,9 @@ function oilsRptReportEditor(rptObject, folderWindow) {
                rptObject, DOM.oils_rpt_param_editor_tbody);
        this.paramEditor.draw();
 
+       removeChildren(DOM.oils_rpt_report_editor_selected_folder);
+       removeChildren(DOM.oils_rpt_output_selected_folder);
+
        var obj = this;
        oilsRptBuildFolder(
                'report',
index a7e9492..f14a2f3 100644 (file)
@@ -34,10 +34,12 @@ var OILS_RPT_TRANSFORMS = {
 
        /* string transforms ------------------------- */
 
+   /* XXX not supported yet
        substring : {
                datatype : OILS_RPT_DTYPE_STRING,
                label : 'Substring'
        },
+   */
 
        lower : {
                datatype : OILS_RPT_DTYPE_STRING,
@@ -141,35 +143,6 @@ var OILS_RPT_TRANSFORMS = {
                label : 'Age'
        },
 
-       /*
-       relative_year : {
-               datatype : OILS_RPT_DTYPE_TIMESTAMP,
-               label : 'Relative year'
-       },
-
-       relative_month : {
-               datatype : OILS_RPT_DTYPE_TIMESTAMP,
-               label : 'Relative month'
-       },
-
-       relative_week : {
-               datatype : OILS_RPT_DTYPE_TIMESTAMP,
-               label : 'Relative week'
-       },
-
-       relative_date : {
-               datatype : OILS_RPT_DTYPE_TIMESTAMP,
-               label : 'Relative date'
-       },
-       */
-
-       /* exists?
-       days_ago : {
-               datatype : OILS_RPT_DTYPE_TIMESTAMP,
-               label : 'Days ago'
-       }
-       */
-
        months_ago : {
                datatype : OILS_RPT_DTYPE_TIMESTAMP,
                label : 'Months ago'
@@ -180,15 +153,6 @@ var OILS_RPT_TRANSFORMS = {
                label : 'Quarters ago'
        },
 
-
-       /* exists?
-       years_ago : {
-               datatype : OILS_RPT_DTYPE_TIMESTAMP,
-               label : 'Years ago'
-       },
-       */
-
-
        /* int  / float transforms ----------------------------------- */
        sum : {
                datatype : [ OILS_RPT_DTYPE_INT, OILS_RPT_DTYPE_FLOAT ],
@@ -254,14 +218,15 @@ function oilsRptTformPicker(args) {
        this.selector = elem('select');
        this.tforms = oilsRptGetTforms(args);
        for( var i = 0; i < this.tforms.length; i++ ) 
-               this.addOpt(this.tforms[i]);
+               this.addOpt(this.tforms[i], this.tforms[i] == args.select );
        appendClear(this.node, this.selector);
 }
 
-oilsRptTformPicker.prototype.addOpt = function(key) {
+oilsRptTformPicker.prototype.addOpt = function(key, select) {
        var tform = OILS_RPT_TRANSFORMS[key];           
        var obj = this;
-       insertSelectorVal(this.selector, -1, tform.label, key);
+       var opt = insertSelectorVal(this.selector, -1, tform.label, key);
+       if( select ) opt.selected = true;
 }
 
 oilsRptTformPicker.prototype.getSelected = function(key) {
index 5131102..35dec1f 100644 (file)
@@ -168,7 +168,12 @@ function oilsRenderSubTree( data, subTreeId, path ) {
                        action = 'javascript:oilsAddLinkTree("' +
                                dataId+'","'+field['class']+'","'+fullpath+'");';
 
-               oilsRptTree.addNode( dataId, subTreeId, field.label, action, field.label,
+               label = field.label;
+
+               /* indicate that this will build a friendly list at report time */
+               if( field.selector ) label += ' **'; 
+
+               oilsRptTree.addNode( dataId, subTreeId, label, action, field.label,
                        (field.type == 'link') ? 'oils_rpt_tree_link_ref' : null );
        }
 }
index 6a77d15..18d3e99 100644 (file)
@@ -59,14 +59,24 @@ oilsRptSetWidget.prototype.getValue = function() {
 
 oilsRptSetWidget.prototype.objToStr = function(obj) {
        if( typeof obj == 'string' ) return obj;
-       return ':'+obj.transform+':'+obj.params[0];
+       //return ':'+obj.transform+':'+obj.params[0];
+       var str = ':'+obj.transform;
+       for( var i = 0; i < obj.params.length; i++ ) 
+               str += ':' + obj.params[i];
+       _debug("objToStr(): built string " + str);
+       return str;
+
 }
 
 oilsRptSetWidget.prototype.strToObj = function(str) {
        if( str.match(/^:.*/) ) {
-               var tform = str.replace(/^:(.*):.*/,'$1');
-               var param = str.replace(/^:.*:(.*)/,'$1');
-               return { transform : tform, params : [param] };
+               var parts = str.split(/:/);
+               _debug("strToObj(): " + str + ' : ' + parts);
+               parts.shift();
+               var tform = parts.shift();
+               //var tform = str.replace(/^:(.*):.*/,'$1');
+               //var param = str.replace(/^:.*:(.*)/,'$1');
+               return { transform : tform, params : parts };
        }
        return str;
 }
@@ -327,6 +337,42 @@ oilsRptAgeWidget.prototype.getDisplayValue = function() {
 
 
 /* --------------------------------------------------------------------- 
+       Atomic substring picker
+       --------------------------------------------------------------------- */
+function oilsRptSubstrWidget(args) {
+       this.node = args.node
+       this.data = elem('input',{type:'text',size:12})
+       this.offset = elem('input',{type:'text',size:5})
+       this.length = elem('input',{type:'text',size:5})
+}
+
+oilsRptSubstrWidget.prototype.draw = function() {
+       this.node.appendChild(text('string: '))
+       this.node.appendChild(this.data);
+       this.node.appendChild(elem('br'));
+       this.node.appendChild(text('offset: '))
+       this.node.appendChild(this.offset);
+       this.node.appendChild(elem('br'));
+       this.node.appendChild(text('length: '))
+       this.node.appendChild(this.length);
+}
+
+oilsRptSubstrWidget.prototype.getValue = function() {
+       return {
+               transform : 'substring',
+               params : [ this.data.value, this.offset.value, this.length.value ]
+       };
+}
+
+oilsRptSubstrWidget.prototype.getDisplayValue = function() {
+       return {
+               label : this.data.value + ' : ' + this.offset.value + ' : ' + this.length.value,
+               value : this.getValue()
+       };
+}
+
+
+/* --------------------------------------------------------------------- 
        Atomic number picker
        --------------------------------------------------------------------- */
 function oilsRptNumberWidget(args) {