uf_protect_col() for smart column protection


Link to this posting

Postby Ursego » 08 Jul 2019, 08:33

To make a DW column not editable (i.e. protected), many developers mark that field as Protected (or insert an expression if the protection is conditional) in the DataWindow Painter. That is very bad practice - I suggest that you use the function uf_protect_col(), whose code is provided below.

It comes in 4 overloads: to protect a single column conditionally and unconditionally, and to protect a few columns at one blow (passed as an array), conditionally and unconditionally as well. The conditional overloads can also be used to remove protection - simply pass the expression "1=0".

Call uf_protect_col() in a script, which initializes your object - like Open event of a window, or the Constructor of a visual UserObject. If you use a framework, then it's likely that you have an event like ue_post_open or ue_post_constructor, which is fired when the object has already been constructed.

Advantages

Using uf_protect_col() (instead of setting the Protect property in the DataWindow Painter) gives you 6 advantages:

@ Looking at the script, you see the whole picture - which columns are protected and when. If you set the Protect property in the DataWindow Painter, that information is hidden - you need to investigate each column's properties.

@ You can build a complicated expression dynamically and assign it in a more elegant way than Modify().

@ In Protect expressions, you can easily use application constants rather than hard-code their values (please read Use constants always).

@ If many columns have a same expression, you can write that expression once and assign to each column, thus preventing code duplication:

Code: Select all
string   ls_protect_expr
string   ls_credit_card_cols[] = {"cc_holder_name", "cc_number", "cc_exp_month", "cc_exp_year", "cc_control_number"}

ls_protect_expr = "payment_method <> '" + n_payment_method.CREDIT_CARD + "'"

gnv_util.uf_protect_col(this, ls_credit_card_cols[], ls_protect_expr)


@ In expressions, hardcoded in the Protect property, you can access only the DW columns, built-in functions (like IsRowNew()) and global user functions. But uf_protect_col() makes it possible to protect columns using Boolean expressions in PowerScript code, where you can do whatever - for example, call objects' functions (not only global functions). These functions can query the database with no performance issues since such a function is called only once - when your code is being executed. If you query the database in a global function, called from the hardcoded expression, then that query will be executed each time the expression is being evaluated, so you will be forced to add caching of the retrieved data. If the condition is coded in PowerScript (rather than as an expression of the Protect property), then, of course, the unconditional overload is used:

Code: Select all
string   ls_credit_card_cols[] = {"cc_holder_name", "cc_number", "cc_exp_month", "cc_exp_year", "cc_control_number"}

if dw_billing.uf_get_payment_method() <> n_payment_method.CREDIT_CARD then
   gnv_util.uf_protect_col(this, ls_credit_card_cols[])
end if

@ You can protect fields from any script at any time without using Modify(). For example, you can call uf_protect_col() just after data retrieval, or in ItemChanged. But, in fact, you should avoid that! Do your best to manage protection of all the columns in one script (like Open event) - it will be easier to manage and less bug-prone. Protect from other scripts only if you have absolutely no choice.

Source code

If an error occurs, uf_protect_col() displays an error message. That is done using the straightforward MessageBox(). Even though it's not the best way, many developers will choose it since their applications don't use exceptions. But if you want to use the exceptions mechanism (described here), which is the correct and preferable way, then use the version, provided in the next comment (rather than the version which appears in the next CODE section).

Here is the source code of uf_protect_col() (the error message version, not the exception version) - please add it to your utilities NVO and pass the DW as the adw argument. If you decide to add the function to your ancestor DW, then delete that argument, and use this instead:

Code: Select all
/***********************************************************************************************************************************************
Dscr:       Protects DW column conditionally, using a logical expression - protect if true, unprotect if false.
            To protect unconditionally, use the overload without as_protect_expr.
            To remove protection, call the overload which accepts an expression, passing "1=0" as the expression.
************************************************************************************************************************************************
Arg:         adw, as_col_name, as_protect_expr
************************************************************************************************************************************************
Developer:   Michael Zuskin > http://linkedin.com/in/zuskin | http://code.intfast.ca/
***********************************************************************************************************************************************/
string   ls_err = ''
string   ls_modify_expr

constant string BG_COLOR__PROTECTED = "536870912" // 536870912 = Transparent; 12632256 = Silver; 67108864 = Button Face
constant string BG_COLOR__EDITABLE = "1073741824" // 1073741824 = Windows Background; 16777215 = White

if IsNull(as_col_name) then
   ls_err = "Column name is NULL."
elseif IsNull(as_protect_expr) then
   ls_err = "Protect expression is NULL."
end if

// Set the expression for the Protect property:
if ls_err = '' then
   ls_modify_expr = as_col_name + ".Protect = ~"0~~t if("+ as_protect_expr + ", 1, 0)~""
   ls_err = adw.Modify(ls_modify_expr)
   if ls_err <> '' then
      if Trim(adw.DataObject) = '' or IsNull(adw.DataObject) then
         ls_err = "DataObject is empty."
      elseif adw.Describe(as_col_name + '.Name') <> as_col_name then
         ls_err = "Column " + as_col_name + " doesn't exist in " + adw.DataObject + "."
      else
         ls_err = "Failed to set Protect property of column " + as_col_name + " (in DataObject" + adw.DataObject + &
                                                   ") to the following expression:~r~n~r~n" + as_protect_expr
      end if
   end if
end if

// Change the background color to make the column looking not-editable:
if ls_err = '' then
   ls_modify_expr = as_col_name + ".Background.Color = ~"0~~t if(" + as_protect_expr + "," + BG_COLOR__PROTECTED + "," + BG_COLOR__EDITABLE + ")~""
   adw.Modify(ls_modify_expr)
end if

if ls_err <> '' then
   MessageBox(this.ClassName() + ".uf_protect_col()", ls_err)
end if

return

The overload which protects a single column unconditionally:

Code: Select all
/***********************************************************************************************************************************************
Dscr:         Protects DW column unconditionally.
            To protect conditionally, use the overload which accepts a logical expression.
            To remove protection, call the overload which accepts an expression, passing "1=0" as the expression.
************************************************************************************************************************************************
Arg:         adw, as_col_name
************************************************************************************************************************************************
Developer:   Michael Zuskin > http://linkedin.com/in/zuskin | http://code.intfast.ca/
***********************************************************************************************************************************************/

this.uf_protect_col(adw, as_col_name, "1=1")

return

The overload which protects a few columns at one blow conditionally:

Code: Select all
/***********************************************************************************************************************************************
Dscr:       Protects DW columns, passed as an array, conditionally, using a logical expression - protect if true, unprotect if false.
            To protect unconditionally, use the overload without as_protect_expr.
            To remove protection, call the overload which accepts an expression, passing "1=0" as the expression.
************************************************************************************************************************************************
Arg:         adw, as_col_names[], as_protect_expr
************************************************************************************************************************************************
Developer:   Michael Zuskin > http://linkedin.com/in/zuskin | http://code.intfast.ca/
***********************************************************************************************************************************************/
int   i
int   li_upper_bound

li_upper_bound = UpperBound(as_col_names[])

for i = 1 to li_upper_bound
   this.uf_protect_col(adw, as_col_names[i], as_protect_expr)
next

return

The overload which protects a few columns at one blow unconditionally:

Code: Select all
/***********************************************************************************************************************************************
Dscr:       Protects DW columns, passed as an array, unconditionally.
            To remove protection, call the overload which accepts an expression, passing "1=0" as the expression.
************************************************************************************************************************************************
Arg:         adw, as_col_names[]
************************************************************************************************************************************************
Developer:   Michael Zuskin > http://linkedin.com/in/zuskin | http://code.intfast.ca/
***********************************************************************************************************************************************/
int   i
int   li_upper_bound

li_upper_bound = UpperBound(as_col_names[])

for i = 1 to li_upper_bound
   this.uf_protect_col(adw, as_col_names[i], "1=1")
next

return
User avatar
Ursego
Site Admin
 
Posts: 143
Joined: 19 Feb 2013, 20:33

Link to this posting

Postby Ursego » 08 Jul 2019, 11:23

If you want to use the exceptions mechanism (described here) rather than simply display an error message, then use this version of uf_protect_col() (don't forget to fill the "Throws:" field in the header with n_ex):

Code: Select all
/***********************************************************************************************************************************************
Dscr:       Protects DW column conditionally, using a logical expression - protect if true, unprotect if false.
            To protect unconditionally, use the overload without as_protect_expr.
            To remove protection, call the overload which accepts an expression, passing "1=0" as the expression.
************************************************************************************************************************************************
Arg:         adw, as_col_name, as_protect_expr
************************************************************************************************************************************************
Developer:   Michael Zuskin > http://linkedin.com/in/zuskin | http://code.intfast.ca/
***********************************************************************************************************************************************/
string   ls_err
string   ls_modify_expr

constant string BG_COLOR__PROTECTED = "536870912" // 536870912 = Transparent; 12632256 = Silver; 67108864 = Button Face
constant string BG_COLOR__EDITABLE = "1073741824" // 1073741824 = Windows Background; 16777215 = White

if IsNull(as_col_name) then
   f_throw(PopulateError(1, "Column name is NULL.")) // f_throw(): http://code.intfast.ca/viewtopic.php?t=1
elseif IsNull(as_protect_expr) then
   f_throw(PopulateError(2, "Protect expression is NULL."))
end if

// Set the expression for the Protect property:
ls_modify_expr = as_col_name + ".Protect = ~"0~~t if("+ as_protect_expr + ", 1, 0)~""
ls_err = adw.Modify(ls_modify_expr)
if ls_err <> "" then
   if Trim(adw.DataObject) = '' or IsNull(adw.DataObject) then
      f_throw(PopulateError(3, "DataObject is empty."))
   elseif adw.Describe(as_col_name + '.Name') <> as_col_name then
      f_throw(PopulateError(4, "Column " + as_col_name + " doesn't exist in " + adw.DataObject + "."))
   else
      f_throw(PopulateError(5, "Failed to set Protect property of column " + as_col_name + " (in DataObject" + adw.DataObject + &
                                                   ") to the following expression:~r~n~r~n" + as_protect_expr))
   end if
end if

// Change the background color to make the column looking not-editable:
ls_modify_expr = as_col_name + ".Background.Color = ~"0~~t if(" + as_protect_expr + "," + BG_COLOR__PROTECTED + "," + BG_COLOR__EDITABLE + ")~""
adw.Modify(ls_modify_expr)

return
User avatar
Ursego
Site Admin
 
Posts: 143
Joined: 19 Feb 2013, 20:33




Ketones are a more high-octane fuel for your brain than glucose. Become a biohacker and upgrade yourself to version 2.0!



cron
Traffic Counter

eXTReMe Tracker