Code Filter User Object for Powerbuilder Apps
When working with the end users who would be using the Purchasing System I was tasked with developing, a requirement came out very early in the process which might otherwise have caused concern; they wanted to sift their data based on any number of various codes assigned to it. Initially there were six different code types they wanted to use, in time this increased to ten. I wanted to implement as generic a solution as possible since there were four windows (initially) this type of behavior was going to apply to.
The various code values themselves were pretty typical, (part class, material type, product group, etc.) but their numbers varied greatly from six to over twenty thousand. I wanted to offer the users the ability to enter both individual code values and code ranges at the same time to maximize flexibility. Due to the nature of the data involved, I used these objects to insert data into a series of tables which were subsequently referenced by a stored procedure which selected the desired records. However, the process can easily be used to manipulate a specific datawindow sql statement or to apply a filter to the results within a datawindow.
The code is PFC based and makes use of the error service.
Datawindow Object d_code_filter (external data source) table(column=(type=char(50) updatewhereclause=no name=codeline dbname="codeline" ) column=(type=char(30) updatewhereclause=no name=tablename dbname="tablename" ) column=(type=char(1) updatewhereclause=no name=singledelim dbname="singledelim" initial=";" ) column=(type=char(1) updatewhereclause=no name=rangedelim dbname="rangedelim" initial="-" ) )
Only the ‘codeline’ column is visible on the datawindow object.
n_cst_filterstring User Object (PFC Based),
global type n_cst_filterstring from n_srv //Instance variables n_cst_string inv_string // string checking service forward prototypes public function long of_parsefilter (string as_filterstring, string as_delimiter, ref string as_filter[]) public function string of_buildfilter (string as_params, string as_delim, string as_replacestring) public function integer of_checkrange (string as_source, string as_rangedelim, string as_singledelim) end prototypes public function long of_parsefilter (string as_filterstring, string as_delimiter, ref string as_filter[]); long ll_return // parse the filter string into an array and return the array size long ll_arraylength string ls_filterarray[] long ll_return = 1
// turn as_filtersting into an array ll_arraylength = inv_string.of_parsetoarray(as_filterstring,as_delimiter,ls_filterarray) IF IsNull(ll_arraylength) THEN ll_return = 0 ELSE as_filter = ls_filterarray ll_return = ll_arraylength END IF RETURN ll_return end function
public function string of_buildfilter (string as_params, string as_delim, string as_replacestring); string ls_return ls_return = '' ls_return = inv_string.of_globalreplace(as_params,as_delim,as_replacestring)RETURN ls_return end function
public function integer of_checkrange (string as_source, string as_rangedelim, string as_singledelim);
// checks string 'as_source', which should be a
// range of codes, to make sure that it is a
// valid range designation and the first code
// is of lesser or equal value to the second code.
//
// example code range: 0100-0200
// '0100' and '0200' are the codes
// '-' is the range delimiter
//
// returns -1 for error condition
// 1 for success
//
integer li_return
long ll_singleoc
string ls_range[]
string ls_error[]
li_return = 1
//how many 'elements' are in the string
ll_singleoc = inv_string.of_countoccurrences(as_source,as_singledelim)
IF (ll_singleoc = 0) THEN
inv_string.of_parsetoarray(as_source,as_rangedelim,ls_range)
IF (Upperbound(ls_range) <> 2) then
// ERROR CONDITION
// should have exactly two elements in a range
ls_error[1] = ''
gnv_app.inv_error.of_Message("1101", ls_error)
li_return = -1
ELSE
IF (Trim(Upper(ls_range[1])) > Trim(Upper(ls_range[2]))) THEN
// ERROR CONDITION
// element 1 cannot be greater than element 2
ls_error[1] = Trim(Upper(ls_range[1]))
ls_error[2] = Trim(Upper(ls_range[2]))
gnv_app.inv_error.of_Message("1101", ls_error)
li_return = -1
END IF
END IF
END IF
RETURN li_return
end function
uo_code_filter object - User object //This control is placed on the form. global type uo_code_filter from u_dw integer width = 859 integer height = 104 string dataobject = "d_code_filter" boolean vscrollbar = false boolean ib_isupdateable = false string is_updatesallowed = "" event ue_mousemove pbm_mousemove end type global uo_code_filter uo_code_filter
type variables Protected: string is_filter[] end variables forward prototypes public function integer of_settablename (string as_tablename) public function integer of_setdefault (string as_default) public function integer of_setdelim (string as_delimtype, string as_delim) public function string of_gettablename () public function integer of_getfilter (ref string as_filter[]) public function string of_buildfilter (string as_columnname, string as_columntype) public function string of_buildfilter (string as_columnname, string as_columntype, string as_prefixchar, long an_padamount) end prototypes
public function integer of_settablename (string as_tablename); // set the database table name the codes reside in int li_return li_return = 0 IF IsNull(as_tablename) OR as_tablename = '' THEN li_return = -1 ELSE this.setitem(1,'tablename',as_tablename) END IF RETURN li_return end function
public function integer of_setdefault (string as_default);
// sets the 'default' value of the code which is
// displayed when the window is opened or set to the defaults
end function
public function integer of_setdelim (string as_delimtype, string as_delim);
// external datawindow has default values of semi-colon(;) as single value delimiter
// and dash(-) as the range delimiter. Use this method if different delimiters
// are desired.
//
// sets the delimiters for the data entry
// return -1 - bad delim type
// return -2 - bad delim
// return -3 - unknown delim type
// return 0 - a okay
integer li_return
li_return = 0
IF (IsNull(as_delimtype)) OR (as_delimtype = '') THEN
li_return = -1
END IF
IF (IsNull(as_delim)) OR (as_delim = '') THEN
li_return = -2
END IF
IF (li_return = 0) THEN
CHOOSE CASE upper(as_delimtype)
CASE 'SINGLE'
this.setitem(1,'singledelim',as_delim)
CASE 'RANGE'
this.setitem(1,'rangedelim',as_delim)
CASE ELSE
li_return = -3
END CHOOSE
END IF
RETURN li_return
end function
public function string of_gettablename (); // gets the database table name the codes reside in RETURN getitemstring(1,'tablename') end function
public function integer of_getfilter (ref string as_filter[]);
// sets the filter array
integer li_return
li_return = 1
as_filter = is_filter
RETURN li_return
end function
public function string of_buildfilter (string as_columnname, string as_columntype);
// build the filter string (SQL syntax)
long ll_i, ll_ac
string ls_rangedelim
string ls_range
string ls_filterstring = ''
string ls_array[]
string ls_quote
string ls_replace
n_cst_filterstring lnv_filterstring
lnv_filterstring = CREATE n_cst_filterstring
CHOOSE CASE as_columntype
CASE 'S' //strings
ls_quote = "'"
CASE ELSE // non strings
ls_quote = ''
END CHOOSE
this.event trigger pfc_accepttext(TRUE)
// sets the is_filter array on the uo
IF (this.event trigger pfc_validation() > 0) THEN
this.of_getfilter(ls_array)
ls_rangedelim = this.getitemstring(1,'rangedelim')
ll_ac = Upperbound(ls_array)
FOR ll_i = 1 to ll_ac
IF (Upper(ls_array[ll_i]) = 'ALL') THEN
ls_range = ''
ls_filterstring = 'ALL'
EXIT
END IF
IF (as_columnname = '') THEN
ls_range = ''
ls_filterstring = ls_array[ll_i]
EXIT
END IF
IF (lnv_filterstring.inv_string.of_countoccurrences(ls_array[ll_i],ls_rangedelim) > 0) THEN
// found a range
IF (Len(ls_range) > 1) THEN // second range found
IF (Len(ls_filterstring) > 1) THEN
ls_filterstring = ls_filterstring + " OR (" + ls_range + ")"
ELSE
ls_filterstring = ls_range
END IF
ls_range = ''
END IF
ls_replace = ls_quote +" and " + as_columnname + " <= "+ ls_quote
ls_range = lnv_filterstring.of_buildfilter(ls_array[ll_i],ls_rangedelim,ls_replace)
ls_range = " " + as_columnname + " >= " + ls_quote + ls_range + ls_quote
ELSE
IF (Len(ls_filterstring) > 1) THEN
ls_filterstring = ls_filterstring + " OR " +as_columnname+" = "+ ls_quote + ls_array[ll_i] +ls_quote
ELSE
ls_filterstring = " "+as_columnname+" = " + ls_quote + ls_array[ll_i] + ls_quote
END IF
END IF
NEXT
IF (Len(ls_range) > 1) THEN
IF (Len(ls_filterstring) > 1) THEN
ls_filterstring = ls_filterstring + " OR (" + ls_range + ")"
ELSE
ls_filterstring = ls_range
ls_range = ''
END IF
END IF
END IF
DESTROY lnv_filterstring
RETURN ls_filterstring
end function
public function string of_buildfilter (string as_columnname, string as_columntype, string as_prefixchar, long an_padamount);
// Polymorph to allow for prefix padding of values
long ll_i, ll_ac
long ll_p
string ls_rangedelim
string ls_range
string ls_filterstring = ''
string ls_array[]
string ls_quote
string ls_replace
n_cst_filterstring lnv_filterstring
lnv_filterstring = CREATE n_cst_filterstring
CHOOSE CASE as_columntype
CASE 'S' //strings
ls_quote = '"'
CASE ELSE // non strings
ls_quote = ''
END CHOOSE
this.event trigger pfc_accepttext(TRUE)
// sets the is_filter array on the uo
IF (this.event trigger pfc_validation() > 0) THEN
this.of_getfilter(ls_array)
ls_rangedelim = this.getitemstring(1,'rangedelim')
ll_ac = Upperbound(ls_array)
FOR ll_i = 1 to ll_ac
IF (Upper(ls_array[ll_i]) = 'ALL') THEN
ls_range = ''
ls_filterstring = 'ALL'
EXIT
ELSE // pad entry with prefix
FOR ll_p = 1 to an_padamount
ls_array[ll_i] = as_prefixchar + ls_array[ll_i]
NEXT
END IF
IF (as_columnname = '') THEN
ls_range = ''
ls_filterstring = ls_array[ll_i]
EXIT
END IF
IF (lnv_filterstring.inv_string.of_countoccurrences(ls_array[ll_i],ls_rangedelim) > 0) THEN
// found a range
IF (Len(ls_range) > 1) THEN // second range found
IF (Len(ls_filterstring) > 1) THEN
ls_filterstring = ls_filterstring + ' OR (' + ls_range + ')'
ELSE
ls_filterstring = ls_range
END IF
ls_range = ''
END IF
ls_replace = ls_quote +' and ' + as_columnname + ' <= '+ ls_quote
FOR ll_p = 1 to an_padamount // pad end range item
ls_replace = ls_replace + as_prefixchar
NEXT
ls_range = lnv_filterstring.of_buildfilter(ls_array[ll_i],ls_rangedelim,ls_replace)
ls_range = ' ' + as_columnname + ' >= ' + ls_quote + ls_range + ls_quote
ELSE
IF (Len(ls_filterstring) > 1) THEN
ls_filterstring = ls_filterstring + ' OR ' +as_columnname+' = '+ ls_quote + ls_array[ll_i] +ls_quote
ELSE
ls_filterstring = ' '+as_columnname+' = ' + ls_quote + ls_array[ll_i] + ls_quote
END IF
END IF
NEXT
IF (Len(ls_range) > 1) THEN
IF (Len(ls_filterstring) > 1) THEN
ls_filterstring = ls_filterstring + ' OR (' + ls_range + ')'
ELSE
ls_filterstring = ls_range
ls_range = ''
END IF
END IF
END IF
DESTROY lnv_filterstring
RETURN ls_filterstring
end function
event constructor;call super::constructor;
// basic stuff
this.of_setbase(TRUE)
this.insertrow(0) // needs a row
this.setrowfocusindicator(Off!)
end event
event type integer pfc_validation();call super::pfc_validation;
// validate the filter and parse it into an array
// if a code range is involved it will be verified
//
// example: filterstring is '0100-0200;0300;0400'
// '-' is the range delimiter
// ';' is the single delimiter
// resulting array is:
// element[1] = '0100-0200' < a valid range
// element[2] = '0300'
// element[3] = '0400'
integer li_return
integer li_elements
integer li_i
string ls_singledelim
string ls_rangedelim
string ls_filterstring
string ls_range[]
string ls_filter[]
n_cst_filterstring lnv_filterstring
lnv_filterstring = CREATE n_cst_filterstring
li_return = 1
ls_filterstring = this.getitemstring(1,'codeline')
ls_singledelim = this.getitemstring(1,'singledelim')
ls_rangedelim = this.getitemstring(1,'rangedelim')
li_elements = lnv_filterstring.of_parsefilter(ls_filterstring,ls_singledelim,ls_filter)
IF (li_elements = 0) THEN
li_return = -1
//invalid or no entry
//ERROR CONDITION
ELSE
FOR li_i = 1 to li_elements
// is this element a range?
IF (lnv_filterstring.inv_string.of_countoccurrences(ls_filter[li_i],ls_rangedelim) > 0) THEN
// make sure range is in order
IF (lnv_filterstring.of_checkrange(ls_filter[li_i],ls_rangedelim,ls_singledelim) < 0) THEN
//ERROR CONDITION
li_return = -1
EXIT
END IF
END IF
NEXT
END IF
IF (li_return > 0) THEN
is_filter = ls_filter
END IF
DESTROY lnv_filterstring
RETURN li_return
end event
Code from windows with the object
User object placed on window is called ‘dw_category’
pfc_postopen event
dw_category.of_setdefault('ALL')
Sample code called from function ‘wf_getfilter’ which returns ls_filterstring.
string ls_filterstring
string ls_categories
string ls_replace
string ls_array[]
string ls_sql
integer li_rc
n_cst_filterstring lnv_filterstring
lnv_filterstring = CREATE n_cst_filterstring
ls_filterstring = ''
ls_categories = dw_category.of_buildfilter('categoryCode','S')
CHOOSE CASE ls_categories
CASE 'ALL'
// no real filter on the column
ls_categories = ''
ls_sql = 'insert into wrkInvActionListCategory values ( @@SPID, -1 )'
EXECUTE IMMEDIATE :ls_sql;
CASE ''
//ERROR
CASE ELSE
// got a filter so you can do something with it.
li_rc = wf_setwrktable('cprCategoryMaster','categoryKey', ' WHERE '+ls_categories)
IF (li_rc < 0) THEN
//ERROR CONDITION
Messagebox(this.title,'Error setting Category filter.')
END IF
END CHOOSE
// call the same methods on any other filter datawindows, capturing the filter string
// into separate variables then combine them (or whatever)// category
RETURN ls_categories
Samples
Data entered in field is: 0100-0120;0300;0500
Filter string returned is:
categoryCode = ‘0300’ OR categoryCode = ‘0500’ OR ( categoryCode >= ‘0100’ and categoryCode <= ‘0120’)