ClientCode Scripting Bible

Garry Lowther • Oct 10, 2005

 


Scripting Engine Developer Guide








Client Code provides very flexible customisability by way of form design, config fields, reports, actions and stored procedures to implement business logic. 


This document provides a programmers guide (bible and tutorial) to scripting which allows developers to perform client based tasks such as validate input, process batch scripts, change screen behaviour etc..












Prepared by:




Garry Lowther

ClientCode.com


Original Draft: September 2000


Current Version: Monday, 10 October 2005


 

Table of Contents


1. Introduction 4

2. Client Code Script Engine Architecture 5

3. CShowForm / CFormObjects 7

4. Client Code Form Events 10

5. Virtual Entities/Forms 11

6. Client Code Object Model 13

7. Example of setting ellipses lookups 15

8. Step by Step Example of Master-Detail Dynamic Grid Handling 16

9. Field Visibility Example Code – How to Use FormFields within ShowForm 21

10. Button Visibility Example Code – How to Use FormButton within ShowForm 22

11. Setting Selected Tab In Script 23

12. Detecting Which Button is Pressed 24

13. Firing an Action Button 25

14. Firing an Action From Script Code 26

15. Validating Action Values 28

16. Creating a Lookup Combo 30

17. Asking the User to Choose from a List 31

18. Using SQL Selection Object 32

19. Modal Virtual Form Interaction 33

20. Action Form Collection Manipulation 36

21. Firing A Report From Script 37

22. Using Classes in VBScript – Object Oriented Programming 38

23. Programming Grid Column/Cell Colours 40

24. Multi-Select Grid Rows 42

25. Launching Core Entity Forms From Script 43

26. Using FileSystemObject in VBScript 44

27. Read all Grid Rows 45

28. External Document Launching 46

29. Global Parameter Value 47

30. Launching E-Mail Client 48

31. Colouring Tabs 49

32. Crystal Report Form Script 50

33. Automating Word 52

34. Updateable Grid for Data Entry 53

35. Programmed Fields Re-Appearing on Record Save 55

36. AddItem Grid Rows 56

37. Unbound Virtual Grid Population 57

38. Refreshing Grids on Other Entity Forms 58

39. Running Mail Merges From Script 59

40. Diagnostics and User Activity Logging for Debugging and Audit Trails 61

41. Closing Action Forms 62

42. Post Code Lookup 63

43. Using Bookmarks in Grids 64

44. Accessing Grid SQL Query Recordset and Cloning 65

45. Unbound Grids - Formatting Columns 66

46. Form_Unload – Preventing form from closing from Script 67

47. Public Sub Form_ExternalRefresh – Catching events in script 68

48. Spin Increments – Setting Visibility from Script 69

49. Report Formulas and Parameters 70

50. Form Timers 72

51. Forcing ExternalRefresh events from Script 73

52. Capturing Main Binder Toolbar Buttons 74

53. Printing Contact Reports from Virtual Forms 75

54. Access System Settings 76

55. Designer Queries in Script 77

56. Setting the Report Destination at run-time from Script 78

57. Intercepting Report Selection Events from Script 80

58. Grid Column Filtering/Sorting for AddItem Grids 81

59. Using CommonDialogue Friendly Function to Find Files 83

60. Form_BeforeDeleteRecord Event 84

61. Form_CoreEntityLookupEvent 85

62. frmEntitySearch Buttons 88

63. Turning Off Virtual Form Menu 89

64. Dynamic Button Pop-Up Menus 90

65. Hiding Tabs in Entity Search and Virtual Forms 91

66. Preventing Entity Search based on Form Permission 92

67. Setting visibility of btnInternet on Internet Field Types 93

68. Setting Script Timeout Globally or for Each Form 94

69. Using the GridAlpha from script 95

70. Setting Field, Button & Tab Visibility from Script 98


 

1. Introduction


Client Code has been developed to be as flexible/configurable as possible from both a user, analyst and programmer perspectives. The reasons for this are:


• Compete with more established ERP/CRM systems

• Allow easier customisation on-site by analysts

• Closely match clients business processes


Most of the design goals have been achieved through:


• Designer 

• Actions

• Reports

• SQL Server Stored Procedures

• SQL Server views/triggers/batch jobs etc..

• Client code scripting


The scripting feature is how we implement a fully programmable client. This allows power users, analysts, implementers etc the opportunity of programming the operation of the client software to perform a multitude of tasks. 


The approach has been to implement support for an industry standard scripting language within Client Code. This document discusses such an implementation.


The goals of scripting integration were as follows:

• Use appropriate scripting engine: VBScript, JavaScript or VBA

• Exposure of Client Code object model

• Handle form business logic e.g. Change title to Mrs changes sex to Female.

• Field events for validation

• Form events for load/save/validate etc..

• Addition of code behind new widgets (buttons, text, lists etc..) to carry out ad-hoc tasks

• Customisable grids and behaviour

• Virtual entities – new forms

• Ad-hoc saveable scripts to be run from GUI

• Batch scripts to run scheduled






 

2. Client Code Script Engine Architecture


This section contains the script engine architecture:






















The red boxes are the configurable forms which each have their own CShowForm class (mustard). Each CShowForm has access to a script engine which is owned by the form (because some non config forms do not have an associated CShowForm).


Each instance of a CScriptEngine (blue) object has access to the global objects (grey) business model hierarchy (discussed below). This contains all of the global objects used by Client Code to access the underlying business data.


In the case of non-config forms, they do not have an associated CShowForm, but still have a script engine to provide the scripting capability and access to the global objects.



 

Object Hierarchy


The object hierarchy consists of a collection class (CScriptEngines) together with a CScriptEngine instance for each form which exposes a scripting interface.


The CScriptEngine has access to a collection of underlying form objects which are the text boxes, lists, controls etc on the form.



































Each forms underlying CFormObjects collection is accessible from ANY script for ANY form. This exposes the very powerful and flexible underlying Visual Basic Forms engine. This allows any script to access the entire set of forms within the application. An example of use might be when an update to a form needs to update a counter on a master form. See later sections for examples of this.


The CFunctionStatus is the class which is available to VBScript for modifying parameters as it is not possible to modify the function parameters as they are not passed by reference. See later chapter for more detail.



 

3. CShowForm / CFormObjects


CShowForm is the class which handles all form processing for each config form (Contact, Company etc..). The changes to CShowForm are necessary to facilitate the event processing of form events. The underlying form may need to be coded with extra widget handling, but most of it can be accommodated from within CShowForm.


An object model has been constructed which allows access to the following:


Object Properties Methods Events

Fields Yes Yes Yes

Buttons Yes No Yes

Menus Yes No Yes

Grids Yes Yes Yes

Tab Yes No Yes

Form Yes Yes Yes

Widgets (status bar, caption, width, window state etc..) Yes No

Function (form function) No Callable Yes


2 classes exist which are added as an object to the Script Engine instantiated for the form. This provides a simple abstraction to the form fields and widgets from within the script engine to facilitate easy access by the script programmer. It is necessary because CShowForm may not exist on the form if it is a non-config variety.


Class: CFormObjects


Properties: EventControl – the control which has raised the event


Methods: FormRef – the Form object 

ShowForm – the CShowForm object for config forms

LoadControl – create a new form control and return reference


Events: None


Description: The collection class for the form objects.

Provides access to all form objects via specific method to differentiate types.

FormRef is discussed below.

CShowForm is also discussed below


This is accessible from within script as the FormObjectsLocal object.



 

Class: FormRef – Object reference to VB Form from CFormObjects


Properties: Type (Field=1, Button=2, Menu=3, Grid=4, Tab=5, Form=6, OtherWidget=7)

Field: BackColour, ForeColour, Caption, Value, ToolTipText

Button: Caption, ToolTipText

Menu: Caption, Checked, Visible

Grid: Row, ColumnValue, ColumnHeader, CellForeColour, CellBackColour

Tab: TabCaption

Form: Caption, WindowState, Height, Width


Methods: FormWidget (the actual underlying widget on form. Any property can thus be manipulated)


Events:

Field: Change, Click, DblClick, GotFocus, KeyPress, LostFocus

Button: Click

Menu: Click

Grid: AfterInitialise, DblClick, RowLoaded, DisplayCell, BeforePopulation

Tab: Click

Form: AfterLoad, AfterLoadRecord, AfterSaveRecord, BeforeSaveRecord, Unload


Description: Contains all methods and properties for the script to interact directly with the underlying form characteristics. Can modify field and button properties, process callbacks, grids etc..

The script for the form and the form design are closely coupled together with this class to provide a fully programmable form.




Class: CShowForm – Object reference to config fields object from CFormObjects


Properties: FormProperties – return object containing detail for form

GetFieldWidget(sTableName, sTableFieldName)

GetFieldDescriptionFromWidgetHwnd – return FieldDescription object from the window handle of the widget.


Methods: None


Events: None


Description: Used to access the config forms objects.


 

IMPORTANT:


It is not possible to modify the script function parameters as they are not passed by reference from within Client Code. It is possible to return a value for those functions documented below by using the FunctionStatus object.


Class: CFunctionStatus – Object reference to parameters list


Properties: SetParameter(sParameterName as string, Value as variant)

GetParameter(sParameterName as string) as variant



Methods: AddParameter(sParameterName as string,, Optional Value)

ClearParameters

Events: None


Description: Exposed to scripting engine as FunctionStatus. This is how the VBScript changes the parameters for passing back to Client Code.


See examples section for numerous examples.




 

4. Client Code Form Events


These events are raised for the specified objects:


  FormObjectEvent_AfterFilter     ' Grid

  FormObjectEvent_AfterInitialise   ' Grid

  FormObjectEvent_AfterInsert     ' Grid

  FormObjectEvent_AfterLoad      ' Form

  FormObjectEvent_AfterLoadRecord   ' Form

  FormObjectEvent_AfterNewCompanyContact     ' frmCompany only

  FormObjectEvent_AfterSaveRecord   ' Form

  FormObjectEvent_AfterUpdate     ' Grid

  FormObjectEvent_BeforeDeleteRecord ' Form

  FormObjectEvent_BeforePopulation  ' Grid

  FormObjectEvent_BeforePrint     ' Form

  FormObjectEvent_BeforeSaveRecord  ' Form

  FormObjectEvent_Change       ' Field

  FormObjectEvent_Click        ' Button, Field, Menu, Grid

  FormObjectEvent_CoreEntityLookup  ' Form

  FormObjectEvent_DblClick      ' Field, Grid

  FormObjectEvent_DisplayCell     ' Grid

  FormObjectEvent_ExternalRefresh   ' Form

  FormObjectEvent_GotFocus      ' Field, Grid

  FormObjectEvent_KeyPress      ' Field, Grid

  FormObjectEvent_LostFocus      ' Field, Grid

  FormObjectEvent_RowLoaded      ' Grid

  FormObjectEvent_RowSelection    ' Grid

  FormObjectEvent_Timer        ' Form

  FormObjectEvent_ToolbarButton    ' Form

  FormObjectEvent_Unload       ' Form


 

5. Virtual Entities/Forms


Designer allows programmers to create ‘virtual entities’. These are defined as ad-hoc forms with no underlying entity. All entity fields would be populated entirely by the scripting object model.


The form can be invoked from anywhere and Designer will allow buttons, fields etc to be added to it with script code behind.


Virtual entities/forms are created in exactly the same manner as normal entities i.e. using the entity builder:


 


 


Pressing the New button loads the Entity Builder:


 


Note that when the Virtual Entity/Form check box is ticked, the Modal Form check box appears, and the Contact Management Links and Entity Links tabs become invisible (they are not of any use).


Enter the name of your entity and provide a sensible description to help self-document the entity.


If you want the form to be loaded modally, i.e. shown on-top of all other forms, centred on the screen, and has all focus such that the user must complete the form to return to Client Code, then check the Modal Form check box.


Select the Form Details tab. This allows you to set a caption for the form, and to assign an icon for it which will allow it to be available from the main list bar and it will also be shown on the top left of the form when loaded:

 


When you save your entity, you will then design the form in the same fashion as other forms, actions and queries. 


 


The main difference is that there are no fixed fields or reference fields and no default buttons. This is because this is a virtual form i.e. it sits within the Client Code infrastructure but does not have any business logic to read or save from the Client Code object model. The whole idea is that you code this logic entirely through script. This has the main advantage of letting you write your own application rules without having to tweak existing rules.


When you create config fields in the Designer for virtual forms, no fields are created on the SQL Server database. It is the responsibility of the script developer to write code which will read/write these fields to/from whatever data source is necessary, which f course may not be a SQL Server database.


When developing script, the standard objects and mechanisms are identical to writing script for other forms, such that virtual form code looks identical to other forms. You can cut & paste code from all parts of the system and re-use global functions etc…


The method of accessing form fields is the same too, with the table name being the name of the newly created entity + “ConfigFields” which is in keeping with the standard system nomenclature for accessing config fields e.g.:


With FormObjectsLocal.ShowForm

sForenames = .GetFieldValue("AddContactConfigFields", "Forenames")

sSurname = .GetFieldValue("AddContactConfigFields", "Surname")

End With


 

6. Client Code Object Model


This section contains detailed documentation of the Client Code Object Model. This object model is split into 2 parts for the purposes of the scripting engine:


• Forms Objects – these provide access to the form upon which the scripts are invoked

• Global Objects – these provide access to the global business object model



Form Objects Examples:


Dim formObjectsfrmMain, formField, formObj

Set formField = FormObjectsLocal.GetFormObjectByName(“Contact”, “Male”)

If formField.formWidget.Value = 1 then

formField.formWidget.Text = “Man”

Else

formField.formWidget.Text = “Woman”

End if


‘ Access another form using different global collection

Set formObjectsMain = FormObjectsGlobal(“frmMain”)

If not formObjectsMain is nothing then

Set formRef = formObjectsMain.FormRef

formRef.Caption = “Someone changed sex!”

End if



 

The exposed Client Code Objects are:


Global Object Description

Initialisation UsrCfgUsers table fields for current user + collections of contacts, companies etc..

Database The CDatabase object which exposes the methods to access the SQL Server database.

WordProcessor Invoke Word automation

StringFunctions String manipulation functions

MessageBox Show messages and ask questions of user

FileSystem Access the underlying windows file system

Selection Select business data e.g. contacts, companies from pop-up dialogue grids

DataWidgets The grid functions to operate upon on-screen grids

DbGet Get standard database functions

Numbers General purpose number manipulation

CrystalReports Underlying crystal report related functions not CRPE

FormFunctions The functions which control Client Code forms

Diagnostics Diags.out file access to record own messages to output file

Miscellaneous Functions such as auto-dial, load web browser etc..

ShellExecute Execute shell functions such as launch e-mail dialogue etc..

Registry Access the registry

AnimateWait Show animation dialogue similar to Client Code start up 

ProgressWait Show progress gauge or tick stages

BigInput Capture any text input from a dialogue box

ErrorHandling Standard error handler functions

MailMerge Mail merging functions


 

 

7. Example of setting ellipses lookups


Public Sub Form_AfterLoad ()

Call MakeWidgetEnabled("ContractConfigFields", "TSAuthoriser1", "btnLookupFind", False)

End Sub



Private Sub MakeWidgetEnabled(sTableName, sFieldName, sAssociatedWidgetName, bEnabledFlag)

Set widget = FormObjectsLocal.ShowForm.GetFieldWidget((sTableName), (sFieldName))

If Not widget Is Nothing Then

With FormObjectsLocal.FormRef.Controls(sAssociatedWidgetName)(widget.index)

.Enabled = False

'.backcolor = vbRed

End With

End If

End Sub


 

8. Step by Step Example of Master-Detail Dynamic Grid Handling


In this example, we create 2 grids using Designer on a form (or action) which are not linked to any entities and write script code to handle the interaction between them.


Step 1: Open form and create 2 grids in Designer.

Call these GridMaster and GridDetail.


 


Step 2: Place these on a convenient location like so:


 


 

Step 3: Save form design and test the design. The tab should look as follows:


 


Step 4: Open the script editor for this form and paste the following code into the editor:


Public Sub Form_AfterLoad ()

Call InitialiseGridMaster( _

FormObjectsLocal.ShowForm.GetFieldWidget( _

"RequirementConfigFields", "GridMaster"))

End Sub



' The table selection grid

Private Sub InitialiseGridMaster(dwGrid)

  Dim i

   

  If dwGrid Is Nothing Then

  MessageBox.appmsg "GridMaster Not found!"

  Exit Sub

  End If

   

  ' Initialise grid

  With dwGrid

  .Columns.RemoveAll

  .DataMode = 2 ' Add Item Grid

  .AllowAddNew = False

  .AllowUpdate = False

  .Caption = "Main Client Code Tables - Select to view details"

  .MaxSelectedRows = 1

  .RecordSelectors = True

  .SelectTypeRow = 1 'Single Select


' More settings

  End With

   

  ' Add columns

  With DataWidgets

    .AddColumnToGrid (dwGrid), 0, "Table Name", 4000, True

    .AddColumnToGrid (dwGrid), 1, "Actual Table Name", 2000, False

  End With

   

  ' Add Rows

  With dwGrid

.AddItem "©Contact©,©Contact©"

.AddItem "©Company©,©Company©"

.AddItem "©Placement©,©Contract©"

.AddItem "©Requirement©,©Requirement©"

.AddItem "©Users©,©Users©"


.AddItem "©FieldDescription©,©FieldDescription©"

.AddItem "©FormProperties©,©FormProperties©"

  End With


' Select first row and fire detail event

dwGrid.AddItemBookmark 1

Call InitialiseGridDetail( _

FormObjectsLocal.ShowForm.GetFieldWidget _

("CompanyConfigFields", "GridDetail"), "Contact")

End Sub


Public Sub Grid_RowSelection ()

Set fd = FormObjectsLocal.ShowForm. _

GetFieldDescriptionFromWidgetHwnd (FormObjectsLocal.EventControl.hWnd)

If fd Is Nothing Then Exit Sub

If fd.TableFieldName = "GridMaster" Then

Set dwGrid = FormObjectsLocal.EventControl

sTableName = dwGrid.Columns(1).Text

Call InitialiseGridDetail(FormObjectsLocal.ShowForm.GetFieldWidget _

("CompanyConfigFields", "GridDetail"), sTableName)

End If

End Sub


Private Sub InitialiseGridDetail(dwGrid, sTableName)

  Dim i, c


  If dwGrid Is Nothing Then

  MessageBox.appmsg "GridDetail Not found!"

  Exit Sub

  End If

   

  ' Initialise grid

  With dwGrid

  .Columns.RemoveAll

  .DataMode = 2 ' Add Item Grid

  .AllowAddNew = False

  .AllowUpdate = False

  .Caption = "First 10 Rows of Table: " & sTableName

  .MaxSelectedRows = 1

  .RecordSelectors = True

  .SelectTypeRow = 1 'Single Select

  .Redraw = False

  End With

 

i = 0

sSQL = "Select Top 10 * From " & sTableName

  Set rs = Database.OpenRecordset((sSQL))

  Do While Not rs.eof

  i = i+1

  If i = 1 Then

  ' Add columns

For c = 1 to rs.Fields.Count 

  DataWidgets.AddColumnToGrid (dwGrid), c-1, _

rs.Fields(c-1).Name, 1000, True

  Next

End If

' Add row to grid

sAddItemString = ""

For c = 1 to rs.Fields.Count 

If c > 1 Then sAddItemString = sAddItemString & ","

  sAddItemString = sAddItemString & "©" & _

StringFunctions.VBFormat(rs.Fields(c-1).Value, "@") & "©"

  Next

dwGrid.AddItem sAddItemString

rs.MoveNext

  Loop

  rs.close

   

  dwGrid.Redraw = True

End Sub


Public Sub Grid_DblClick ( Cancel )

Set fd = _

FormObjectsLocal.ShowForm.GetFieldDescriptionFromWidgetHwnd _

(FormObjectsLocal.EventControl.hWnd)

If fd Is Nothing Then Exit Sub

If fd.TableFieldName = "GridDetail" Then

Set dwGrid = FormObjectsLocal.EventControl

sField1 = dwGrid.Columns(0).Text

MessageBox.appmsg "You double clicked record: " & sField1 & "?"

End If

End Sub


Step 5: Edit the script if necessary and replace CompanyConfigFields with the table upon which your created the grids.


Step 6: Apply the changes and open the Client Code form and it should operate as follows when any row in the grid is selected in the master grid (with either the mouse click or cursor key up/down), the detail grid should show the first 10 records from the table:

 


The code demonstrates a number of features:

• Creation of empty grids with no standard functionality

• Dynamic creation of grid columns

• Grid Event processing: row selection and row drill down

• Master/Detail functionality which can be applied to any database tables

• Speed at which the script engine can interact with the database in real-time

• Flexibility of independence from the Client Code entity builder framework

 

9. Field Visibility Example Code – How to Use FormFields within ShowForm


See Chapter 70 – V2.00.070 onwards for much better method than that described below.


In order to fully address the issue of widget visibility from script, I attach some code which you will find useful.

 

Here is a technical explanation of the Client Code internal config forms engine:

Each CShowForm class (the run-time FormObjectsLocal.ShowForm object) has a number of child objects.

One of the objects is CFormField which is an on-form instance of the CFieldDescription class which describes the field.

By setting the Visible (or Enabled, BackColour, FontSize, ForeColour, Left, Top, Height, Width, ReadOnly, ShowLabel, ToolTipText) property of the CFormField object, you allow CShowForm to treat the field in the way you want it which overrides the form designed attributes.

 

The example below shows how I have done it for the contact availability date which I do not want to be viisible when the contact is a client.

The SetFieldVisible function can be placed on any form and used accordingly for any field.

 

' Hide/Show controls accordingly

Sub ProcessContactType()

 

  ' Determine whether the contact is a client or not

  bClient = Not FormObjectsLocal.ShowForm.GetFieldValue("Contact", _

"ContactTypeCandidate")

 

  ' Now set the visibility of these fields

  Call SetFieldVisible("Contact", "AvailabilityDate", Not bClient)

End Sub

 

' Generic function to set a fields (and labels) visibility

Private Sub SetFieldVisible(sEntityName, sTableFieldName, bVisible)

  Dim objField, objLabel, formField

     

  With FormObjectsLocal.ShowForm

   ' Get the widget and CFormField object for the specified field

   Set objField = .GetFieldWidget((sEntityName), (sTableFieldName))

   Set formField = .GetFormFieldFromFieldName((sEntityName), (sTableFieldName))

         

         

' If field found, set visiblity so that CShowForm class knows when to show

   If Not formField Is Nothing Then formField.Visible = bVisible

       

   ' Make field widget visible/invisible right now without refreshing whole form

   If Not objField Is Nothing Then objField.Visible = bVisible

     

   ' Do control label visibility now too

   Set objLabel = .GetFieldWidget((sEntityName), (sTableFieldName), 1)

   If Not objLabel Is Nothing Then objLabel.Visible = bVisible

  End With

End Sub

 

----- Original Message ----- 

Sent: 30 January 2001 17:33

Subject: Request for new facilities


> GaRRY,

>   There are a couple of things that I need:

> * When a config form is clicked on, all the controls refresh themselves. If there is any script on the form to modify the controls' appearance, the

> changes are lost when the refresh is completed. Can I have a hook to intercept the refresh?

> * When selecting a placement to display from the requirement form, the placement form is loaded but the controls are not displayed until the form

> is clicked on. Can you investigate this? I can send you an export schema if that will help.

 

10. Button Visibility Example Code – How to Use FormButton within ShowForm


In order to fully address the issue of button visibility from script, here is some code which you will find useful.

This is very similar to that in the previous chapter, but uses the form buttons collection.


Public Sub Button_Click

' Not interested in non-config buttons

if FormObjectsLocal.EventControl.Name <> "btn" then Exit Sub


Set btn = FormObjectsLocal.ShowForm.GetButtonFromIndex("btn", _

 FormObjectsLocal.EventControl.Index)

If btn Is Nothing Then Exit Sub


Select case btn.ButtonFunction

Case “The One To Make Invisible”

Call SetButtonVisibility(btn.ButtonFunction, False)

Exit Sub

End Select

End Sub


See chapter 70 for V2 details on easily setting visibility.


' Set the visibility of the specified button.

' This uses the CShowForm class to control the buttons visibility between 

‘ tab clicks

'

Private Function SetButtonVisibility(sButtonFunction, bVisible)

' Get the form button from the CShowForm class

Set formButton = FormObjectsLocal.ShowForm.GetButtonFromFunction( _

"btn", (sButtonFunction))

If formButton Is Nothing Then Exit Function

' Set its visibility

formButton.Visible = bVisible

' Now set the current widget instance visibility

Set btnWidget = formButton.FormWidget

If btnWidget Is Nothing Then Exit Function

' Only set the visibility if this button is in view

If FormObjectsLocal.ShowForm.isWidgetVisibleOnCurrentTab(btnWidget.hWnd) Then

btnWidget.Visible = bVisible

End If

End Function



 

 

11. Setting Selected Tab In Script


See Chapter 70 for V2 code.


Developers,

 

To force another tab to get focus on a config form and force redraw of the tab widgets, use the following code

 

With FormObjectsLocal.FormRef

  .Tabby.Tabs(2).Selected = True

  .tabby_click

End With

 

This performs the same processing as if the user selected the tab with the mouse.

The .tabby_click event corresponds to the Public Sub tabby_Click method of the underlying form (you VB'ers).

 

Garry.

-----Original Message-----

From: Matt Jessop [mailto:Mattb@itsquared.com]

Sent: 29 January 2001 09:52

To: 'garry@technical.demon.co.uk'

Subject: Selecting Tabs

Garry,

 I am using the following command to select differrent tabs on a form, it works, but it is not drawing the form objects until you click on the form somewhere.

 Me ? or you ?

 g

   FormObjectsLocal.FormRef.Tabby.Tabs(2).Selected = True




 

12. Detecting Which Button is Pressed


Create a button using Designer and give it a function name: “TabTest”


Drop it onto a form.


Write the following script code to catch the button click even for it:


Public Sub Button_Click()


' Not interested in non-config buttons

if FormObjectsLocal.EventControl.Name <> "btn" then Exit Sub


Set btn = FormObjectsLocal.ShowForm.GetButtonFromIndex(“btn”, _

FormObjectsLocal.EventControl.Index)


If btn is nothing then exit sub


If btn.ButtonFunction = “TabTest” Then

‘ Your code for this button in here

End if

End Sub


 

13. Firing an Action Button 


This tiny script allows you to invoke an action from a config form.

 

Steps to reproduce:

1. Create action 

2. Create an action button 

3. Add the action button to the form 

4. Write this code to fire the action associated with the button from script:



Sub YourFunction() 

  Call FireActionButton("Get In Touch")

End Sub

 

Private Sub FireActionButton(sActionName)

 

  Set btn = FormObjectsLocal.ShowForm.GetButtonFromFunction("btn", (sActionName))

  If btn Is Nothing Then Exit Sub

 

  ' Fire the action button

  btn.FormWidget.Value = True

End Sub


See next example for more code relating to this.

 

14. Firing an Action From Script Code 


This script allows you to invoke an action from any form using script code alone. 

In the example, a button is created with no link to an action or extension. This is then fired to operate upon the details of an independent script-controlled grid to fire the action using the grid data.


This makes use of an exposed function on the underlying form:

FormObjectsLocal.FormRef.RunFormAction ActionId, ContactId

 

Steps to reproduce:

1. Follow section above: Step by Step Example of Master-Detail Dynamic Grid Handling in order to create master/detail script controlled grids.

2. Create action (or reuse existing action e.g. Note/Call etc…) – Note the ActionId

3. Create a button called RunActionAgainstContactId – do NOT assign to an action.

4. Add the button to the form beneath the detail grid.

5. Write this code to fire the action associated with the button from script:



Public Sub Button_Click

' Not interested in non-config buttons

if FormObjectsLocal.EventControl.Name <> "btn" then Exit Sub


Set btn = FormObjectsLocal.ShowForm.GetButtonFromIndex("btn", _

 FormObjectsLocal.EventControl.Index)


If btn Is Nothing Then Exit Sub

If btn.ButtonFunction = "RunActionAgainstContactId" Then

Call RunActionAgainstContactId

Exit Sub

End If

End Sub



' Button clicked to fire an action against the currently selected contact

'

Private Sub RunActionAgainstContactId

Set dwGrid = FormObjectsLocal.ShowForm.GetFieldWidget( _

"CompanyConfigFields", "GridDetail")

If dwGrid Is Nothing Then Exit Sub

If instr(dwGrid.Caption, "Contact") = 0 Then

MessageBox.AppStopMsg _

"You can only fire this button when contacts are displayed in the grid."

Exit Sub

End If

' Get selected contactid

ContactId = CLng(GetGridCellValue(dwGrid, "ContactId"))

'MessageBox.appmsg "Running action against Id: " & ContactId

' ActionId=32 is Note

FormObjectsLocal.FormRef.RunFormAction 32, clng(ContactId), 0

End Sub




New: March 18th 2003 – New parameter now available in RunFormAction.


This is a comma delimited string containing ID’s of contacts.


e.g.


sContactId = “10, 20, 25, 26, 28”


FormObjectsLocal.FormRef.RunFormAction 32, 0, 0, (sContactId)


This will run the action over the 5 contact id’s listed in the string.




 

15. Validating Action Values


' Example to demonstrate the capturing in script of the on-screen fields

' in an action so that the action can be prevented from running if all

' expected fields are not correctly input by the user.

'

' Feb 2001

'


' Validate input here

Public Sub Form_BeforeSaveRecord ( Id, Cancel )

bCancel = Not ValidateActionFields

If bCancel Then FunctionStatus.SetParameter("Cancel") = True

End Sub


Private Function ValidateActionFields()

' Check the candidate Id - must be set when creating a placement

' candidateId = FormObjectsLocal.ShowForm.GetFieldValue("ContractPlacement", "CandidateId")

Set candidateIdCollection = FormObjectsLocal.Formref.candidateIdCollection

If candidateIdCollection Is Nothing Then

bCandidateError = True

Else

If candidateIdCollection.count = 0 Then

bCandidateError = True

Else

If clng(candidateIdCollection(1)) = 0 Then

bCandidateError = True

End If

End If

End If

If bCandidateError Then

MessageBox.AppStopMsg "Please select a candidate for the placement."

Exit Function

End If

' Make sure rates are set

payRate = cdbl(FormObjectsLocal.ShowForm.GetFieldValue("Requirement", "StandardPayRate"))

If payRate <= 0 Then

MessageBox.AppStopMsg "Please enter a valid candidate pay rate."

Exit Function

End If

chargeRate = cdbl(FormObjectsLocal.ShowForm.GetFieldValue("Requirement", "StandardChargeRate"))

If chargeRate <= 0 Then

MessageBox.AppStopMsg "Please enter a valid client charge rate."

Exit Function

End If


' Make sure we have valid dates

startDate = FormObjectsLocal.ShowForm.GetFieldValue("Requirement", "EarliestStartDate")

If len(startDate) = 0 Then

MessageBox.AppStopMsg "Please enter a start date for the placement."

Exit Function

End If

endDate = FormObjectsLocal.ShowForm.GetFieldValue("Contract", "ContractEndDate")

If len(endDate) = 0 Then

MessageBox.AppStopMsg "Please enter an end date for the placement."

Exit Function

End If

' All OK?

ValidateActionFields = True

End Function


 


16. Creating a Lookup Combo


This procedure takes the example of creating a job title combo on the company form and populating it with job titles. This is not possible to do using reference fields.

 

Steps to reproduce:


a. Open Designer for company form and create a new config field called JobTitles.

b. This should be of type Lookup, not searchable, and with no values.

c. Drop this onto the company form

d. Write the following piece of code into the script editor for the form:



Public Sub Form_AfterLoad

' JobTitles Combo

Call InitialiseJobTitlesCombo _

(FormObjectsLocal.ShowForm.GetFieldWidget( _

“CompanyConfigFields”, “JobTitles”)) 

End Sub



‘ JobTitles Combo

Private Sub InitialiseJobTitlesCombo(cmbJobTitles) 


If cmbJobTitles Is Nothing Then Exit Sub

cmbJobTitles.clear

MySQL = “Select JobTitle From JobTitle” & _

“ Order by Ordering”

Set rs = Database.OpenRecordset((MySQL))

Do While Not rs.EOF

sJobTitle = rs(0)

With cmbJobTitles

.AddItem sJobTitle

If .ListCount = 1 Then .ListIndex = 0

End With

rs.MoveNext

Loop

rs.Close

End Sub


The widget on the form should look as follows when run in Client Code:


 

 

17. Asking the User to Choose from a List


This procedure shows how to use the internal frmSelection form to offer the user a choice of items in order to choose one.

 


Private Sub SelectionFormTest

Set frmSelection = FormFunctions.GetFormReference("frmSelection")

With frmSelection

.ClearItems

.AddItem "1. Do Nowt at " & Now

.AddItem "2. Do Something at " & Now

.Caption = "Selection Test"

.label = "Please select any option"

.ShowModal

If Not .Selected Then Exit Sub

MessageBox.AppMsg "You selected '" & .SelectedItem & "' ?"

End With

End Sub


For a discussion and examples of using the more sophisticated SQL selection mechanism, see the following section.


 

18. Using SQL Selection Object


This procedure shows how to use the internal SQL selection object (5.00.063 or later) to pass in a SQL statement, setup grid columns etc and retrieve the selected records from a modal dialogue box.

It is of use when the you need to offer the user a choice of selecting a range of records from any table or tables in the database.


' This function will allow the user to select a candidate contact from

' a pop-up and will show the correct priority and type to boot!

Private Sub SQLSelectionFormTest

Set SQLSelection = Selection.GetSelectSQLObject

With SQLSelection

.Columns = 6

.ColWidth(0) = 0

.ColWidth(1) = 0

.ColWidth(2) = 0

.ColWidth(3) = 1500

.ColWidth(4) = 2000

.ColWidth(5) = 1200

.Caption = "Select any Candidate"

.SQL = "Select Top 50 ContactId, Priority, Type, Christian as Forenames, " & _

“Surname, DateOfBirth as 'Date of Birth'" & _

" From Contact" & _

" Where Contact.Type = 1" & _

" Order by Surname, Christian"

.ContactSelection = True

.PriorityColumn = 1

.TypeColumn = 2

If Not .Selection Then Exit Sub

MessageBox.AppMsg "You selected ContactId:" & .SelectedRecord(1) & " ?"

End With

End Sub



' SQL selection of any number of placements

Private Sub SQLSelectionPlacement

Set SQLSelection = Selection.GetSelectSQLObject

With SQLSelection

.Columns = 4

.ColWidth(0) = 0

.ColWidth(1) = 1200

.ColWidth(2) = 2000

.ColWidth(3) = 2500

.Caption = "Select one or more Placements"

.SQL = "Select ContractId, Reference, Company.Name as Company" & _

", Christian + ' ' + Surname as Candidate" & _

" From Contract, Company, Contact" & _

" Where Contract.CompanyId = Company.CompanyId" & _

" And Contract.CandidateId = Contact.ContactId" & _

" Order by Reference"

.MultipleSelection = True

If Not .Selection Then Exit Sub

For i = 1 to .SelectedCount

If i > 1 Then s = s & ", "

s = s & clng(.SelectedRecord(cint(i)))

Next

If len(s) > 0 Then MessageBox.appMsg "You selected " & .SelectedCount & _

" placements: " & s & "?"

End With

End Sub


Note how you can set up the column widths, specify multiple record selection (using shift/ctrl keys), set caption, assign SQL statement, cater for contact priorities and contact types to allow colours.

 

19. Modal Virtual Form Interaction


This procedure shows how to use the modal virtual form capability of Client Code (5.00.063 or later) by invoking it from script code to display defaults, validate the data and return the data to the caller.


Step 1: Create a modal virtual entity


 


Step 2: Design the modal virtual entity form and create data capture fields


 


Step 3: Design the modal virtual entity script code to capture data and validate it


 


' On button click - validate input and close if OK

Public Sub Button_Click ()

With FormObjectsLocal.showform

sForenames = .GetFieldValue("AddContactConfigFields", "Forenames")

sSurname = .GetFieldValue("AddContactConfigFields", "Surname")

If len(sForenames) = 0 or len(sSurname) = 0 Then

MessageBox.AppStopMsg "Please enter both forenames and surname." & _

sForenames & sSurname

Exit Sub

End If

End With

' All OK, then close form using correct exposed method to preserve stacked scripts

FormObjectsLocal.FormRef.FormUnload

End Sub



Step 4: Create a button on the calling form for the user


 


 


Step 5: Write code for the button to invoke the modal virtual form, pass in default values and capture validated values from it.


Public Sub Button_Click

' Not interested in non-config buttons

if FormObjectsLocal.EventControl.Name <> "btn" then Exit Sub


Set btn = FormObjectsLocal.ShowForm.GetButtonFromIndex("btn", _

FormObjectsLocal.EventControl.Index)

If btn Is Nothing Then Exit Sub

If btn.ButtonFunction = "AddContact" Then

Call AddContactButton

Exit Sub

End If

End Sub


' Show a modal virtual entity to capture contact details to add to our grid

Private Sub AddContactButton

Set dwGrid = FormObjectsLocal.ShowForm.GetFieldWidget("CompanyConfigFields", "GridUpdate")

If dwGrid Is Nothing Then Exit Sub


Set frmModalForm = FormFunctions.GetFormReference("frmAddContact")

If frmModalForm Is Nothing Then

MessageBox.AppStopMsg "Could not find form: frmAddContact"

Exit Sub

End If

With frmModalForm

' Send initialisation data in

.SetFieldValue("Forenames") = StringFunctions.VBFormat(Now, "DDDD DD")

.SetFieldValue("Surname") = StringFunctions.VBFormat(Now, "MMMM YYYY")

' Show it

.ShowModal


' After modal form is closed, read the values from it

sForenames = .GetFieldValue("Forenames")

sSurname = .GetFieldValue("Surname")

End With


If len(sForenames) > 0 and len(sSurname) > 0 Then

  dwGrid.AddItem "©" & sForenames & "©,©" & sSurname & "©"

  End If

End Sub



Step 6: What it looks like in operation:


 


Step 7: This data is then added to the underlying form grid:


 


 

20. Action Form Collection Manipulation


This procedure shows how to add a contact to the list of default contacts in an action collection.

It also demonstrates how to use the FastContactSearch function to pop-up a contact selection dialogue.


Sub Form_AfterLoad

Dim coll, i, s


Set coll = FormObjectsLocal.formref.ContactIdCollection

If coll Is Nothing Then Exit Sub

If coll.count = 0 Then Exit Sub

' Add another contact

ContactId = Selection.FastContactSelection() 

If ContactId = 0 Then Exit Sub

' Add to collection

FormObjectsLocal.formref.ContactIdCollection.Add cstr(ContactId)

' Show visuals

FormObjectsLocal.ShowForm.SetFieldValue("CallClient", "ClientContactId") = _

"< List: " & FormObjectsLocal.formref.ContactIdCollection.Count & _

" Client Contact records >"

End Sub


 

21. Firing A Report From Script


Reports can be easily invoked from the print button or by a new button which is linked to a specific report. This works fine for standard entities or new entities, where all record processing is taken care of when set up in Designer by the forms engine.


If however, a virtual entity has been implemented, or the report requires specific pre-processing (as opposed to writing script inside a report form), then invoking the report from script is the appropriate method.


This section shows how this is done.


Step 1: Create an entity.

Step 2: Create a Crystal report which links the tables and configure this in forms designer (see reports design and tutorial document).

Step 3: Add a button onto the form, but do not set it up as a report button as it will defeat the object of this example!

Step 4: Copy this code into the form script editor. Note how we capture the loaded or saved record id and this is used to create a SQL IN clause which is passed into the PrintReport function of the Initialisation.Reports collection.


Dim m_AdvertisementId ' as Long


Public Sub Form_AfterLoadRecord ( Id )

m_AdvertisementId = id

End Sub


Public Sub Form_AfterSaveRecord ( Id )

m_AdvertisementId = id

End Sub


Public Sub Button_Click

' Not interested in non-config buttons

if FormObjectsLocal.EventControl.Name <> "btn" then Exit Sub


Set btn = FormObjectsLocal.ShowForm.GetButtonFromIndex _

("btn", FormObjectsLocal.EventControl.Index)

If btn Is Nothing Then Exit Sub

If btn.ButtonFunction = "PrintReportFromScript" Then

Call PrintReportFromScript

Exit Sub

End If

End Sub


Private Sub PrintReportFromScript

If m_AdvertisementId > 0 Then

Initialisation.Reports.PrintReport "Advert Test.rpt", _

"(Advertisement.AdvertisementId = " & m_AdvertisementId & ")"

End If

End Sub


Please see section 49 on Report Formulas and Script for more detail and examples.

 

22. Using Classes in VBScript – Object Oriented Programming


VBScript version 5 is supported in Client Code. This adds sophisticated capabilities to VBScript, namely support for classes. Classes provide programmers with the opportunity to develop fully object oriented code by encapsulating re-usable business logic into a class. The class can be implemented to abstract the complexity of the logic by exposing a neatly designed interface which can be used by the programmer.


The potential of classes is shown in this very simple example:

Create a class inside a global module in Designer :


 


This is the code:


Class CTestClass

Private sTestString

Property Get TestString

TestString = sTestString

End Property

Property Let TestString (s)

sTestString = s

End Property

End Class


The class is called CTestClass (classes should be prefixed by C). The class contains its own private variable calls sTestString which can only be accessed by users of the class using the properties defined.

There are 2 properties, both called TestString which respectively Get and Set (via the Let operator) the value of the private variable. Private variables cannot be accessed directly by users of the class.


To see how the class might be used, see the following code:


Set m_TestClassObject = New CTestClass

m_TestClassObject.TestString = _

StringFunctions.VBFormat(Now, "DD-MMM-YYYY HH:nn:ss")

MessageBox.appmsg "CTestClass.TestString=" & m_TestClassObject.TestString


The code creates a new instance of CTestClass thus creating an object (the definition of an object in OOP (object oriented programming) is that it is an instance of a class) which exposes all of the properties/functions/subroutines of the class.

Thus in the above example, we assign a value to the TestString property (Let), then display it (Get).

Clearly the properties can do much more that simply set a value e.g. perform calculations, access the database, send messages etc… 


When you access the Client Code objects e.g. Database, this is actually an internal (VB6) object which is an instance of the class CDatabase.

 

23. Programming Grid Column/Cell Colours


The grid is a Sheridan Data Widget. Thus the properties and methods and events for the grid must follow this conventions demonstrated below.

This example processes the Grid_RowLoaded event and colours a contact grid according to the normal contact colours for type and priority, but also colours rows of another grid where the cell is set to a particular value:

 

There are examples of how to do this in the ITContract database: Company and Contact forms. Look for Grid_RowLoaded event.

 

This is how to set the row colours for any grid:

 

Sub Grid_RowLoaded

  With FormObjectsLocal.EventControl

    .Columns(2).BackColor = vbRed

    .Columns(2).ForeColor = vbBlue

  End With

End Sub

 

And for a tighter control, you can do this:

 

Public Sub Grid_RowLoaded ()

Set dwGrid = FormObjectsLocal.EventControl

If dwGrid Is Nothing Then Exit Sub

Set fd = _

FormObjectsLocal.ShowForm.GetFieldDescriptionFromWidgetHwnd (dwGrid.hWnd)

If fd Is Nothing Then Exit Sub

Select Case fd.TableFieldName

Case "GridDetail"

If instr(dwGrid.Caption, "Contact") > 0 Then

  Call DataWidgets.ColourContactPriorities ((dwGrid), _

        CInt(GetGridCellValue(dwGrid, "Type")), _

        CInt(GetGridCellValue(dwGrid, "Priority")) )

End If

Case "GridUpdate"

' Colour the row with my name in it to blue with yellow text

sName = GetGridCellValue(dwGrid, "Surname")

If instr(sName, "Lowther") > 0 Then

 styleSetName = "Style" & vbYellow & vbBlue

     

    With dwGrid

    .StyleSets((styleSetName)).ForeColor = vbYellow

    .StyleSets((styleSetName)).BackColor = vbBlue

     

    For i = 0 To .Columns.Count - 1

      .Columns(i).CellStyleSet ((styleSetName))

    Next

  End With

End If

End Select

End Sub

 

Private Function GetGridCellValue(dwGrid, ColumnName)

  With dwGrid

   For i = 0 to .Columns.Count - 1

      If lcase(.columns(i).Caption) = lcase(ColumnName) Then

        GetGridCellValue = .Columns(i).Value

        Exit Function

      End If

   Next

  End With

End Function

 

This last example uses the knowledge about contact types to colour the grid based upon the users preferences.


 

24. Multi-Select Grid Rows


In order to create a grid with multi-select rows, the .SelectTypeRow property must be set to either 2 or 3:


' Initialise grid columns

With dwGrid

.Columns.RemoveAll

  .DataMode = 2 ' Add Item Grid

  .AllowAddNew = False

  .AllowUpdate = False

  .Caption = "Placements for " & sSalesOffice

  .MaxSelectedRows = 8

  .RecordSelectors = True

  .SelectTypeRow = 2 ' 1 - single, 2 - multi

End With


In order to select all highlighted rows into an array, use this code:



  m_NumSelectedPlacementIds = 0

  With dwGrid

    ' Multi select - Get in order as appear in grid

    For r = 0 To .Rows - 1

      bm = .AddItemBookmark(r)

      For i = 0 To dwGrid.SelBookmarks.Count - 1

        If CStr(bm) = CStr(.SelBookmarks(i)) Then

        m_NumSelectedPlacementIds = m_NumSelectedPlacementIds + 1

        ReDim Preserve m_SelectedPlacementIds _

(cint(m_NumSelectedPlacementIds))

        m_SelectedPlacementIds(m_NumSelectedPlacementIds) = _

CLng(.Columns(0).cellValue(bm))

        End If

      Next

    Next

  End With


 

25. Launching Core Entity Forms From Script


If a grid is under script control and the user is permitted to drill down into the underlying record, these functions should be used :


If FormFunctions.isformloaded("frmPerson") Then

Set f = FormFunctions.GetAFormInstance("frmPerson", True)

Else

' Load new form then

Set f = FormFunctions.LoadFormByName("frmPerson", clng(ContactId), _

"Script Contact")

End If

f.LoadContactDetails clng(ContactId)



' Load entity form using same technique

If FormFunctions.isformloaded("frmContractorInvoice") Then

Set f = FormFunctions.GetAFormInstance("frmContractorInvoice")

Else

' Load new form then

Set f = FormFunctions. LoadFormByName ("frmContractorInvoice",1, _

"Script ContractorInvoice")

End If

f.LoadEntityDetails 1



Or for new entity forms


If FormFunctions.isformloaded("frmAdvertisement") Then 

  Set f = FormFunctions.GetAFormInstance("frmAdvertisement")

  f.LoadEntityDetails 0

  FormFunctions.AddFormToTabView (f), ""

Else

  Set f = FormFunctions.LoadFormByName("frmAdvertisement", 0, "New Advert")

End If



 

26. Using FileSystemObject in VBScript


The file system object component (SCRRUN.DLL) needs to be installed and registered for it to be used in script.

This gives access to the FileSystemObject which exposes many file system features. Search MSDN to find complete list. (“Microsoft Scripting Run-time Library Features”)


This example demonstrates how to use the object from a Client Code VBScript function to get attributes from a folder and file and to copy a single file:


 

Private Sub CopyFileUsingFileSystemObject

 Dim fso, fldr, f

 

 Set fso = CreateObject("Scripting.FileSystemObject")

 If fso Is Nothing Then

  MessageBox.appstopmsg "Could not instantiate the Scripting.FileSystemObject"

  Exit Sub

 End If

 

 Set fldr = fso.GetFolder("c:")

 MessageBox.AppMsg "Folder name: " & fldr.Name

 

 Set f = fso.GetFile("c:\Winnt\ClientCode32.ini")

 MessageBox.AppMsg "Last modified: " & f.DateLastModified

 

 ' Copy file

 fso.CopyFile "c:\winnt\ClientCode32.ini", "c:\temp\Copied.ini"

End Sub

 

 

27. Read all Grid Rows


If a grid is scripted, i.e. one that you have total control over?

If so it is known as an AddItem grid. Other grid population mechanisms are known as Bound (bound to a SQL recordset) and Unbound (grid calls you back for each row of data it requires). Standard Client Code grids usually use Unbound as they are more efficient.

Scripting currently cannot populate grids using any technique other than AddItem.


This example demonstrates reading a single column from all rows in the grid:


Dim strAdvertIdList, bm  ' Bookmark 

 

strAdvertIdList = ""

With dwGrid

  For row = 1 To .Rows

    bm = .AddItemBookmark(row - 1)

    if len(strAdvertIdList) > 0 then strAdvertIdList = strAdvertIdList & ", "

    strAdvertIdList = strAdvertIdList & .Columns(0).CellValue(bm) 

  Next

End With

 

 

28. External Document Launching 


These functions are useful for launching the operating system features to open browsers, e-mail, auto-dial and documents:

 

' Fire E-Mail Client to send mail to recipient Miscellaneous.EMailHotLink(sEMailAddress as String)

     

' Fire Browser to view address 

Miscellaneous.WorldWideWebHotLink(sWWWAddress as String) 

 

' Play specified .wav file 

Miscellaneous.PlaySound(sWaveFile as String)   

     

' Dial specified number using TAPI

Miscellaneous.AutoDialNumber(sTelNumber as String)     

 

' Fire Shell Association program

ShellExecute.OpenThisDoc(hWnd as long, sFilename as string)

 

In this last function, you will need to pass in the actual window handle.

Do this as follows:

 

' Check if file exists

if FileSystem.FileExists(sFilePathAndName) then

  ' Open the specified file.

  ' This function will open word for .DOC's, IE for .jpg, gif etc...,

  ‘ media player for .mpg etc...

  ShellExecute.OpenThisDoc FormObjectsLocal.FormRef.hWnd, (sFilePathAndName)

end if

 


 

29. Global Parameter Value


Each script runs in its own address space for the current form.

The only ways to share variables between forms/scripts is to either communicate through the database (slow/inefficient) or via formobjectsglobal and access form widgets (cumbersome).


A neater solution is to have a shared collection of parameters which can be accessed from any script direct into/out of memory.


This is accomplished by a let/get property on the Initialisation object:


Public Property Let GlobalParameterValue(sParameterName as String, sValue as Variant)

Public Property Get GlobalParameterValue(sParameterName as String) as Variant

 

This is used in the following example:


' Assign a variable to the global parameters

Initialisation.GlobalParameterValue(“PlacementId”) = 100


…….


' Retrieve a variable from the global parameters from any form script

PlacementId = clng(Initialisation.GlobalParameterValue(“PlacementId”))


 

30. Launching E-Mail Client


In order to send an e-mail from script, an object reference to the Client CodeEMailGateway.dll must be instantiated. This provides a generic encapsulation of the various e-mail clients and an easy to use set of properties and methods which allow for e-mails to be sent to many recipients with attachments.


This example demonstrates the mechanism:


Sub SendEMail

Const RecipientTo = 1

Const RecipientCC = 2

Const RecipientBCC = 3

Const ProtocolMAPI = 1

Const ProtocolOutlook = 4

Const ProtocolMailTo = 5


' Get the e-mail address and attachment to send

sEMailAddress = FormObjectsLocal.ShowForm.GetFieldValue( _

"ReferenceConfigfields", "RefereeEmail")


sAttachment = FormObjectsLocal.ShowForm.GetFieldValue( _

"ReferenceConfigfields", "ReferenceRequest")


sCandidateName = FormObjectsLocal.ShowForm.GetFieldValue( _

"ReferenceRequest" , "CandidateId")



' Get the normal Client Code Email object references from Client CodeEMailGateway.dll

With EMailGateway

.NewMessage

.AddRecipient sEMailAddress, sCandidateName, RecipientTo

   

.Subject = "Reference request for " & sCandidateName


  .Message = "Please find attached a request for a reference."

  .AddAttachment sAttachment


  .ShowDialogue = True

  .Send(sErr)

End With

End Sub


 

31. Colouring Tabs


It may be necessary to colour the tabs on data entry forms for data validation or to draw attention to certain tabs or even to satisfy the artistic design tendencies of customers!


This is how to do this:


set tab = FormObjectsLocal.FormRef.tabby.Tab(1)    ' First tab

 

with tab

  ' Set the tab back colour

  .BackColor = vbRed   ' Or any RGB colour

  .BackColorSource = 1  ' Important - it can’t work this out on its own!

 

  ' Set the tab foreground colour - the text

  .ForeColor = vbYellow  ' Or any RGB colour

  .ForeColorSource = 1  ' Important - it can’t work this out on its own!

end with



 

32. Crystal Report Form Script


When a report has been designed to have an associated data input form to capture parameters (See Reports Design Document and Tutorial), it is possible (build 5.00.081 or later) to manipulate the complete query before this is passed to the report for execution.


This allows the script developer to manipulate the query and add/remove portions of the SQL query depending upon user input. This example is taken from the report tutorial:


This example demonstrates the mechanism:


' This function is called after the user has requested the print of the report having filled in 

' all on-screen values.

'

' This is the query from Client Code when invoked:

'

'Select

'*

'From

' ((Requirement Inner Join Contact Contact On

'Requirement.ContactId = Contact.ContactId)

'Inner Join Users On

'Requirement.UserId = Users.UserId)

'Inner Join Company On

'Requirement.CompanyId = Company.CompanyId

'

'Where 1=1 

' And Requirement.RequirementId In 

' (12,13,7,16,90,42,19,48,60,61,64,65,66,70,71,72,88,92,91,6,32,34,14,21) 

' Or Requirement.RequirementId In ' (77,10,4,94,96,95,1,39,40,44,68,79,82,83,85,86,87,5,93,11)

'Order By Requirement.Reference ASC


Public Sub Form_AfterSaveRecord ( Id )

'MessageBox.appmsg "Pre Tweak: " & vbcrlf & ReportProperties.SQLQuery 

' Tweak the formula now

ReportProperties.SQLQuery = RemoveRequirementIdInClause


' Get the input reference

sReference = FormObjectsLocal.ShowForm.GetFieldValue("RequirementsListConfigFields", "AField")

If len(sReference) > 0 Then 

ReportProperties.SQLQuery = _

AddAndClause("And Requirement.Reference Like '%" & sReference & "%'")

End If


'MessageBox.appmsg "Post Tweak: " & vbcrlf & ReportProperties.SQLQuery 

End Sub


Private Function RemoveRequirementIdInClause()

Dim x, y

Const AndRequirementIdIn = "And Requirement.RequirementId In ("

Const OrRequirementIdIn = "Or Requirement.RequirementId In ("

sFormula = ReportProperties.SQLQuery

Do While True

x = instr(sFormula, AndRequirementIdIn)

If x = 0 Then x = instr(sFormula, OrRequirementIdIn)

If x = 0 Then Exit Do

' Now find matching closing bracket

y = instr(x, sFormula, ")")

' Get pre and post string segments

sPre = Left(sFormula, x - 1)

sPost = Mid(sFormula, y+1)

' Assemble again

sFormula = sPre & sPost

Loop

RemoveRequirementIdInClause = sFormula

End Function


Private Function AddAndClause(sAndClause)

Dim sFormula

sFormula = ReportProperties.SQLQuery

' Find Order by

x = instr(sFormula, "Order By")

If x > 0 Then

sFormula = Mid(sFormula, 1, x-1) & " " & sAndClause & " " & Mid(sFormula, x)

End If

AddAndClause = sFormula

End Function


 

33. Automating Word


Script can access the same word processor classes as used by Client Code. This is exposed through the WordProcessor object as the following example demonstrates:


Private Sub WordMagic

With WordProcessor

.Initialise False

.OpenFile "D:\CVData\Upload\File1.doc", False, True

  .AppendFile "D:\CVData\Upload\File2.doc", True

  .SaveAs "D:\CVData\Upload\" & StringFunctions.VBFormat(Now, _

"YYYYMMMDDhhnnss") & ".rtf"

End With

End Sub


In the above example, File1 is opened in word and File2 is appended to it. This is then saved as an RTF file with the current date/time stamp.


Other useful functions for automating word are:


Public Sub CloseFile(sFileName as String) ‘ Close named file

Public Property Get Description() As String ‘ Get name of word processor version

Public Property Get GetFileContents() As String ‘ Get unformatted text from open document

Public Property Let SetFileContents(s As String) ‘ Set unformatted text in open document

Public Sub HighlightWord(sWord as String) ‘ Highlight word in open document

Public Sub MailMerge(sDataSource as String) ‘ Mail merge current document using data source

Public Sub NewFile() ‘ Open a new document

Private Sub ReplaceInSection(section As Object, sOldWord As String, sNewWord As String)

‘ Replace old word with new word in named section


 

34. Updateable Grid for Data Entry


This section demonstrates how to create an updateable grid and use it for data input purposes:


Create a new field of type Grid called: GridUpdate.


 


Create a button to retrieve the grid data with button function: GridUpdateButton.

Open script editor to write code to initialise the grid:


' Editable grid

Call InitialiseGridUpdate( _

FormObjectsLocal.ShowForm.GetFieldWidget("CompanyConfigFields", _

"GridUpdate"))


….


' The editable grid which can capture data input and validate it.

' Can also allow add new rows if necessary.

Private Sub InitialiseGridUpdate(dwGrid)

  Dim i

   

  If dwGrid Is Nothing Then

  MessageBox.appmsg "GridUpdate Not found!"

  Exit Sub

  End If

   

  ' Initialise grid

  With dwGrid

  .Columns.RemoveAll

  .DataMode = 2 ' Add Item Grid

  .AllowAddNew = True

  .AllowUpdate = True

  .Caption = "Updateable Grid"

  .MaxSelectedRows = 1

  .RecordSelectors = True

  .SelectTypeRow = 1 'Single Select

  End With

   

  ' Add columns

  With DataWidgets

    .AddColumnToGrid (dwGrid), 0, "Forename(s)", 1500, True

    .AddColumnToGrid (dwGrid), 1, "Surname", 1500, True

  End With

   

  ' Add Rows

  With dwGrid

.AddItem "©Garry©,©Lowther©"

.AddItem "©Matt©,©Jessop©"

  End With

End Sub


‘ Capture button click

If btn.ButtonFunction = "GridUpdateButton" Then Call GridUpdateButton


….


' Called when the button is pressed to see the data

Private Sub GridUpdateButton

Set dwGrid = FormObjectsLocal.ShowForm.GetFieldWidget( _

"CompanyConfigFields", "GridUpdate")

If dwGrid Is Nothing Then Exit Sub

  On Error Resume Next

  With dwGrid

    ' Important to force save of existing updated data

    .Update

     

    For row = 1 To .Rows

      bm = .AddItemBookmark(row - 1)

       

      If row > 1 Then s = s & VbCrLf

      For col = 0 To .Columns.Count - 1

        s = s & " " & .Columns(col).cellValue(bm)

      Next

    Next

  End With

   

  MessageBox.appmsg "Grid contents: " & s & "?"

End Sub


Now run Client Code and open the form, the initial data should appear as follows:


 


You can then type into any row and update the text.

To add new rows, simply place the cursor into any cell in the row marked with an asterisk and all further rows can be added this way as shown below:


 

Pressing the Show Data button will show the input data.  


In order to capture when user is inputting data, write code in the Grid_AfterUpdate event:


 


 

35. Programmed Fields Re-Appearing on Record Save


The problem with saving from tabs and causing fields under script control to show incorrectly can be a problem in certain circumstances.

The simple fix is to write code for the form after save record event which will force the first tab into focus, or if this is not acceptable, then the following script sample demonstrates how to cause the tabs to redraw the visible fields so that the fields do not show up:

 

' Called after record has been saved.

Public Sub Form_AfterSaveRecord ( Id )

 

 ' Force a refresh of the currently selected tab so that programmed fields do not 

 ' show up on save when curren tab is not the first.

 With FormObjectsLocal.FormRef

   selectedIndex = .tabby.selectedtab.index

   If selectedIndex <> 1 Then 

     .Tabby.Tabs(1).Selected = True

     .Tabby.Tabs(selectedIndex).Selected = True

     .tabby_click

   End If

 End With

End Sub


 

36. AddItem Grid Rows


Build 1.00.015 has easy to use functions to add fields and entire recordsets to a grid:


DataWidgets.AddItem (dwGrid As SSDBGrid, ParamArray FieldList())

DataWidgets.AddRecordsetRow (dwGrid As SSDBGrid, rs As Recordset)

DataWidgets.PopulateGridFromSQLQuery (dwGrid As SSDBGrid, sSQL as String)

DataWidgets.InitialiseAddItemGrid (dwGrid As SSDBGrid, sCaption as string)


This function is passed the handle to a grid and a list of values for each column:


‘ Initialise grid columns, SQL etc..

Set rs = Database.OpenRecordset( (sSQL) )

Do while not rs.EOF

DataWidgets.AddItem (dwGrid), rs(“ContactId”), rs(“FullName”)

rs.MoveNext

Loop

rs.Close


If the recordset contains all possible columns and you do not want to do any field manipulation (formatting, lookups), another function can be used which accepts a recordset:


‘ Initialise grid columns, SQL etc..

Set rs = Database.OpenRecordset( (sSQL) )

Do while not rs.EOF

DataWidgets.AddRecordsetRow (dwGrid), (rs)

rs.MoveNext

Loop

rs.Close


Both of these functions remove the need to manipulate strings and field delimiters in order to add a row to a grid.


The ultimate function is PopulateGridFromSQLQuery which requires only a reference to the grid and a SQL statement in order to fully populate itself. It even generates the columns in the grid from the query if none exist.


‘ Get the top 50 companies ordered by company name

sSQL = “Select Top 50 * From Company Order by Name”

DataWidgets.PopulateGridFromSQLQuery (dwGrid), (sSQL)


This one function call populated the entire grid from a SQL statement.


A full (very small) example of how to populate a grid created in Designer is:


Public Sub Form_AfterLoad()


Set dwGrid = FormObjectsLocal.ShowForm.GetFieldWidget( _

“EntityName”, “GridFieldName”)

if dwGrid is nothing then exit sub


With DataWidgets

.InitialiseAddItemGrid (dwGrid), “Top 50 Contacts”

.PopulateGridFromSQLQuery (dwGrid), “Select top 50 * From Contact”

End With

End Sub

37. Unbound Virtual Grid Population


Build 1.00.015 has easy to use functions to bind a SQL statement to a grid to provide:


• Virtual unbound recordsets – the data is demand paged from the server

• Sortable columns – standard column sorting (right mouse column header click)

• Automatic column generation – for prototyping purposes, all columns are generated


The techniques demonstrated here should be used for all grids which can be fully populated from a SQL statement.


The following new functions are now available:



DataWidgets.InitialiseUnboundGrid (dwGrid As SSDBGrid, sCaption as string)


DataWidgets.PopulateGridFromSQLQuery (dwGrid As SSDBGrid, sSQL as String)



Note: the word unbound derives from the 3 modes that the data grid can operate:

• Add Item – rows are added to the grid 1 at a time like a list box. This is used for small non-SQL oriented data sets.

• Bound – the grid is bound to a data control on a form. This is useful when developing prototypes but does not scale to production databases.

• Unbound – the grid is populated from a SQL recordset which is demand paged from the server. 


A simple example demonstrates how to add an unbound grid to view all companies:


Public Sub Form_AfterLoad()


Set dwGrid = FormObjectsLocal.ShowForm.GetFieldWidget( _

“EntityName”, “GridFieldName”)

if dwGrid is nothing then exit sub


With DataWidgets

.InitialiseUnboundGrid (dwGrid), “All Companies”

.PopulateGridFromSQLQuery (dwGrid), “Select * From Company”

End With

End Sub


Not only can the grid now efficiently process very large recordsets, but it also automatically provides column sorting:  by right mouse clicking on the column header.


Warning: Column sorting is very generic so you must obey an important rule when writing your SQL query. This rules relates to fields of type TEXT in SQL Server. TEXT fields cannot be sorted unless cast to a VARCHAR data type. For example the JobTitle column should be written as:

 SELECT….. Convert(Varchar(64), JobTitle) as JobTitle …. FROM….

This will ensure that sorting the JobTitle column will be efficiently processed.


It is normal to add your own style to columns (width, type etc..) and this should be done using DataWidgets.AddColumnToGrid before calling DataWidgets.PopulateGridFromSQLQuery.

 

38. Refreshing Grids on Other Entity Forms


When a form wishes to fire a button on another entity form which can be virtual or not, perhaps to refresh a grid or view, then this example shows how to click the button programmatically from another form script:


Private Sub RefreshBidManagerGrid

Set frmBidManager = _

FormFunctions.GetAFormInstance( _

"frmFormDesignedByUser", True, "", "BidManager")


If frmBidManager Is Nothing Then exit sub ‘ Form not open

Set btn = frmBidManager.GetShowForm.GetButtonFromFunction( _

"btn", "RefreshBidStagesGrid")


If btn Is Nothing Then Exit Sub ‘ Button not found

btnIndex = btn.FormWidget.Index

frmBidManager.btn(btnIndex).Value = True

End Sub


If the form is a virtual entity, replace "frmFormDesignedByUser" with "frmVirtualFormDesignedByUser" in the GetAFormInstance function.



28 March 2002 Update:


It is now possible to use the frm{EntityName} in order to access forms which is more intuitive. The example below shows how this works:


If FormFunctions.isformloaded("frmAdvertisement") Then 

  Set f = FormFunctions.GetAFormInstance("frmAdvertisement")

  f.LoadEntityDetails 0

  FormFunctions.AddFormToTabView (f), ""

Else

  Set f = FormFunctions.LoadFormByName("frmAdvertisement", 0, "New Advert")

End If


' Even works for Virtual Entity Forms

Private Sub OpenToDoList

If FormFunctions.isformloaded("frmToDoList") Then 

  Set f = FormFunctions.GetAFormInstance("frmToDoList")

  FormFunctions.AddFormToTabView (f), ""

Else

  Set f = FormFunctions.LoadFormByName("frmToDoList", 0, "My ToDo List")

End If


End Sub




 

39. Running Mail Merges From Script


The mail merging technology now includes a script friendly interface to generate mail merges without requiring any pre-processing of the data sources into the expected format.


A new object is exposed: MailMerge, which can be accessed from the script editor.

This object exposes a number of properties/methods:


Name Type Description

NewDocument Method Called to instantiate a new merge document

KeySQL Property The SQL which generates the ID’s of the keys

DataSourceSQL Property The SQL which generates the full data file

Template Property The word template .DOT file

AppendDocument Method Append a document to collection

SaveAs Property Save the merged document as this file name

UserId Property Set the user who is running the merge

RunMailMerge Method Run the merge and generate the document


Note that KeySQL and DataSourceSQL are mutually exclusive i.e. only 1 should be set at any 1 time.

The DataSourceSQL gives the programmer much more control over the data source production.


The use of this technique is now described by use of 2 examples:


Private Sub MailMergeTest

  ‘ Option 1 – Tight control using .DataSourceSQL

  With MailMerge

    .NewDocument

    .DataSourceSQL = _

"Select ContactId, Christian + ' ' + Surname as ContactName" & _

    " From Contact Where ContactId = " & _

    FormObjectsLocal.FormRef.ContactIdCollection(1)


    .Template = "C:\Program Files\ClientCode\DataSourceTest.dot"

    .AppendDocument "C:\Program Files\ClientCode\DataSourceTest.dot"

    .SaveAs "C:\temp\DataSourceTest.rtf"

    .RunMailMerge

  End With

 

  ‘ Option 2 – Use existing Universal Data Source.tmm schema

  With MailMerge

    .NewDocument

    .KeySQL = "Select CompanyId, 26531 as ContactId, 99 as RequirementId, " & _

" 25 as ContractId, " & _

    " 33518 as CandidateId, 4 as AdvertisementId, " & _

    " 3 as ContractorInvoiceId, 1 as TestEntityId" & _

    " From Company Where CompanyId = " & m_CompanyId


    .Template = "\\ClientCode2000\ActionTemplates\client mailshot.dot"

    .AppendDocument "D:\CVData\P\Synonyms Test.doc"

    .SaveAs "\\ClientCode2000\ActionTemplates\Mailshots\MMTest.rtf"

    .RunMailMerge

  End With

End Sub


The .DataSourceSQL property accepts any SQL string returning any recordset from any tables in the database. The programmer determines all fields and records which are written to the data source. The programmer must therefore manage the Word templates and associated design-time TMM data source file. This is the property to use if very tight control of the merge process is required. Note this property is available only in build 1.00.117 or later.


The .KeySQL property accepts a SQL string returning only the keys of the underlying entities. In this example, we want to merge the document consisting of the current company (m_CompanyId), requirement 99, placement 25, candidate 33518, advertisement 4, contractor invoice 3 and test entity 1.

This allows you to control the relationships between the records.


The .Template property is the full path to the word .dot file.


.AppendDocument can be called any number of times to append any number of documents (CVs perhaps) to the end of the merge.


The .SaveAs method tells the merge that the resulting merged file will be saved to the specified file name.


The .RunMailMerge method starts the process of creating the data source (.tmm file), instantiating word, opening the .dot template and merging to a new document using the data source. If the .SaveAs file is set, it then saves the document. The resulting document is then made visible for viewing.

 

40. Diagnostics and User Activity Logging for Debugging and Audit Trails


For debugging, consider using the Diagnostics object for debugging as this logs to the diags file in real-time without intrusion and can capture hundreds of messages. Turn diagnostics on either via the Configure->Diagnostics menu option, or using the /Diag command line option.

To use in your code, just use the following syntax:

 

Diagnostics.Diag _

"Your message goes here... Concatenate variables, properties etc..."

 

The main advantage to this approach is that you can leave these in production systems and messages are only logged when diagnostics are enabled when attempting to debug a problem encountered on-site.

The .out file produced also contains version + anciliary information which can help diagnose problems.

 

Note, that when diagnostics are enabled, all MessageBox.AppMsg and AppStopMsg and QuestionYesNo etc.. are also logged to the .out file.

 

For logging user activity to SQL Server as an audit trail, use the following:

 

Initialisation.Users.DbInsertUserActivity (Initialisation.UserId), _

"The message to be logged", startDate, endDate

 

The startDate and endDate can be set to Now, but these can be used to capture the time taken for a particular operation.

All records are written to the UserActivity table only if the Configure->User Activity Logging menu option is set by administrator.


 

41. Closing Action Forms


It may be necessary to close an action form from script for example when a user is not permitted to run an action. In this case the following method can be called from the Form_AfterLoad event in order to stop the form from loading and displaying:

 

FormObjectsLocal.FormRef.FormUnload


Note: From 15 Jan 2002 this method is available to all config forms.


 

42. Post Code Lookup


Added post code database lookup capability to the system and exposed this as script. If the customer has licensed the post code address file (PAF) from QAS Systems, then the post codes will exist in the database.


The object PostCodeLookup is exposed through script and can be used as follows:


Public Sub Button_Click ()

Set btn = _

FormObjectsLocal.ShowForm.GetButtonFromIndex("btn", _

FormObjectsLocal.EventControl.Index)

If btn Is Nothing Then Exit Sub

   On Error Resume Next

  Select Case btn.ButtonFunction

      Case "AlternativeAddressPostCodeLookup"

Call AlternativeAddressPostCodeLookup

    End Select

End Sub


' Button lookup from alternative address fields.

Private Sub AlternativeAddressPostCodeLookup

sPostCode = FormObjectsLocal.ShowForm.GetFieldValue( _

"ContactConfigFields", "Alternative1PostCode")

If len(sPostCode) = 0 Then

MessageBox.VBMsgBox gVBmsgBoxSpaceOut ( _

"Please enter a post code before searching for a matching address.")

Exit Sub

End If

' Do the lookup now

With PostCodeLookup

.PostCode = sPostCode

If .Search Then

FormObjectsLocal.ShowForm.SetFieldValue("ContactConfigFields", _

"Alternative1StreetAddress") = .Street

FormObjectsLocal.ShowForm.SetFieldValue("ContactConfigFields", _

"Alternative1StreetSuburb") = .Suburb

FormObjectsLocal.ShowForm.SetFieldValue("ContactConfigFields", _

"Alternative1City") = .City

FormObjectsLocal.ShowForm.SetFieldValue("ContactConfigFields", _

"Alternative1County") = .County

FormObjectsLocal.ShowForm.SetFieldValue("ContactConfigFields", _

"Alternative1PostCode") = .PostCode

End If

End With

End Sub


Other properties of the PostCodeLookup object are as follows:


Enabled Read True of post code lookup enabled for site

PostCodeRadialSearching Read True if radial searching enabled for site

TopRecordCount Write Number of records to display in wildcard post code search

PopUpSelection Write Set to True if a wildcard pop-up selection dialogue is to be displayed.


 

43. Using Bookmarks in Grids


As a rule, it is not advisable to enumerate through the grid to access the rows and cells directly as this is inefficient. See the following section to see how to access the underlying unbound recordset.


 In order to retrieve all rows in a non-additem grid, use the following code:



With dwGrid

.Redraw = False


.Movelast

.Movefirst


For i = 0 to .Rows - 1

.Bookmark = i

ContactId = .Columns(0).Text

If i = 0 Then 

s = s & (ContactId)

Else

s = s & ", " & (ContactId)

End If

Next

.Redraw = True

End With


 

44. Accessing Grid SQL Query Recordset and Cloning


When a grid is populated from a single SQL statement using the function: DataWidgets.PopulateGridFromSQLQuery, the grid handling is performed automatically by the application.

There are occasions however, when you may require access to the underlying recordset in order to perform some extra processing (calculating totals etc..).

The following example shows how to populate a grid, retrieve the automatically generated underlying recordset, clone it and enumerate through the cloned recordset to access the values:


With DataWidgets

  .InitialiseUnboundGrid (dwGrid), "First 1000 Rows of Table: " & sTableName


sSQL = "Select Top 1000 * From " & sTableName

   

' One stop shop - even easier than drinking beer!

.PopulateGridFromSQLQuery (dwGrid), (sSQL)

End With


' Test access to the recordset using cloning technique

s = ""

Set rs = DataWidgets.GetUnboundScriptedGridRecordsetFromHWnd(dwGrid.hWnd)

If Not rs Is Nothing Then

Set cloneRS = rs.Clone

With cloneRS

.MoveFirst

Do While Not .EOF

s = s & " " & .Fields(1).Value & .Fields(2).Value

.MoveNext

Loop

.Close

End With

End If

MessageBox.appmsg "Recordset contents=" & s



 

45. Unbound Grids - Formatting Columns


As regards formatting, there are 2 ways to achieve this: Using SQL (fastest and most efficient) and using Row Level Callbacks (slower, but more control)

 

SQL Method

 

Code the SQL Select with the convert function to get a UK display date format:

Select convert(varchar(11), StartDate, 113) as StartDate

The numbers might not be exactly what you want but this is what you do to get your formatting:

Select Convert(varchar(11), convert(decimal(10,5), numField)) as numField

This will give you a number with 5 decimal places. (Notice the double convert to get the value into text format because ADO will strip trailing zeros).

 

Row Level Callbacks

Use the Grid_RowLoaded event to capture the loaded row from the SQL statement and format the specified column explicitly. Example:

Public Sub Grid_RowLoaded

  Set dwGrid = FormObjectsLocal.EventControl

  If dwGrid Is Nothing Then Exit Sub

  Set fd = _

FormObjectsLocal.ShowForm.GetFieldDescriptionFromWidgetHwnd(dwGrid.hWnd)

  If fd Is Nothing Then Exit Sub

 

  Select Case fd.TableFieldName

    Case "DateViewGrid"

      ' Format the third column (0 base columns) which is a date into 

' the format e.g. 19-Feb-2002

      With dwGrid

        If .Columns.Count > 2 Then       

          .Columns(2).Text = _

StringFunctions.VBFormat(.Columns(2).Text, "DD-MMM-YYYY")

        End If

      End With

  End Select

End Sub

Warning: Failure to check the column counter before setting the column value will result in accessing a non-existent grid cell which will cause the grid to bring down the application!


 

46. Form_Unload – Preventing form from closing from Script


By using the Form_Unload (Cancel ) event, it is possible to prevent the user from closing the form without saving the changes controlled by script. 


The application knows when a form is ‘dirty’ i.e. fields have been changed but not saved and it correctly prompts the user in these instances. The application however, knows nothing about the script behaviour and which fields it may control, so offers this event to allow script to prevent the form from closing in such circumstances.


Here is an example:


' Ask user to quit

Public Sub Form_Unload ( Cancel )

Cancel = _

(MessageBox.QuestionYesNo("Are you sure you want to quit?", _

"Form_Unload") <> vbYes)

FunctionStatus.SetParameter("Cancel") = Cancel

End Sub


In the above example, the event is called from the application when the user wants to close the form. The event simply asks the user for verification and returns the status to the application.

If the user answers Yes, the form will close, otherwise it will not.



 

47. Public Sub Form_ExternalRefresh – Catching events in script


When any data changes within the system, any script can catch all events associated with the change using the following event implemented in build 1.00.052. 


Public Sub Form_ExternalRefresh ( FormName, Param1, Param2, Param3 )


The first parameter is the name of the form which is raising the event, and the following parameters will vary depending upon what type of update has occurred. In general Param1 will denote what has been updated, and Param2 will be the ID of the record.


The following example shows how to invoke the scheduled activity/note history form from script and capture the update event from it in script (in order to perhaps refresh a bespoke appointments grid).


' Load the last task created

MaxTaskId = Database.GetSnapshotField("Select max(TaskId) From Task", 0)

Set frmMainBinder = FormFunctions.GetAFormInstance("frmMainBinder", True)

Call frmMainBinder.EditTask((MaxTaskId))



Public Sub Form_ExternalRefresh ( FormName, Param1, Param2, Param3 )

If FormName = “frmMainBinder” and Param1 = “TaskId” then

‘ Refresh the bespoke appointments grid…

End if

End Sub


 

48. Spin Increments – Setting Visibility from Script


It is sometimes necessary to set the visibility of some fields by script. 


The numerical data entry fields spin increment widgets:  are separate from the actual field.


They have the same index as the field so it is possible to write a generic function as follows:


Public Function gSetSpinIncrementVisibility(sEntityName, sTableFieldName, bVisible)

With FormObjectsLocal.ShowForm

Set objField = .GetFieldWidget((sEntityName), (sTableFieldName))

If objField Is Nothing Then Exit Function

Set formField = .GetFormFieldFromFieldName((sEntityName), (sTableFieldName))

index = objField.Index

Set spin = FormObjectsLocal.formref.controls("spinIncrementValue")(index)


If .IsWidgetVisibleOnCurrentTab((objField.hwnd)) Then 

spin.Visible = bVisible

End If

End With

End Function


This can be used as follows:


Public Sub Tab_Click ()

Call DoInvisibleFields

End Sub


Public Sub Form_AfterLoadRecord ( Id )

Call DoInvisibleFields

End Sub.


Public Sub DoInvisibleFields

Call SetFieldVisible("ContactConfigFields","Formula1",

bDoFormula)

Call SetFieldVisible("ContactConfigFields","Formula2",

bDoFormula)


'set spin increments invisible

Call gSetSpinIncrementVisibility("ContactConfigFields", " Formula1", 

Not bDoFormula)

Call gSetSpinIncrementVisibility("ContactConfigFields", " Formula2", 

Not bDoFormula)


End Sub


 

49. Report Formulas and Parameters


In build 1.00.062, it is now possible to pass formulas from script into crystal reports.

This has the major advantage that the report is more programmable from script and temporary tables do not need to be used to pass in non-derived data.


The example below shows how this can be achieved:


' Modify SQL here to remove context sensitivity

Public Sub Form_AfterSaveRecord ( Id )

ReportProperties.AddScriptFormula("StartDate") = "01-Jan-2003"

ReportProperties.AddScriptFormula("EndDate") = "31-Jan-2003"

End Sub



In Build 1.00.079 – it is now possible to control printing and formulas from a scriptable form without needing to have a report form. The following code shows how to do this:



sReportName = "ProjectIssuesSummary.rpt"


Set report = Initialisation.Reports.GetReportProperties((sReportName))


MySQL = "(Status = 'In Progress')"


report.AddScriptFormula("ReportParameters") = "Outstanding Issues ('In Progress')"


Call Initialisation.Reports.PrintReportFromReportPropertiesObject((report), (MySQL))

 


In build 1.00.111 – it is now possible to add report parameters (for stored procedures) using the same technique:



sReportName = "p_TestSPInCrystalReport.rpt"

Set objReport = Initialisation.Reports.GetReportProperties((sReportName))

objReport.AddScriptParameter("vchrSurname") = "A%"

objReport.AddScriptParameter("vchrUpdatedSince") = "01-Jan-2001"

Call Initialisation.Reports.PrintReportFromReportPropertiesObject((objReport), "")



In build 1.00.012 – the ability to control the report from script has been significantly enhanced to allow the developer to control the report much more tightly. 2 new methods and a new property have been exposed:


Method #1: OpenExportedReport – Specifies that the exported report is not to be launched (Word, Excel etc..)

Method #2: TabViewName – Specifies the name of the tab view (allows multiple views of same report)

Property #1: ExportedFileName – The full path of the file produced on export


The following code demonstrates how to use these:


Private Sub PrintReportForCompany(CompanyId, sCompanyName, sReportName, iReportDestination)

Set objReport = Initialisation.Reports.GetReportProperties((sReportName))

With objReport

.AddScriptParameter("vchrSurname") = Left(sCompanyName, 1) & "%"

.AddScriptParameter("vchrUpdatedSince") = "01-Jan-1900"

' The report destination – see this section

.Destination = iReportDestination

' We don't want the print engine to open the document after export

.OpenExportedReport = False

' We want unlimited report windows (tabs) when printing to screen

.TabViewName = "Rpt:" & GetTabNameFromCompany(sCompanyName)

End With

' Now launch the report

Call Initialisation.Reports.PrintReportFromReportPropertiesObject(

(objReport), "")


' Read the exported filename back now in order to rename/copy etc..

If len(objReport.ExportedFileName) > 0 Then

Call ProcessExportedReport(CompanyId, sCompanyName, 

objReport.ExportedFileName)

End If

End Sub


Private Sub RunSalesInvoiceReportAndEMailAsAttachmentWithTimesheetImage 

(sReportName, sGUID, lInvoiceId)

Const CrystalReportDestination_WP = 2

Const CrystalReportDestination_PDF = 5


Set objReport = Initialisation.Reports.GetReportProperties((sReportName))

With objReport

.Destination = CrystalReportDestination_WP

.AddScriptParameter("vchrUser") = CStr(Initialisation.UserId)

.AddScriptParameter("vchrReportName") = sReportName

.AddScriptParameter("vchrInvoiceString") = ""

.AddScriptParameter("vchrGuid") = CStr(sGUID)

.OpenExportedReport = False

.TabViewName = "Printing Invoice..."

End With

Call Initialisation.Reports.PrintReportFromReportPropertiesObject((objReport), "")

If Len(objReport.ExportedFileName) > 0 Then

sFileName = objReport.ExportedFileName

'MessageBox.AppMsg "Printed report: " & sFileName & "?"


' Copy this file to a unique location so that it can be referenced in 

‘ a note/history against the invoice etc.. for tracking purposes

sDestinationFile = 

FileSystem.DirWithSeparator(Initialisation.GetSystemSetting( 

"ConsolidationInvoiceFolder")) & _

"Invoice" & lInvoiceId & "_" & Initialisation.User.LoginName & "_" & _

StringFunctions.VBFormat(Now, "DD-MMM-YYYY") & "_" & StringFunctions.VBFormat(Now, "hhnnss") & _

"." & FileSystem.SuffixFromFileName((sFileName))


Call gCopyFile(sFileName, sDestinationFile)


' Attach this e-mail as an attachment with timesheet image etc..

Call EMailInvoiceAttachmentsToPlacementRecipients(

sDestinationFile, lInvoiceId)

End If

End Sub

 

50. Form Timers


In build 1.00.066, it is now possible to raise a form timer event in scripted forms. The timer fires every minute starting 1 minute after the form is loaded. The benefit of this is so that forms can be used to refresh display statistics etc.. without requiring user intervention.


The following example code re-calculates the current gross domestic product of a nation every minute:


‘ This function fires every minute

Public Sub Form_Timer ()

Call CalculateGrossDomesticProductStatisticsEveryMinute

End Sub



 

51. Forcing ExternalRefresh events from Script


In build 1.00.069, it is now possible to raise a global external form refresh event in scripted forms. This event is normally called when any record is saved from a non-virtual form.


The script developer can now raise this event to all loaded forms to for example, refresh grids or special views of data.


‘ Code to Cause refresh event in interested parties e.g. Project - ExternalRefresh

FormFunctions.InvokeScriptedFormRefreshMethodForAllForms _

"frmFormDesignedByUser", "Entity Update", "ProjectIssueId", m_ProjectIssueId 



See section 48 above for information about how to capture the event from script on any form and process the request.



In order to let all interested forms know that a task has been updated, use the following:

' Need to tell Task Grids what we have done

Call FormFunctions.FireTaskUpdateEventMessenger (TaskId)

 

52. Capturing Main Binder Toolbar Buttons


In build 1.00.072, it is now possible to capture the toolbar button clicks directly in script to take appropriate actions e.g. Print, Save, Search etc...


The Id parameter contains the ID of the toolbar button (ID_Print, ID_Save etc..) and the Cancel parameter can be set to True to stop ClientCode processing the button.


The following example shows how a new event is now available which is fired for every toolbar click when the form has focus:


' Catch users button press to do the printing

Public Sub Form_ToolbarButton ( Id, Cancel )


Select Case Id

Case "ID_Print": Call PrintCurrentGrid

' Ignore all others

End Select

' We will process the button thank you!!!

FunctionStatus.SetParameter("Cancel") = True

End Sub



 

53. Printing Contact Reports from Virtual Forms 


In build 1.00.072, it is now possible to utilize the Crystal report functionality for printing contact reports directly from script on any form. This technique is probably best utilized on a virtual form where scripted grids are under control and the user is accessing the main print button on the toolbar.


The following example follows on from the previous section and shows how to print the contents of the current unbound scripted grid:


Private Sub PrintCurrentGrid

Set dwGrid = GetCurrentGridInView

If dwGrid Is Nothing Then

MessageBox.AppMsg "Please select a grid to print."

Exit Sub

End If

' Print the contact oriented report now

CrystalReports.PrintContacts FormObjectsLocal.FormRef, dwGrid

End Sub


Private Function GetFavouritesListGrid

Set GetFavouritesListGrid = FormObjectsLocal.ShowForm.GetFieldWidget(EntityName, "FavouritesListGrid")

End Function


Private Function GetSearchResultsListGrid

Set GetSearchResultsListGrid = FormObjectsLocal.ShowForm.GetFieldWidget(EntityName, "SearchResultsListGrid")

End Function


Private Function GetCurrentGridInView

Set dwGrid = GetSearchResultsListGrid

  If Not FormObjectsLocal.ShowForm.IsWidgetVisibleOnCurrentTab((dwGrid.hwnd)) Then 

Set dwGrid = GetFavouritesListGrid

  If Not FormObjectsLocal.ShowForm.IsWidgetVisibleOnCurrentTab((dwGrid.hwnd)) Then Set dwGrid = Nothing

  End If

   

  Set GetCurrentGridInView = dwGrid

End Function


See section 50 for further details about invoking reports.


 

54. Access System Settings


An administrator can set system wide settings using the ConfigureSystem Settings menu:


 


To access these from script, use the Initialisation.GetSystemSetting(sName) function.


This is available from version 1.00.091.


Example:


sSystemSetting = Initialisation.GetSystemSetting ("YourSettingName")



In version 1.00.131, the programmer can now generate and update their own settings using the Initialisation.SetSystemSetting(sName, sValue) property:



Initialisation.SetSystemSetting("GoToAssistDefaultId") = lDefaultId


This function writes the new value directly to the database SystemSetting table.

 

55. Designer Queries in Script


From build 1.00.096, it is possible to use the designer to add queries to config and virtual forms.

This allows the queries to be accessible from script, therefore allowing a systems administrator to control the grid layout/fields etc.. without requiring script access – thus speeding up the customization process.


' Get the query for this view

Set gridQuery = _

FormObjectsLocal.FormRef.GridQuery.GetQueryFromCollection((sQueryName))

' if gridquery is nothing warn the user and get out

If gridQuery Is Nothing Then 

MessageBox.Appmsg "gridQuery is nothing?"

Exit Sub

End If

' the SQL Query

sSQL = gridQuery.QueryProperties.SQLQuery

' define the sql to append to grid sql

sAppendSQL = "..... " & m_PlacementId

' prepare the SQL before populating the grid

sSQL = Replace(sSQL, "--PlaceHolderForAdditionalSQL", sAppendSQL)


Data Widgets Grid

' dont redraw now

dwGrid.Redraw = False

' populate the grid

With DataWidgets 

.InitialiseUnboundGrid (dwGrid), "Rates"

  Call FormObjectsLocal.FormRef.GridQuery.InitialiseGrid((dwGrid), (sQueryName)) 

' set the grid properties

  With dwGrid

  .AllowAddNew = False

  .AllowUpdate = False

  .MaxSelectedRows = 8192

  .RecordSelectors = True

  .SelectTypeRow = 1 '1 - single, 2 - multi, 3 - more control

  End With

'MessageBox.AppMsg "SQL:" & sSQL

' populate the grid now

.PopulateGridFromSQLQuery (dwGrid), (sSQL)

End With

' redraw grid

dwGrid.Redraw = True


Alpha Grid

Set alphaGrid = _

FormObjectsLocal.ShowForm.GetFieldWidget(EntityName, "PlacementGrid")

If alphaGrid Is Nothing Then Exit Sub

With grdA

.ColumnPrefixToRemove = "Placement"

.EnableAutoEntityLoading "Placement", "Placement", "Placement_PlacementId"

' Apply the query to the grid.

.SetQuery Query.QueryProperties

End With

 

56. Setting the Report Destination at run-time from Script


From build 1.00.111, it is possible to allow the user to select the report destination at run-time:



 


 


 


 

This is the report form script:


' Report destinations

Const CrystalReportDestination_Screen = 0

Const CrystalReportDestination_Printer = 1

Const CrystalReportDestination_WP = 2

Const CrystalReportDestination_SpreadSheet = 3

Const CrystalReportDestination_HTML = 4

Const CrystalReportDestination_PDF = 5



Const EntityName = "BossDumpConfigFields"


Public Sub Form_AfterLoad ()

Call PopulateReportDestinationCombo

End Sub


Private Sub PopulateReportDestinationCombo

Set cmb = FormObjectsLocal.ShowForm.GetFieldWidget(EntityName, "ReportDestination")

With cmb

.AddItem "Screen"

 .ItemData(.ListCount-1) = CrystalReportDestination_Screen

.AddItem "Printer"

  .ItemData(.ListCount-1) = CrystalReportDestination_Printer

.AddItem "Word"

.ItemData(.ListCount-1) = CrystalReportDestination_WP

.AddItem "Excel"

  .ItemData(.ListCount-1) = CrystalReportDestination_SpreadSheet

.AddItem "Web Page"

 .ItemData(.ListCount-1) = CrystalReportDestination_HTML

.AddItem "PDF"

 .ItemData(.ListCount-1) = CrystalReportDestination_PDF

.ListIndex = 0

End With

End Sub


' Called before report is fired

Public Sub Form_BeforeSaveRecord ( Id, Cancel )

' Point at user specified destination

Set cmb = FormObjectsLocal.ShowForm.GetFieldWidget(EntityName, "ReportDestination")

ReportProperties.Destination = cmb.ItemData(cmb.ListIndex)

End Sub



 

57. Intercepting Report Selection Events from Script


From build 1.00.114, it is possible to capture the list of reports selected at run-time from the Print toolbar button. This allows the developer to orchestrate the printing of reports entirely from script.


The following example shows code which is available in the frmCompanies form to capture a new event called Form_BeforePrint which is called after the report selection dialogue is displayed, but before each report is printed by the Crystal sub-system.


‘ frmCompanies Form Script

Public Sub Form_BeforePrint(reportCollection, Cancel)

If reportCollection Is Nothing Then Exit Sub

If reportCollection.Count = 0 Then Exit Sub


' Tell ClientCode that we will process the report selection from here

FunctionStatus.SetParameter("Cancel") = True


s = "Report Collection: "

For i = 1 to reportCollection.Count

s = s & vbcrlf & reportCollection(i)

Next


' Test access to the recordset using cloning technique

s = s & vbcrlf & vbcrlf & "Recordset contents:" & vbcrlf

Set rs = FormObjectsLocal.FormRef.GetRecordsetForSelectedTab

If Not rs Is Nothing Then

Set cloneRS = rs.Clone

With cloneRS

.MoveFirst

Do While Not .EOF

s = s & " " & .Fields(1).Value & .Fields(2).Value

.MoveNext

Loop

.Close

End With

End If

MessageBox.AppMsg (s)

End Sub



Thus the sequence of events is as follows:


     ïƒ    



This is the print button on the main toolbar when the companies form is displayed. 

Then the company reports selection dialogue pops up.

Then the event is raised in script code, which processes the list and cancels the default behaviour.

 

58. Grid Column Filtering/Sorting for AddItem Grids


From build 1.00.116, it is now possible to sort and filter AddItem grids in the same manner as Unbound grids providing some rules are followed.


Have now enhanced ClientCode to support grid sorting and filtering for AddItem grids used with the DataWidgets.PopulateGridFromSQLQuery method.

 

The latest release is at: www.clientcode.com

 

Grid sorting works by default, however in order to ensure that filtering works, you need to provide explicit Select column names and some comment fields and an Order By clause in the SQL as shown in the slightly modified code below.

 

Let me know if this works on your timesheetHR system?

 

Garry

 

Form: Consolidation

Function: ShowSelectedTimeSheets

...

'DWA 9 Jan 2004

'  10 Jan 2004: From GL to DWA: Need to Select SPECIFIC column names in order to allow FILTERING

 

  sSQL = "SELECT TimeSheetID, PlacementID, SupplierCompany, JobTitle " & _

   ", Company, Candidate, ContratorType, StartDate, EndDate, ProcessDate" & _

   ", NetPay, PayVAT, GrossPay, NettCharge, VATCharge, GrossCharge, Freq, Pending" & _

   ", SalesInvoiceProduced, PurchaseInvoiceProduced" & _

   " FROM v_Consolidation_SelectedTimeSheetList_v3 " & _

   " WHERE Company = '" & strClient & "'" & _

   vbcrlf & "-- Grid Filter Start --" & vbcrlf & "-- Grid Filter End --" & vbcrlf & _

   " Order By TimeSheetID"

...


You may ask the question: ‘Why not just use an Unbound grid instead of an AddItem if using a full SQL statement?’

The answer is because Unbound grids are NOT editable (I don’t believe) as they are populated from a recordset, thus it is not possible to add CHECK BOX columns and have the user select rows. This IS however possible with an AddItem grid.

Here is an example which uses the AddItem technique but adds a check box column:


sSQL = “as above code…”


' Initialise grid columns

  With dwGrid

    .Columns.RemoveAll

    .DataMode = 2     ' Add Item Grid

    .AllowAddNew = False

    .AllowUpdate = True

    .MaxSelectedRows = 8192

    .RecordSelectors = True

    .SelectTypeRow = 3 '1 - single, 2 - multi, 3 - more control

  End With


With DataWidgets

    .AddColumnToGrid (dwGrid), 22, "Select", 600, True ‘ The check box one

end with


' Select column is an updateable check box

  With dwGrid

    For x = 17 To 22

      With .Columns(x)

        .DataType = 11

        .Style = 2

      End With

    Next

     

  End With 


' Lock these cells as non-updateable

  With dwGrid

    For i = 0 To 19

      .Columns(i).Locked = True

    Next

    .Columns(21).Locked = True

    .Columns(10).ForeColor = vbBlue

    .Columns(11).ForeColor = vbBlue

    .Columns(13).ForeColor = vbBlue

    .Columns(14).ForeColor = vbRed

    .Columns(15).ForeColor = vbRed

    .Columns(16).ForeColor = vbRed

    .Columns(20).Locked = True

    .Columns(22).BackColor = vbGreen '&hffff00 '&H00FFFFC0&

     

    .Caption = "Pending && Selected TimeSheets - " & strClient

  End With


  DataWidgets.PopulateGridFromSQLQuery (dwGrid), (sSQL)

  dwGrid.Redraw = True


Then in button_Click event handling:


With dwGrid

 For row = 0 to .Rows-1

 .Bookmark = row

  If (cbool(.Columns("Sales").Value) = False) _

and (cbool(.Columns("Select").Value) = True) Then

 ' Build comma-delimited list of selected non-SelfBill TimesheetIds

  End If

 Next

End With 


 

59. Using CommonDialogue Friendly Function to Find Files


The FileSystem.CommonDialogue method is not script friendly (passes parameters byref) so a wrapper function: FileSystem.FileDialogue has been developed to provide the same functionality for pointing at files on the file system.


Public Sub Button_Click ()

' Not interested in non-config buttons

If FormObjectsLocal.EventControl.Name <> "btn" Then Exit Sub


Set btn = FormObjectsLocal.ShowForm.GetButtonFromIndex("btn", _

FormObjectsLocal.EventControl.Index)

If btn Is Nothing Then Exit Sub


Select Case btn.ButtonFunction

Case "ImportHTML": Call ImportHTMLFile

End Select

End Sub


Private Sub ImportHTMLFile

sFile = FileSystem.FileDialogue("Find HTML Page", (sFile), "*.*", _

"All Files (*.*)|*.*", 1)

MessageBox.appmsg "File=" & sFile

End Sub



Use 1 for File Find and 2 for new file.



 

60. Form_BeforeDeleteRecord Event


Build 1.00.122 introduced a programmable event to allow developers to control the end-user deletion of key records.


Public Sub Form_BeforeDeleteRecord(Id, Cancel)



The Id is the current record to be deleted.

Cancel is a flag which the programmer can set to False to prevent the application deleting the record.


 


' Catch deletion event and prevent if e.g. timesheets have allocated hours etc..

Public Sub Form_BeforeDeleteRecord (Id, Cancel)

MessageBox.AppMsg "In script: " & FormObjectsLocal.FormRef.EntityName & " - Id:" & Id

FunctionStatus.SetParameter("Cancel") = True

End Sub


Currently this event is only available in frmEntitySearch, frmCompanies and frmContacts.


 

61. Form_CoreEntityLookupEvent


Build 1.00.124 introduced a programmable event to allow developers to fully control the end-user selection of core entity lookup records. These are the buttons on the fields of type Contact, Company, User or Currency. This event ALSO applies to the Re-assign Company pop-up menu on the contact form.


Public Sub Form_CoreEntityLookup (TableName, TableFieldName, RecordId, RecordText, Cancel)


The TableName and TableFieldName correspond to the FieldDescription record of the field in question. The RecordId is the current Id of the core entity (and the .Tag value of the associated text box) and is used to return the new value to the caller. The RecordText is the text currently displayed in the text box and is used to return the new value to the caller. The Cancel flag if set to True, forces the application to abandon its own processing of the button.


The event is fired when a user selects the spyglass button within the text box containing the field:

 


and also when the user selects the re-assign Company pop-up menu from the contact forms, Re-assign Company Name & Address button:

 



The event fires BEFORE the user is prompted to select a lookup so that script can COMPLETELY override the behaviour of the pop-up record selection.


This provides the ability for the script programmer to decide what SQL to generate, what columns to show and also what business rules to enforce in limiting the end-user to fit the current context.


The following example code shows how this event is utilized in a form for which a candidate (not a client, supplier, doctor, lawyer etc..) must be selected in the above field:


Public Sub Form_CoreEntityLookup (TableName, TableFieldName, RecordId, _

RecordText, Cancel)


Select Case TableFieldName

Case "CandidateReference"

Call CandidateLookup(TableFieldName, RecordId, RecordText) 

Case Else

Exit Sub ' Let application handle

End Select

' Set the function status now

With FunctionStatus

.SetParameter("RecordId") = RecordId

.SetParameter("RecordText") = RecordText

.SetParameter("Cancel") = True

End With

End Sub


' Do a contact lookup using script functions

Private Sub CandidateLookup(TableFieldName, RecordId, RecordText)

Set SQLSelection = Selection.GetSelectSQLObject

With SQLSelection

.Columns = 9

.ColWidth(0) = 0

.ColWidth(1) = 0

.ColWidth(2) = 0

.ColWidth(3) = 1500 ' Forenames

.ColWidth(4) = 1800 ' Surname

.ColWidth(5) = 3000 ' Address

.ColWidth(6) = 1200 ' Home Tel No

.ColWidth(7) = 1200 ' Work Tel

.ColWidth(8) = 1200 ' Mobile Tel

.Caption = "Select any Candidate"

.SQL = "Select ContactId, Priority, ContactType As Type, " & _

"Christian As 'Forename(s)', Surname," & _

    " TheAddress as 'Address', HomeTelNo as 'Home Tel No.', WorkTelNo as 'Work Tel No.', MobileTelNo as 'Mobile Tel No.'" & _

    " FROM v_AllContacts" & _

    " Where ContactType = 3 " & _

    " Order by Surname, Christian"

     

.ContactSelection = True

.PriorityColumn = 1

.TypeColumn = 2

If Not .Selection Then Exit Sub

RecordId = .SelectedRecord(1)

End With


Set Contact = Initialisation.Contacts.FindFirst((RecordId))

RecordText = Contact.Christian & " " & Contact.Surname

End Sub


The following example is how to utilise the code in frmPerson – The contact form for managing the company re-assignment:


' Called whenever a core lookup button invoked - this includes the assign company menu

Public Sub Form_CoreEntityLookup(TableName, TableFieldName, RecordId, RecordText, Cancel)

' Display the message box for debug

'MessageBox.AppMsg "Form_CoreEntityLookup: " & TableName & ", " & TableFieldName & _

' ", " & RecordId & ", " & RecordText & ", " & Cancel

' Now get the list of contacts for this type of lookup


Select Case TableFieldName

Case "CompanyName"

Call CompanyLookup("Client", RecordId) 

Case Else

Exit Sub ' Let application handle

End Select

' Set the function status now

With FunctionStatus

.SetParameter("RecordId") = RecordId

.SetParameter("RecordText") = RecordText

.SetParameter("Cancel") = True

End With

End Sub


' Do a company lookup using script functions

Private Sub CompanyLookup(CompanyType, CompanyId)

Set SQLSelection = Selection.GetSelectSQLObject

With SQLSelection

.Columns = 2

.ColWidth(0) = 0

.ColWidth(1) = 5000 ' Company

.Caption = "Select a " & CompanyType & " Company"

.SQL = "select Company.CompanyId, Company.Name as 'Company Name'" & _

" From Company, CompanyType, EntityType" & _

" Where Company.CompanyId = CompanyType.CompanyId" & _

" and CompanyType.EntityTypeId = EntityType.EntityTypeId" & _

" and EntityType.EntityName = 'Company'" & _

" and EntityType.Name = '" & CompanyType & "'" & _

" Order by Company.Name"

If Not .Selection Then Exit Sub

CompanyId = .SelectedRecord(1)

End With

End Sub


 

62. frmEntitySearch Buttons


In order to control the use of buttons on the entity search form (frmEntitySearch), the following script can be used to (for example) hide the New button:



 



These are the VB constants used:

Private Const BTN_COMPANY = 0

Private Const BTN_CONTACT = 1

Private Const BTN_SEARCH = 5

Private Const BTN_PRINT = 6

Private Const BTN_CLOSE = 7

Private Const BTN_UPDATE = 8

Private Const BTN_NEW = 10

 

so to disable the New button, I'd use the following script

 

Public Sub Form_AfterLoad ()

 ' May need to identify which entity search is operational

 if FormObjectsLocal.FormRef.EntityName = "SalesInvoice" Then

  FormObjectsLocal.FormRef.btn(10).Visible = False

 End If

End Sub

 



 

63. Turning Off Virtual Form Menu


When a virtual form is created, the form menu is normally of no use.


This menu for example has no use and uses up valuable screen space:


 


Use this code in the form script to turn off the form menu:



Public Sub Form_AfterLoad ()


Set ctl = FormObjectsLocal.FormRef.ctlFormCaptionController

If Not ctl Is Nothing Then ctl.ToolbarVisible = False


End Sub



 

64. Dynamic Button Pop-Up Menus


Build 1.01.011 has new script enabled code to allow developers to dynamically create pop-up menus which are displayed on a button click.


This allows programmers to provide buttons with groups of functions.


The approach is to allow the programmer to show a menu on say a button click, then respond to the Menu_Click event when the user fires the menu (which may not be immediately, so we cannot block).


This is what the user interaction looks like using the code that follows:


 



Private Const DeleteSelectedEMailOnly = "Delete selected e-mail only"

Private Const DeleteAllEMails = "Delete all e-mails"


Public Sub Button_Click ()

‘ Fire the pop-up menu now

Call FormFunctions.ShowPopUpMenu(FormObjectsLocal.FormRef, _

DeleteSelectedEMailOnly, DeleteAllEMails)


End sub


' Get the menu callback from those menus we have fired dynamically

Public Sub Menu_Click (Caption, Index, Cancel)

Select Case Caption

Case DeleteSelectedEMailOnly: Call DeleteEMail(True, False, True)

Case DeleteAllEMails: Call DeleteEMail(False, True, True)

End Select

End Sub


As you can see, the button click fires our code to display a pop-up menu, and when the user selects menu option, the Menu_Click event fires and we can determine which option was selected.



 

65. Hiding Tabs in Entity Search and Virtual Forms


Build 1.01.012 has slightly enhanced code to allow the script developer to turn off the tabs where not required (in the case of a design query overriding the default search).


This is done by coding the frmEntitySearch form script as follows:


Public Sub Form_AfterLoad ()

Select Case FormObjectsLocal.FormRef.EntityName

Case "SalesInvoice"

FormObjectsLocal.FormRef.tabby.TabStyle = 3 ' Turn off all tabs

End Select

End Sub


This will cause the form resize event to ignore the tabs and therefore draw a large grid and appropriately positioned buttons:


 



To remove the tabs in a virtual form, you can employ exactly the same technique.


An alternative, which is NOT recommended is to use the following code to make the one and only tab invisible:


Public Sub Form_AfterLoad ()

FormObjectsLocal.FormRef.tabby.Visible = False

End Sub


You should of course position your grid in the Designer to be higher than originally designed (same top co-ordinate at the tab).


 

66. Preventing Entity Search based on Form Permission


Build 1.01.014 provides a new FormUnload method on the frmEntitySearch form to allow the developer to prevent users from loading entity searches if they are not permissioned for access to any version of an entity form.


This is done by coding the frmEntitySearch form script as follows:


Public Sub Form_AfterLoad ()

If Not gIsUserPermissionedForEntityForm( _

FormObjectsLocal.FormRef.EntityName) Then

MessageBox.AppMsg "You are not permissioned to open this form."

FormObjectsLocal.FormRef.FormUnload ' 1.01.014 or later

Exit Sub

End If

End Sub



The global function used is as follows:


' Return True if the user is permissioned to view a form for the specified entity

Public Function gIsUserPermissionedForEntityForm(sEntityName)

MySQL = "Select Count(*) from formversionusers fvu, formproperties fp where fp.formid = fvu.formid" & _

" And fvu.UserId = " & Initialisation.UserId & " And fp.EntityName = '" & sEntityName & "'"

gIsUserPermissionedForEntityForm = _

(cint(Database.GetSnapshotField((MySQL), 0)) > 0)

End Function

 

67. Setting visibility of btnInternet on Internet Field Types


Similar to the Spin increment shown in section 48, it is necessary to se the visibility of the globe icon  (btnInternet) used on internet fields, this can be done using the function below, again the globe has the same Index as the field so you can use pretty much the same function as the one in section 48


It is sometimes necessary to set the visibility of some fields by script. 


Public Function gSetBtnInternetVisibility(sEntityName, sTableFieldName, bVisible)

With FormObjectsLocal.ShowForm

Set objField = .GetFieldWidget((sEntityName), (sTableFieldName))

If objField Is Nothing Then Exit Function

Set formField = .GetFormFieldFromFieldName((sEntityName), (sTableFieldName))

index = objField.Index

Set globe = FormObjectsLocal.formref.controls("btnInternet")(index)


If .IsWidgetVisibleOnCurrentTab((objField.hwnd)) Then 

globe.Visible = bVisible

End If

End With

End Function


Example:Call gSetBtnInternetVisibility("RequirementConfigFields", "ApplicationURL", True)


 

68. Setting Script Timeout Globally or for Each Form


In build 2.00 onwards, it is possible to set the script timeout for each form in order to control the timing of the script engine.


This is done by the exposure of the ScriptEngines object which is a collection of all script engines within the system. The ScriptEngines object exposes a property and an object of interest to scripts:


ScriptEngines.ScriptTimeoutMilliseconds

ScriptEngines.GetScriptEngine(hWnd as Long)



The following example code shows how to set the global and local script timeouts:


Public Sub Form_AfterLoad ()

' Set script timeout for all subsequently instantiated script engines

ScriptEngines.ScriptTimeoutMilliseconds = 60000

' Set script timeout in milliseconds for this form only

Set objEngine = _

ScriptEngines.GetScriptEngine((FormObjectsLocal.FormRef.hWnd))

If Not objEngine Is Nothing Then

objEngine.ScriptTimeoutMilliseconds = 10000

End If

End Sub


 

69. Using the GridAlpha from script


When scripting populating a grid alpha from script is is very strongly recommended that you base it upon a query. This has the following advantages:


1) Simplified Code.

2) User customization to the grid is saved and restored automatically.

3) Simplified SQL.

4) Changes to the grid and its functionality can be carried out by a non scripter.


To set up a grid in this manner carry out the following steps:


1) Create a query, associated with the form in question (Note: the query should follow all of the best practices defined in the Grid Queries Design Document.doc).

2) Set the options on the query to enable/disable the relevant functions on the grid (EG: Allow Printing.)

3) Add the following code to script to initialise the grid (Note: This code only needs to be executed ONCE in the forms lifetime. NOT every time the grid is filled or the record is changed) :


With FormObjectsLocal

‘ Get a reference to the query and the grid.

Set Grid = .ShowForm.GetFieldWidget(“ContactConfigFields”,”QuotesGrid”)

Set Query = .FormRef.GridQuery.GetQueryFromCollection(("ContactQuotes"))

End with


‘ Check that we have found both objects.

If Grid Is Nothing Or Query Is Nothing Then Exit Sub


‘ This is the line that does the work, the grid will load all of its properties from the grid.

Grid.SetQuery Query.QueryProperties, False


The SetQuery method takes 2 arguments, the first is the query properties object, and the second is a Boolean specifying whether the grid should immediately fill the grid using the queries SQL. Pass it true if the grid query does not require any contextual information (EG: the current record being viewed.)


Assuming that you do need to filter the records in the query then you will need to actually fill the grid, telling it how to filter the grid.


‘ Get a reference to the grid.

Set Grid = FormObjectsLocal.ShowForm.GetFieldWidget(“ContactConfigFields”,”QuotesGrid”)


‘ This is the line that fills the grid. The only argument is an optional filter clause. This will be appended to the Queries SQL.

Grid.FillGridFromQuery "ContactId = " & m_ContactId


You would call this whenever the contact Id changed, or when you wanted the grid to refresh.

As you can see the above code is far simpler than working with prior grids, defining the columns etc in script. All of this information will be loaded from the query, and if the user changes any of it (Moves a column for example), it will be persisted and restored without the scripter having to do any work.


In addition, non scripters can go in and alter the grid layout etc without having to mess around with code.


In situations where the grid can not be based upon a query (I struggle to think of any such situation at present), it can be set up manually as follows:


  With Grid

   

    ‘ Adds a column to the grid based upon a field in the results:

    ‘ Arguments: Caption, Field Name, Width, Visible.

    .AddColumnWithUnderlyingFieldName “ContactId”, “ContactId”, 1000 , False

    .AddColumnWithUnderlyingFieldName “Forename(s)”, “Christian”, 1000 , True

    .AddColumnWithUnderlyingFieldName “Surname”, “Surname”, 1000 , True

       

    ' Fill the grid.

    .FillGridFromSQL sSQL

     

    ' Group by the Surname column.

    .FieldGrouped("Surname") = True


  End With 

 

Both of the above situations require the grid to be populated using SQL, however it also supports AddItem type population. This can be achived as follows:


‘ The Add Item grid works with ADO data types (Used for formatting and sorting).

Const adVarChar = 200

Const adDBDate = 133

Const adInteger = 3


With Grid

   

    ‘ Adds a column to the grid based upon a field in the results:

    ‘ Arguments: Caption, Width, Visible, DataType (Used for formatting and sorting).

    .AddColumnWithDataType “ContactId”, 1000 , False, adInteger

    .AddColumnWithDataType “Forename(s)”, 1000 , True, adVarChar

    .AddColumnWithDataType “D O B”,1000 , True, adDBDate


    sCDL = .DefaultAddItemColumnDelimiter

    sRDL = .DefaultAddItemRowDelimiter


   ‘ Construct an Add Item string 

    ‘(Note you can do this a line at a time, or put all lines into a string and then 

    ‘ add them to the grid in one go as shown here)

   sAddItemsString = “1” & sCDL & “Dhugal” & sCDL & “01/06/1977” & sRDL & _

         “2” & sCDL & “Garry” & sCDL & “01/06/1917”


   ‘ Add the items to the grid.

   .AddItems sAddItemsString


End With 


Grouping within the grid can be achived as follows:


 ' Group by the Surname column.

Grid.FieldGrouped("Surname") = True



 

70. Setting Field, Button & Tab Visibility from Script


In build 2.00.070 onwards, it is possible to program field, button and tab visibility very easily…



Field Visibility:


FormObjectsLocal.ShowForm.ShowFieldFromName(EntityNameCF, "EarliestStartDate") _

= True


This deals with ALL related widgets such as labels, lookup buttons, spin buttons etc..




Button Visibility:


FormObjectsLocal.ShowForm.ShowButtonFromFunction(“Fire Interactive Video”) = False


Tab Visibility:


FormObjectsLocal.ShowForm.ShowTabFromIndex(1) = True


This deals with cases when tab to be hidden is selected.


Tab Selection:


FormObjectsLocal.ShowForm.SelectTabFromIndex(2) = True


This ignores cases when tab to be selected is not visible.




 

Appendix A - Type Mismatch Errors


This excerpt from MSDN explains the reason why some functions in Client Code will not be allowed to be called with BYREF parameters because of limitations in the VBScript (or JavaScript) language.



INFO: Type Mismatch Errors When You Pass Parameters from ASP to a Visual Basic Component

________________________________________

The information in this article applies to:

• Active Server Pages 

• Microsoft Visual Basic Learning, Professional, and Enterprise Editions for Windows, versions 5.0, 6.0

________________________________________


SUMMARY

The code sample below illustrates various scenarios that causes 

Type Mismatch

errors with method calls from an Active Server Page (ASP) using Visual Basic Script to Visual Basic COM components. 


'VB Code : [Project=prjParam;Class=clsParam]

'By default, the parameter is passed 'ByRef'


Sub x( a as string )

  a = "Changed"

End Sub 


'ASP Code

k = "Hello"


'Create the above VB object


Set obj = Server.CreateObject("prjParam.clsParam")


obj.x k    'Type Mismatch error occurs


obj.x (k)   'Using PARANTHESIS forces 'ByVal' , 'k' does not change


Call obj.x (k) 'Type Mismatch error occurs


Call obj.x cstr(k)  'The CSTR function returns a string,

           'the address of the variable (k) is not passed.

           'The value of 'k' doesn't change


Set obj = Nothing 

The following is another example that can cause the error: 


'VB Code : [Project=prjParam;Class=clsParam]

'If you do not specify, by default the parameter is passed 'ByRef'

'Note: Parameter type is VARIANT


Sub y( a as variant )

  a = "Changed"

End Sub


'ASP Code

k = "Hello"


'Create the above VB object


Set obj = Server.CreateObject("prjParam.clsParam")


obj.y k    'changes 'k'


obj.y (k)   'Using PARANTHESIS forces 'ByVal' , 'k' doesn’t change


Call obj.y (k) 'changes 'k'


Set obj = Nothing 


MORE INFORMATION

VBScript only supports VARIANT ByRef parameters. You can use VBScript to call a procedure that takes ByRef strings, but the default behavior of components built with Visual Basic is to fail with a type mismatch error when trying to pass ByRef parameters to these components. OLE Automation's default type-coercion function fails when asked to convert a ByRef variant into any other ByRef type. 


VBScript does not impose this restriction. However, it is the default behavior of the component that decides that a ByRef variant cannot be converted into a ByRef string. 


If a parameter to a procedure is enclosed in parenthesis, the parameter is first evaluated as an expression. Because the result of an expression cannot be passed by reference, the parameter is passed ByVal and no error is reported. 


Avoid using ByRef parameters unless they are explicitly needed. This is because: 

• ByRef procedures cause more overhead during cross-process marshaling, because COM must marshal the value both to and from the object. ByVal parameters require only one-way marshaling.


However, if you are willing to accept the overhead of any marshaling at all, an extra parameter marshal back is unlikely to cause a huge performance degradation.

• ByRef parameters can introduce hard-to-find bugs in your code if you accidentally change the value of one of the parameters.

• JScript does not support ByRef parameters of any type, so if you plan to write components that will support JScript, you should not use ByRef parameters at all. 


by Garry Lowther 02 Apr, 2024
The newest version of TriSys Mobile is now available for Apple and Android users
Top and Bottom Grid Paging now also available in TriSys Apex
by Francisco Janes 18 Mar, 2024
Top and Bottom Grid Paging now also available in TriSys Apex
by Francisco Janes 13 Mar, 2024
TriSys Apex now features editable contact previews designed to streamline your cloud recruitment database experience.
by Francisco Janes 12 Mar, 2024
TriSys Cloud Remote desktop for recruitment professionals. Cloud hosted recruitment software.
by Garry Lowther 05 Feb, 2024
We are planning some very exciting innovations and enhancements to the products and services of TriSys Business Software in 2024: TriSys Apex 2024 Our cross-platform web browser recruitment CRM was released in 2014 and uses the TriSys cloud platform to process all its data and functionality. Customers with any type of computer, tablet or phone can access their recruitment data and conduct sophisticated workflows to drive their business forward, wherever they are and at any time. We plan a number of innovations this year, including the port of the temps booking sheet, and integration of AI. TriSys 2024 The latest version of our premiere windows desktop recruitment CRM is now available to all existing customers. This application was first released in 1992 and is a rock-solid enterprise grade recruitment business app. For customers on the 2023 version, the application will automatically upgrade. All new features will be documented in the coming months. Our SDK is still the only recruiting software toolkit for Microsoft .Net. TriSys for the Microsoft Store was delivered using Visual Studio 2022. TriSys Cloud Platform Our cloud infrastructure platform was conceived and built in 2002. It has grown and matured to comprise literally hundreds of cloud servers distributed across the planet. Customers use our platform to store and process their large corporate data assets, via our software products and services. Expect more integration with world class web services, including artificial intelligence, through our Web API . Third Party Integrations The TriSys Recruitment Software Platform enables unlimited integration flexibility including standard and complex integrations across multiple popular third party industry suppliers enabling you to use TriSys as a process hub offering a single source of truth. We have recently built a sophisticated integration with Zapier , and this year we will fully integrate with additional best-of-breed third party business platforms including artificial intelligence (AI). TriSys Mobile Suite Recruiters can access their recruitment database from their tablet and phone by installing our app , however the mobile landscape is changing with a new class of 'app-builders' utilising low-code or no-code technologies. Due to the power and flexibility of the TriSys Web API, Recruitment agencies can cost-effectively commission their own dedicated and branded mobile app to engage with their clients and candidates. A new truly-native version of TriSys mobile will be released to the stores in Q1 2024. TriSys Web API Almost all our recent innovations centre around our web application programming interface which provides a fully secure set of programmable end-points not just into the TriSys Cloud Platform, but also to a host of third party global ecosystems, to provide customers with unlimited flexibility to deploy a range of sophisticated recruiting business benefits. Web API plans for 2024 are to add mobile app-specific functionality allowing the TriSys platform to reach a wider range of customers by utilising AI. TriSys Platformix Five years ago, we introduced a website builde r technology utilising a mix of cloud platforms, which we called Platformix . This is the successor to Web Jobs in that it is a full recruitment website content management system (CMS) with very tight integration with the TriSys recruiting database, plus state of the art blogging, SEO compliancy, and analytics. We will continue to invest in building TriSys Widgets to allow our customers to build high engagement web portals. We will also continue integration with Stripe to allow Gig-Economy services to be built for our customers. TriSys Open Source We now have full GitHub source code integration with all our software build tools, meaning that TriSys is now the only commercial recruitment software vendor to offer an open-source model. From 1st January 2024, existing customers are able to license TriSys' source code to build their own highly-specific recruitment CRM app for desktop, mobile and web. We are also planning to partner with other organisations to build complex line of business applications for all industries, based upon the thousands of man-years development and testing which has been invested in the TriSys Platform code base. TriSys Remote Desktop The original TriSys ASP was launched 22 years ago , and comprises a windows hosted desktop running on our cloud. Last year we launched a Windows 11 version with full compatibility with Microsoft Office 365 allowing customers to have full control over the apps they can install. This year we will continue to support all Microsoft supported versions of Windows.
by Francisco Janes 28 Nov, 2023
Announcing Real-Time E-Mail Synchronisation for your Business
by Francisco Janes 24 Nov, 2023
Announcing a New TriSys Integration: Daxtra Search Nexus and Capture
by Francisco Janes 23 Nov, 2023
Enhancing Recruitment Efficiency with ChatGPT
by Garry Lowther 22 Nov, 2023
This afternoon, I researched how to construct intrinsic load balancing within our existing production Web API to take advantage of multiple Web API servers on the TriSys Cloud Platform . I decided to use ChatGPT, given the extremely high profile of OpenAI's flagship service after the amazing events over these last few days. The dialogue between myself and ChatGPT was akin to me working with another ASP.Net Web API expert who knew more about specific topics than I did, however it was necessary for me to test the generated code and indeed suggest a fix to an issue which prevented the code working in a live environment. Me: In ASP.NET Web API, how can I route a request to another Web API server? ChatGPT: In ASP.NET Web API, routing a request from one Web API server to another is a task that typically involves creating a proxy or a forwarding mechanism within your API. This is useful in scenarios like load balancing, microservices architecture, or when integrating with external APIs. Here's a basic approach to achieve this: Me: With regards to Handling the Response and your advice: Once you receive the response from the target server, you can either directly return this response to the client or process/modify it as necessary. Can you supply .NET code which demonstrates this? ChatGPT: After numerous exchanges like this over 20 minutes, and much copy and pasting, I had working code running in the Visual Studio debugger which was remoted to allow my Postman session to make Web API requests. Eventually, we arrived at a working solution. Obviously I asked ChatGPT to summarise this work for this blog post: Title: Simplifying Load Balancing in Web Applications When building a robust web application, ensuring that it can handle multiple users smoothly is crucial. This is where 'load balancing' comes in - it's like having several doors to a shop, so customers don’t have to queue up at just one. In technical terms, we're distributing the user requests across multiple servers. In our journey, we encountered a few hurdles and solved them one by one. Here’s how we did it: 1. The Art of Forwarding Requests: Imagine you’re sending letters (user requests) to a friend (the server). But instead of one friend, you have several who can respond. We created a system where these letters are sent to any one of your friends who's available, ensuring no single friend is overwhelmed. 2. Addressing the Envelopes Correctly: We hit a snag - the letters were being sent, but they had the wrong return address, confusing the recipients. By adjusting the 'return address' on the letters (known as the Host header in web terms), we made sure each letter reached its destination correctly. 3. The Right Stamp for the Letter: Initially, we were putting every possible stamp (headers) on the letters, but not all were needed. So, we carefully selected only the necessary stamps, making sure the letters weren’t overloaded with unnecessary information. 4. Dealing with Misdirection: At one point, our letters were being sent correctly, but they seemed to vanish - like sending them to a house that doesn’t exist (HTTP 404 error). After double-checking the address and making sure our friends were ready to receive the letters, we fixed this issue. 5. Open Lines of Communication: Finally, we made sure that the way we send letters (synchronous vs. asynchronous communication) was efficient. This ensured that our system could handle many letters at once without getting bogged down. In essence, we fine-tuned the process of directing traffic to ensure a smooth experience for the users. It’s like ensuring a well-organized event where guests can move freely without any bottlenecks. This analogy should help non-technical readers understand the complexity behind load balancing in a web application in a more relatable way. Feel free to adjust the language or details to better suit your blog's style and audience!
by Garry Lowther 11 Oct, 2023
Recruiters can benefit from various good business practices in today's competitive job market. Here are some key practices that can help recruitment agencies and professionals succeed: 1. Candidate-Centric Approach : - Personalization : Tailor your communication and job recommendations to individual candidates, showing that you understand their unique skills and career goals. - Transparency : Be honest and transparent about job opportunities, including company culture, compensation, and job requirements. 2. Leverage Technology : - Applicant Tracking System (ATS) : Use an ATS like TriSys to streamline candidate management, automate administrative tasks, and enhance collaboration among team members. - AI and Data Analytics : Implement AI-driven tools for candidate sourcing, screening, and predictive analytics to make data-informed decisions. 3. Build Strong Employer Relationships : - Understanding Client Needs : Take the time to understand your clients' businesses, their cultures, and their specific hiring needs to provide better candidate matches. - Communication : Maintain open and consistent communication with clients to keep them informed about the recruitment process. 4. Effective Networking : - Online and Offline Networking : Build a strong professional network both online (e.g. LinkedIn) and offline (e.g. industry events). Networking can lead to referrals and valuable connections. 5. Diversity and Inclusion : - Diverse Candidate Sourcing : Make an effort to source candidates from diverse backgrounds, promoting inclusion and reducing bias in hiring. - Inclusive Language : Use inclusive language in job postings and communication to attract a broader range of candidates. 6. Candidate Experience : - Responsive Communication : Provide timely feedback and updates to candidates throughout the recruitment process. - Feedback Loop : Seek feedback from candidates about their experience and use it to improve your processes. 7. Continuous Learning : - Stay Informed : Keep up-to-date with industry trends, emerging technologies, and best practices in recruitment. - Professional Development : Invest in ongoing training and development for yourself and your team. 8. Compliance and Ethics : - Data Privacy : Ensure compliance with data protection regulations (e.g. GDPR) and handle candidate data responsibly. - Ethical Practices : Uphold ethical recruitment standards, treating all candidates and clients with fairness and respect. 9. Measurable Metrics : - Key Performance Indicators (KPI's) : Define and track KPIs to assess the effectiveness of your recruitment efforts, such as time-to-fill, quality of hire, and cost-per-hire. 10. Adaptability : - Flexibility : Be adaptable in responding to changes in the job market, economic conditions, and shifts in candidate expectations. 11. Content Marketing : - Content Creation : Share valuable content, such as blog posts, webinars, or whitepapers, to establish expertise in your niche and attract both clients and candidates. 12. Client Education : - Educate Clients : Help clients understand the importance of a streamlined and efficient hiring process, offering guidance on improving their hiring practices. 13. Feedback Loop : - Feedback and Improvement : Regularly gather feedback from both candidates and clients to identify areas for improvement in your processes. Good business practices in recruitment not only enhance the quality of your service but also build trust with both candidates and clients. These practices can contribute to long-term success in the competitive field of recruitment. If you are considering starting a new recruitment agency, we published some good advice a few years ago.
More posts
Share by: