Vba, Macro
Vba, Macro
022708 2008 Blackbaud, Inc. This publication, or any part thereof, may not be reproduced or transmitted in any form or by any means, electronic, or mechanical, including photocopying, recording, storage in an information retrieval system, or otherwise, without the prior written permission of Blackbaud, Inc. The information in this manual has been carefully checked and is believed to be accurate. Blackbaud, Inc., assumes no responsibility for any inaccuracies, errors, or omissions in this manual. In no event will Blackbaud, Inc., be liable for direct, indirect, special, incidental, or consequential damages resulting from any defect or omission in this manual, even if advised of the possibility of damages. In the interest of continuing product development, Blackbaud, Inc., reserves the right to make improvements in this manual and the products it describes at any time, without notice or obligation. All other products and company names mentioned herein are trademarks of their respective holder. All other products and company names mentioned herein are trademarks of their respective holder.
RE7Enterprise-VBAEssentials-022708
VBA . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
What is VBA? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94 API vs. VBA . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95 The VBA Environment . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95 Macros. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99 VBA DLL Macros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 138 Customizing RE:Anywhere for Remote Access Using VBA . . . . . . . . . . . . . . . . . . 140 VBA Code Samples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145
INDEX . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147
IV
WE L C O M E
Essentials
Contents
____________________________________________________________________ Who is This Guide For? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 Documentation Map . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 Programming Language . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 Sample Code. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 Raisers Edge Programming Essentials . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 Objects and Object Models . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 What Are Objects and Object Models? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 The Raisers Edge Object Model . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 Data Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 The Raisers Edge Type Library. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 Using Early Bound Objects and the Type Library . . . . . . . . . . . . . . . . . . . . . . . . 7 Using the Type Library from VBA. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 Accessing the References Dialog . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 Setting a Reference to The Raisers Edge Type Library. . . . . . . . . . . . . . . . 10 Using the Type Library from an API Application . . . . . . . . . . . . . . . . . . . . . . . 10 Accessing the References Dialog from Visual Basic 5.0 and Higher . . . . . . 11 The Raisers Edge Object Fundamentals . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 The SessionContext . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 Accessing the SessionContext from VBA. . . . . . . . . . . . . . . . . . . . . . . . . . . 12 Accessing the Session Context from API . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 Initializing and Releasing Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 The Init and CloseDown Methods. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 Data Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 Data Object Hierarchy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14 What Are Top Level Objects?. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14 Loading a Data Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 How Many Ways Can I Load a Data Object? . . . . . . . . . . . . . . . . . . . . . . . . 15 An Alternate Method to Load Data ObjectsThe Database ID. . . . . . . . . . 15 Using The Raisers Edge Search Screen to Load Your Data Object . . . . . . 16 Updating Data Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18 The Fields Property . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 Validation and Integrity. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20 Adding and Deleting Data Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20 Adding a Record Using a Data Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20 Deleting a Record Using a Data Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 Programming Child Objects and Collections . . . . . . . . . . . . . . . . . . . . . . . . 21 What is a Child Object? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 Child Collection Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 The Standard Child Collection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 The Child Top Collection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
CHAPTER 1
The Child View Collection. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . The Top View Collection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Adding and Deleting Child Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Adding a Child Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Deleting a Child Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Sorting Collections . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . SortField . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . SortOrder . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Filtering Data Object Collections. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Error Handling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Return Code Based . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Error Code Based. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . User Interface (UI) Objects. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Data Entry Forms. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Showing a Standard Form . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Raisers Edge ActiveX Controls . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Data Grid . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Attributes Grid . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Phones/Email/Links Grid. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Service Objects. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Query Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Opening a Query. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Processing a Query Result Set . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Creating Static Queries. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Report Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Reports Categories Collection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Reports Types Collection. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Report Instances Collection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Report Objects Sample . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Code Tables Server . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Table Lookup Handler . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Attribute Type Server. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Annotation Form . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Using the Annotation Form Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Notepad Form . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Using the Notepad Form Object. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Media Form . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Using the Media Form Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Property Viewer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Using the Property Viewer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Search Screen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Using the Search Screen Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . MiscUI . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Using the MiscUI Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Advanced Concepts and Interfaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Using the IBBDataObject Interface. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Using the IBBMetaField Interface. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Transactions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Custom View: Creating Custom Parts. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Custom Parts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Adding a Custom Part . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
28 28 30 30 31 31 31 32 32 33 33 33 34 35 35 37 39 41 44 44 45 45 45 46 49 49 51 52 54 56 59 62 65 65 67 67 69 69 72 72 74 74 77 77 79 80 81 83 84 84 85
ESSENTIALS
This guide provides Visual Basic developers with all the information needed to customize and enhance The Raisers Edge. From a quick VBA macro to a full blown application based on The Raisers Edge Object API, you can find what you need here. A wealth of programming samples are provided to illustrate key concepts and provide you with a solid foundation on which to build your custom Raisers Edge solutions. Please remember... We provide programming examples for illustration only, without warranty either expressed or implied, including, but not limited to, the implied warranties of merchantability and/or fitness for a particular purpose. This guide assumes you are familiar with Microsoft Visual Basic and the tools used to create and debug procedures. Blackbaud Customer Support can help explain the functionality of a particular procedure, but they will not modify, or assist you with modifying, these examples to provide additional functionality. If you are interested in learning more about The Raisers Edge VBA and API optional modules, contact our Sales department at [email protected]. The programming examples and related code provided to you via this guide are the property of Blackbaud, Inc. and you may not copy, distribute, convey, license, sublicense, or transfer any rights therein. All examples are subject to applicable copyright laws. We hope you find this guide useful as you develop for The Raisers Edge. If you are not sure if this material is targeted for you, see Using This Guide on page 3. If you have programmed in Visual Basic before, we suggest you review the Documentation Map on page 4. This map is a great starting point from which you can navigate to the particular aspect of Raisers Edge programming that interests you.
CHAPTER 1
Documentation Map
This guide is broken down into logical sections, each designed to help you learn and use a particular aspect of the available extensibility technologies. Because there is important information that applies to both VBA for Advanced Customization and API for Advanced Application Development optional modules, some documentation for both products is included in the Essentials chapter. If you come across VBA or API information that is not applicable to your organizations Raisers Edge software package, contact Sales at [email protected] for more information.
The Essentials
This chapter introduces key concepts that you need to understand as you program with The Raisers Edge.
Programmers Reference
This section, located only in the help file, provides a detailed listing of all objects and services available to developers programming with The Raisers Edge object model.
Programming Language
The code samples in this guide are written using Visual Basic 6.0 language. This language is shared by VBA, Microsoft Visual Basic 6.0, Microsoft Office 2000, and other VBA 6.0 host applications. While it is possible to use API from other languages (C++ or Java, for example), Blackbaud can only provide support regarding Visual Basic programming.
Sample Code
Periodically, we provide code samples to illustrate a point. Code samples always appear in the following format.
'Programming Example ' we will put VB code comments in Green
Dim oGift as CGift Set oGift = New CGift oGift.Init REApplication.SessionContext
Note.... You may notice occasional line breaks in the code due to layout constraints. All line breaks are documented with the standard _ in the code sample.
ESSENTIALS
Note that we sometimes clarify points in the code samples using standard Visual Basic comments. To accentuate them, all comments appear in green.
The SessionContext
This section introduces the most important object in the system. No Blackbaud programming task can be tackled without using a SessionContext.
Data Objects
All data elements in The Raisers Edge are modeled using data objects. Data objects provide a high-level COM programming interface to every data record in The Raisers Edge.
Service Objects
These objects provide a high-level interface to the system level functionality in various Raisers Edge modules such as Query, Export, and Reports.
CHAPTER 1
Data Objects
The Raisers Edge object model is based primarily around the data that the program manages. It does not expose the interface as a programmable entity. Because the key to your Raisers Edge system is the data that it manages, data objects are the key to programming The Raisers Edge.
ESSENTIALS
Lets take a look at a simple example. In The Raisers Edge, constituents can be assigned a constituent code. Constituent codes are used to denote important information on each record (such as Alumni, Past Parent, Major Donor). A constituent can have any number of constituent codes on her record (in relational database terms there is said to be a one-to-many relationship between a record and its constituent codes). A constituent code has no meaning outside of a record. For this reason, in The Raisers Edge data object model, the ConstituentCodes object is a child of the CRecord data object, which is the object in the system that represents constituents.
In this diagram, we see that for each CRecord there is a child object named ConstituentCodes, and the ConstituentCodes object has child object named CConstituentCode. The ConstituentCodes object name is plural for a very important reason. It is a collection object. This means it is used to iterate through any number of children (in this case constituent codes). All collection objects in The Raisers Edge object model can be navigated using For Each syntax, which is the standard for navigating VBA collections. Take a look at the next code sample. Don't worry about the detailsthey are introduced later in this guide.
'Note: The code to initialize and load a CRecord (oRecord) ' object omitted for brevity
Dim oConsCode as CConstituentCode
'Print all of this constituent's constit codes to the ' VBA debug window
For Each oConsCode in oRecord.ConstituentCodes Debug.Print oConsCode.Fields(CONSTITUENT_CODE_fld_CODE) Next oConsCode
CHAPTER 1
Another incredible productivity gain that becomes available when using type libraries with Visual Basic 5.0 and higher (or VBA) is Intellisense. If you have worked with VB or VBA, you have probably noticed while programming with objects that when you hit . after an object variable defined as a type described in a type library (or Visual Basic component) the code editor appears and displays a list similar to the one in the following graphic.
VBs Intellisense feature displays only the properties and methods that are available on the object. In the above graphic, you see properties and methods such as Actions, Addresses, Aliases. These are all child objects that are available for a CRecord object. VB can only work this magic if you are using an early-bound object. By early-bound, we mean an object variable that is declared as a specific type. Take a look at the following code sample.
'This variable is late bound. While it will still work, ' it will incur significant runtime overhead, and it will ' yield no intellisense
Dim oRecord As Object Set oRecord = New CRecord
'This early-bound variable provides optimal speed and ' access to the VB/VBA intellisense feature.
Dim oRecordEarly As CRecord Set oRecordEarly = New CRecord
ESSENTIALS
10
CHAPTER 1
Note... If you unmark the System checkbox, you must exit The Raisers Edge and enter the program again to restore System references. System references load when you enter The Raisers Edge. Therefore, if you try to add the reference back on the References dialog, an Automation error generates.
ESSENTIALS
11
Accessing the References Dialog from Visual Basic 5.0 and Higher
To set a reference to the library from Visual Basic 5.0 or higher, create a new VB project and select Project, References from the menu bar.
The SessionContext
Whenever you use an object exposed by The Raisers Edge object model, it must be initialized first. All objects are initialized with a very important object parameter, the SessionContext. The SessionContext is the key to programming The Raisers Edge. This object holds information regarding the state of the active instance of The Raisers Edge application. Please remember.... The SessionContext is the key to programming The Raisers Edge. This object holds information regarding the state of the active instance of The Raisers Edge application When you create new instances of objects and initialize them with a SessionContext, the object queries the SessionContext for important information they need to operate (for example, a handle to the low-level database connection interface).
12
CHAPTER 1
'Load Gift 1
oGift.Load 1
'Load Gift 1
oGift.Load 1
Note the similarities to the earlier VBA sample. The first three lines of code in the sample remain constant for any API application and are usually placed in a section of your API application that is executed only once (for example, in your main forms Load event).
ESSENTIALS
13
' Properly release reference to Gift Object using the CloseDown method
oGift.CloseDown
Initialize (.Init) with a SessionContext and release (.CloseDown) the object when you are done. If you attempt to use a Raisers Edge object without properly initializing it, a trappable runtime error is raised.
Closing down objects can be harder. If you fail to properly CloseDown an object, potentially all the object resources remain alive and in memory. To many developers, this is known as a memory leak. The objects attempt to detect this situation and raise errors in many situations if a .CloseDown call was not made. In some cases this type of leak cannot be detected immediately, leading to some hard-to-track bugs. Remember, if it has an .Init method, it probably has a .CloseDown method also, and you should always make sure you call them both.
Data Objects
Most Raisers Edge programming involves data objects. As discussed in Objects and Object Models on page 6, data objects provide a high-level abstraction layer over the records in the underlying Raisers Edge database. In this section we learn the basics of programming with data objects.
14
CHAPTER 1
The previous graphic shows the standard Raisers Edge Records page. The highlighted buttons are top level objects. Just as the end-user must first open a Constituent to access his constituent codes, you, as the developer, must load a constituent object first before navigating to the constituents constituent codes. In addition to the items above, constituent relationships and event participants are also top level objects.
ESSENTIALS
15
In the code snippet above, we see the programmer has instantiated a CRecord object. As mentioned earlier, CRecord is the data object that encapsulates individual and organization constituent records. Note the drop-down list that appeared automatically when the developer entered the unique field argument. Visual Basic knows which arguments are valid in the context of a CRecord because The Raisers Edge type library exposes this information. The productivity boost gained here cannot be overstated. As you program with Raisers Edge objects, you will see that throughout the system, arguments are exposed to Intellisense in this fashion.
16
CHAPTER 1
Code Sample
These code samples show the various ways to load a constituent data object for the hypothetical constituent Michael Simpson. Mr. Simpson has a social security number of 025-64-6381, and a database (primary key) of 166.
Dim oConstit as CRecord Set oConstit = New CRecord oConstit.Init REApplication.SessionContext
Using The Raisers Edge Search Screen to Load Your Data Object
So far, we have seen how to load a data object given a specific search key. Many times this is a completely acceptable solution (for example, if you are building your own customized search screen). However, in some cases you may require a more robust search, or you may want to concentrate on your application and use as many pre-built components as possible.
ESSENTIALS
17
The Raisers Edge exposes its search screen as a programmable entity. Using the standard Raisers Edge search screen from Visual Basic code is easy. The Raisers Edge search screen is referred to as a Service Object, meaning it is an object that provides easy access to Raisers Edge functionality. We are jumping ahead a little herethe many service objects provided by The Raisers Edge are discussed later in this guide, but at this point it is important to at least introduce the concept of using the search screen to load a data object.
Dim oConstit as CRecord
'If The user didn't cancel - assign the ' record they selected to our data object
If Not oSearch.SelectedDataObject Is Nothing Then Set oConstit = oSearch.SelectedDataObject End If
18
CHAPTER 1
The search screen (from the UI, this is the Open screen) is the end result of the previous code sample. The end-user is presented with the standard Raisers Edge search dialog. If the end-user selects a record, the search service constructs the appropriate data object, which we access from code via the SelectedDataObject property.
ESSENTIALS
19
In this sample, you can see the developer has accessed the fields property of a gift data object. He is presented with a drop-down of all the available fields on a gift, and he is selecting GIFT_fld_amt (which represents the Gift Amount field on the gift record). Review the complete code sample below that loads a gift from the database into the gift data object, increments the Gift Amount field (by $10.00), and then saves the gift.
Dim oGift As CGift Set oGift = New CGift oGift.Init REApplication.SessionContext oGift.Load 2
'Clean Up
oGift.CloseDown Set oGift = Nothing
This sample illustrates how simple it is to update your data using data objects. Remember, if we put invalid data into the amount field (for example, xxxx) when we issue the .Save method on the object, the data object raises a trappable error.
20
CHAPTER 1
If a Visual Basic developer attempts to over-apply a pledge using Visual Basic code, a trappable error is raised with the same message (accessible via the err.description property on the Visual Basic Error object). You will learn about trapping and handling data object errors later. For now, it is important to understand this validation exists to maintain a high level of consistency and integrity in your database. The main point to remember is the object insulates your database, and no garbage can make it to the database without first being validated by the object. This rule applies to every facet of the data element. The Raisers Edge uses code just like the code provided in the examples, so you can be sure that updates using data objects are consistent with updates made by end-users in the system.
'Always clean up. Objects with an Init() method typically ' have a CloseDown() method.
oRec.CloseDown set oRec = Nothing
ESSENTIALS
21
Since the CRecord object (remember, CRecord is the data object that represents a constituent in The Raisers Edge) has only one required field, we are able to initialize a new object, set the contents of the Last name field, and issue a save. All top level objects are added in this exact same manner. Earlier in this guide, we discussed child objects and the object hierarchy. You access child objects only via a parent (top level) object. Therefore, child objects are added in a slightly different fashion. For more information about adding and deleting child objects in detail, see Programming Child Objects and Collections on page 21.
'Load the record via the Social Security Number ' Note: we left out some error trapping here to keep the sample clear ' (for example if this record didn't exist)
oConstit.LoadByField uf_Record_SOCIAL_SECURITY_NO, "025-64-6382"
22
CHAPTER 1
In the following graphic, we see The Raisers Edge constituent form. The form encapsulates all the child objects of a constituent. Note that the constituent codes are all children of this record and are available only through the constituent's CRecord object.
Child objects cannot be created, loaded, saved, initialized, or deleted. All these actions are accomplished via methods exposed by the child objects parent object in the hierarchy.
ESSENTIALS
23
Why does this seemingly artificial constraint exist? Child objects depend on the parents save method because the parent may have to enforce rules governing membership in the collection. When the parent is saved, all the child objects in the collection are saved if they are dirty (meaning either their data has been changed since they were loaded, or they have been newly added), and all the objects that have been removed from the collection are deleted from the database.
Following standard collection object model design practices, The Raisers Edge always has two closely related classes that handle exposing collections: the parent, which is always named in the plural form (for example, ConstituentCodes), and the child, which is always named in the singular form (for example, CConstituentCode).
24
CHAPTER 1
The code sample loops through each constituent code found linked to the constituent currently being referenced by the oRecord variable. When the last code is accessed, the loop automatically terminates. Here is a sample of the output from the code above.
'Modify each constituent code, setting it's date ' from to December 13th, 1967
oCode.Fields(CONSTITUENT_CODE_fld_DATE_FROM) = "12/13/1967" Next oCode
'Important! None of the changes are saved until ' the next line of code executes!!
oRecord.Save
ESSENTIALS
25
It is very important to note that the constituent code changes were not immediately committed to the database. Remember, child objects do not have a save method; the top-level parent (in this case the oRecord object variable) is responsible for the save. When we issue oRecord.Save, all the changes made are validated against any system and end-user Business Rules. If the rules pass all the rule checks, the records are committed to the database. If a rule fails to be validated, The Raisers Edge raises a trappable runtime error. For example, one of the rules that apply to constituent codes is the Date from field must come before (chronologically) the Date to field. So, in the example above, if we had a constituent code child element that already has a date to of 12/12/1967, The Raisers Edge would yield the following error.
As we mentioned earlier in this guide, this internal checking is paramount to preserving database integrity. You simply cannot corrupt your Raisers Edge data from Visual Basic code. The same rules that apply to an end-user apply to your data objects.
'Access an element of the collection that has an ' underlying database id (primary key) of 5.
With oRec.ConstituentCodes.Item("5") Debug.Print .Fields(CONSTITUENT_CODE_fld_DATE_FROM) End With
To ensure consistent access to collections across the object model, we provide these two different methods. The most common use of the Item method of a child collection is to pass it a numeric parameter, accessing the nth item. This becomes more evident when we discuss sorting later in this section. When we work with top-level collections, the value of accessing collection elements via the database ID becomes more clear.
26
CHAPTER 1
ESSENTIALS
27
With The Raisers Edge Enterprise, the Campaigns tab is not a child top collection. It is view only.
28
CHAPTER 1
This is the standard fund record form presented by The Raisers Edge. In this graphic, the end-user has navigated to the Campaigns tab of a fund record. The campaigns linked to the Building Fund are highlighted. To link another campaign to the fund, the end-user would simply click the binoculars in the grid and an Open search screen would appear with a list of campaigns that exist in the database. From here, the end-user could select an additional campaign to link to the fund. The important thing to remember is the user is not entering any new data. She is simply linking this fund to an existing campaign. The same applies to programmatic manipulation of a child top collection. The child top collection has an add method just like any collection. However, the add method of a child top collection accepts one argument, which is a reference to an existing top-level object. The code sample below explains this.
'Load fund record 1
Dim oFund as CFund Set oFund = New CFund oFund.Init REApplication.SessionContext oFund.Load 12
'Get the first campaign in the system ' by navigating to the first element in top level campaigns collection
Dim oCamps As CCampaigns Set oCamps = New CCampaigns oCamps.Init REApplication.SessionContext Dim oCamp As CCampaign Set oCamp = oCamps.Item(1)
ESSENTIALS
29
One powerful feature of top view collections is the ability to apply a filter to the collection when it is initialized so that only a specific subset of objects are included. For example, you may want to only include active campaigns when using the CCampaigns collection. In this case, you pass the correct filter constant (tvf_Campaign_ActiveOnly) and as the collection is initialized, it contains only campaigns that have been marked as active. This additional parameter is optional.
'Define a variable to navigate the top view collection.
Dim oAllCamps as CCampaigns Dim oCamp as CCampaign Set oAllCamps = new Ccampaigns oAllCamps.Init REApplication.SessionContext, tvf_Campaign_ActiveOnly For Each oCamp in oAllCamps Debug.Print oCamp.Fields(Campaign_fld_Description) Next oCamp
Each top-level object has a corresponding top view collection. Remember, a distinguishing characteristic of a collection is that the objects name takes the plural form. The table below lists all the top-level objects and their corresponding collections. The Record Type column refers to the record as it is represented in The Raisers Edge. The Data Object column lists the corresponding data object that is used to manipulate the record type programmatically. The Collection Object column lists the top view collection object that can be used to navigate the records top view collection. Record Type Constituent Gift Action Fund Campaign Appeal Membership Job Special Event Description CRecord CGift CAction CFund CCampaign CAppeal CMembership CJob CSpecialEvent Collection Object CRecords CGifts CActions CFunds CCampaigns CAppeals CMemberships CJobs CSpecialEvents
30
CHAPTER 1
'The Add method returns a reference to a new CConstituent Code ' object in the CConstituentCodes collection
Set oConsCode = oRecord.ConstituentCodes.Add
'This step saves the new constituent code information to the database
oRecord.Save
'Clean up!
oRecord.CloseDown Set oRecord = Nothing
The important point to remember is that the Add method of the collection is the only way to create a new child object. All child objects are added using the same process.
ESSENTIALS
31
'The object is not actually removed from the database until this step
oRec.Save
Either method accomplishes the same task. The situation determines the best method to use. When you remove a child object, no warning message appears, so you should add a warning that the end-user is about to delete information.
Sorting Collections
After you know how to access and move through collections, you may want to arrange the objects in a different order from the way they normally appear in the collection. Not all collections can be sorted in this manner, but many of the more commonly used collections do support sorting. When sorting collections, you must keep a couple of very important things in mind. First, when using the Item method, remember that it returns the nth member based on the current sort. Second, when using top view collections, it is possible to filter out top-level objects using a query. If you filter the collection using a query, the query order is retained regardless of following settings.
SortField
You can use the SortField property to specify any data field available in the member object to be the field used to sort the entire collection. With the use of IntelliSense and Enums, it is very easy to choose the field you would like to sort by.
32
CHAPTER 1
SortOrder
The SortOrder property allows you to sort in either ascending or descending order. If no SortOrder is specified, then the default order is ascending.
'Initialize collection
Dim oFunds As CFunds Set oFunds = New CFunds oFunds.Init REApplication.SessionContext
ESSENTIALS
33
Once you know the query ID, set the property FilterQueryID equal to this query ID. The collection returns only child objects contained in that query. Note the child objects are sorted into the collection in the same order as in the query.
Dim oQuery As CQueryObject Set oQuery = New CQueryObject oQuery.Init REApplication.SessionContext
'This tells the collection which query (Major Donors) to filter with.
oRecords.FilterQueryID = oQuery.Fields(QUERIES2_fld_QUERIESID)
'From here on, we can use the oRecords collection and it will only ' contain cRecord objects that are in the Major Donor query.
Error Handling
Before you resolve errors generated during program processing, it is important to understand the possible ways The Raisers Edge objects can communicate with your programming. As you program, many times a The Raisers Edge object needs to return information to your programs. For example, using a query to filter the objects in our collection. If you tried to use a campaign query to filter a fund collection, this would not work. The object needs some way to communicate this back to the program, so that you can resolve this problem. You can use two methods to accomplish this: Return Code Based on page 33 Error Code Based on page 33
34
CHAPTER 1
When an error is raised, we can access information about the error by using the Err object provided by VB. Err.Description is a helpful property that tells you the reason for the error, such as failing to specify all required fields when adding a Raisers Edge new fund through code.
Dim oFund As CFund Set oFund = New CFund oFund.Init REApplication.SessionContext oFund.Fields(FUND_fld_DESCRIPTION) = "The Sullivan Scholarship Fund" On Error GoTo ErrorHandler oFund.Save
'Clean up!
oFund.CloseDown Set oFund = Nothing
'Always place an Exit Sub before the Error Handler ' to prevent entering the Error Handler unintentionally.
Exit Sub ErrorHandler: MsgBox Err.Description, vbOKOnly
'This returns processing back to the line after where the error occurred
Resume Next End Sub
If we ran this code, the CFund object would raise an error and we would get a message box similar to the one below.
ESSENTIALS
35
Some ActiveX controls in The Raisers Edge are very convenient for displaying certain types of information. For example, all of the different types of attributes are displayed in a specific control, called the Attributes grid. In your program, if you need to display a list of attributes, you can use the same control The Raisers Edge developers use to display attributes.
oMoveServer
IBBMoveServer
36
CHAPTER 1
Code Sample
Dim oCampaign as CCampaign Set oCampaign = New CCampaign oCampaign.Init REApplication.SessionContext
'If we wanted to use show an existing Campaign, we would load it here. ' Or we could set some of the .Fields before we display the Campaign.
oCampaign.Fields(Campaign_fld_CampaignID) = "LIBRARY" oCampaign.Fields(campaign_fld_DESCRIPTION) = "Library Campaign" oCampaign.Fields(campaign_fld_START_DATE) = "11/6/98" Dim oForm as CCampaignForm Set oForm = New CCampaignForm oForm.Init REApplication.SessionContext
'Clean up!
oForm.CloseDown Set oForm = Nothing oCampaign.CloseDown Set oCampaign = Nothing
ESSENTIALS
37
This code displays a fully functional Campaign so the end-user can do anything he normally does on a Campaign form in The Raisers Edge.
38
CHAPTER 1
To use these controls in Visual Basic 6.0, first add the components to your VB project. To do this, select Project, Components from the menu bar. When the Components form displays, mark Blackbaud RE ActiveX Controls 7.5b.
Note... If you are using VBA, the control references are REControls7b.REAttributeGrid, REControls7b.REDataGrid, and REControls7b.REPhoneGrid. For more information about VBA, see the VBA chapter in this guide. Once you have added the Controls to your project, the next step is to place the control you want on the form.
ESSENTIALS
39
Data Grid
The data grid is used in many places to display information and enable the user to select a record from the list in The Raisers Edge. For example, it is used to display lists of constituents, funds, and gifts. After you have drawn the grid onto your form, you may want to set the property PreviewDataType. This property, which is only available at design-time, allows you to tell the control the type of data objects you want to display. It then shows all the columns that are standard for that type of record. You can then size the grid appropriately. You do not have to set this property during design-time. The grid displays the columns based on the type of collection you use regardless. However, it is an easy way to determine the best size for the grid.
40
CHAPTER 1
Now, lets review the grid during run-time. Before we can use the grid, we must call the Init method. Next, we set the DataCollection property by passing a collection of the data objects we want to display. The last step to displaying the grid is to call the Refresh method. Any time your data collection changes, you should call the Refresh method. This updates the grid using the current members of the collection. The following code sample shows you how to use the grid.
Option Explicit Private moRecord as CRecord Private Sub Form_Load() Set moRecord = New CRecord moRecord.Init ReApplication.SessionContext moRecord.Load 166 REDataGrid1.Init REApplication.SessionContext Set REDataGrid1.DataCollection = oRecord.Gifts REDataGrid1.Refresh End Sub Private Sub Form_Unload(Cancel As Integer) REDataGrid1.CloseDown End Sub
There are methods for the grid that offer access to the same features present in The Raisers Edge. Calling the CustomizeCollection method brings up a form the end-user can use to select columns and sort the order in which they appear. Using the ShowLegend method enables the user to select colors, use Bold and Italic type, and add subscripts for certain data objects.
ESSENTIALS
41
There are also events associated with the grid that allow your end-users to open records from the data grid. For example, there is a double-click event that passes the ID of the selected record, so you can load and display the record. The following code is an example of this.
Private Sub REDataGrid1_DoubleClick(ByVal lSelectedID As Long) Dim oGiftForm As CGiftForm Set oGiftForm = New CGiftForm oGiftForm.Init REApplication.SessionContext Dim oGift As CGift Set oGift = New CGift oGift.Init REApplication.SessionContext
'This uses the ID that is passed by the event to load the correct gift
oGift.Load lSelectedID Set oGiftForm.GiftObject = oGift oGiftForm.ShowForm False, Me, True
'Clean up!
oGift.CloseDown Set oGift = Nothing oGiftForm.CloseDown Set oGift = Nothing End Sub
Attributes Grid
Unlike the Selection List, the Attributes grid allows you to display attributes and enables your end-user to make changes to those attributes.
42
CHAPTER 1
To use the grid, you must first initialize it using the familiar Init method. Next, you need to pass the grid the collection of attributes to be displayed using the AttributeCollection property. Lastly, have the grid refresh itself using the Refresh method. When you finish with the grid (probably when the form unloads), call the CloseDown method, in order to free up the memory associated with the grid. The following code is an example.
Option Explicit Private moRecord as CRecord Private Sub Form_Load() Set moRecord = new CRecord moRecord.Init ReApplication.SessionContext moRecord.Load 166 With REAttributeGrid1 .Init REApplication.SessionContext Set .AttributeCollection = oRecord.Attributes .Refresh End With End Sub Private Sub Form_Unload(Cancel As Integer) REAttributeGrid1.CloseDown End Sub
This is all you need to display a list of attributes. It is important to remember the attribute collection and the grid do not automatically update when one changes. For example, if attributes are added to the collection, the grid does not automatically display those new attributes. In this case, you need to call the Refresh method again. This method updates the grid with the current information from the attributes collection. If your end-user makes changes to the attributes listed on the grid, these changes do not automatically update in the collection. In order to save those changes, call the SaveGridToCollection method. This updates the collection with the end-users changes. To actually save the records to the database, you need to call the Save method for the Parent record of the attributes collection. Use the following code sample to do this.
'This updates the oRecord.Attributes collection with ' the user's changes to the grid
REAttributeGrid1.SaveGridToCollection
'The changes are not saved to the database until the ' parent record is saved
On Error GoTo ErrorHandler oRecord.Save On Error GoTo 0
ESSENTIALS
43
The Attributes grid also has a method that handles situations if invalid data is in the collection when it is saved. For example, if an invalid date is entered for an attribute, a trappable error is generated when you save the parent record. By using the HandleError method, you can restore focus to the invalid field. The following code is an example of how to do this.
ErrorHandler:
'This will display a message box, so the user will know what the error is
MsgBox Err.Description
'This checks to see if the error is caused by the data in a data object
If Err.Number = bbErr_DATAOBJECTERROR Then
'This uses the ErrorObject to determine which part of the record ' caused the error
If TypeOf moREApi.SessionContext.ErrorObject.InvalidObject _ Is IBBAttribute Then
'When we pass the Err.Number it will return focus to the ' incorrect field on the grid
REAttributeGrid1.HandleError Err.Number End If End If
44
CHAPTER 1
Phones/Email/Links Grid
The Phones/Email/Links grid has many features in common with the Attributes grid. They are both designed to enable the user to input or update information. They also have many of the same properties and methods; therefore, much of the code is familiar. The Phones/Email/Links grid automatically formats phone numbers according to the format specified in Configuration.
Service Objects
The Raisers Edge is built on a foundation of programmable objects that provide a high level abstraction over The Raisers Edge data model. UI objects enable programmatic access to the systems data entry forms. The wide range of data and UI objects all share a common programming interface. While these objects represent the heart of the system, there is a lot more to The Raisers Edge than just data and UI components. Other objects enable access to discrete functionality within the application. These objects cannot specifically be categorized because they each provide a service via their own unique programming interface. To help organize these entities, Blackbauds object model refers to them as Service Objects. In this section we review the service objects exposed by The Raisers Edge and examine the mechanics of programming them. It is likely that service objects are called upon frequently as you tackle various development tasks with the system. For example, with the Query service object you can access pre-existing queries, so you can work with the querys result set, opening up a wide range of reporting and data analysis possibilities.
ESSENTIALS
45
Query Objects
A query object is referred to a group of objects that provide query functionality in The Raisers Edge object model. These objects include: CQueryObject CQueryObjects CQuerySet CStaticQ These four objects allow programatic access to existing queries, access to the output of a query, and the ability to create a new static query that can be used elsewhere in The Raisers Edge. A solid understanding of these objects work goes along way to making your projects faster and more efficient.
Opening a Query
Opening an existing query is quite easy and similar to the data objects that you learned about previously. You access information about a query through the CQueryObject. First, you must initialize the object and then load it. Like data objects, there is a Load method if you know the database ID of the query. There is also a CQueryObjects collection you can loop through to find the correct query. After you load the query, you can access its result set. The following code sample shows how this is done.
Dim oQuery as CQueryObject Set oQuery = New CQueryObject oQuery.Init REApplication.SessionContext
46
CHAPTER 1
Or, if you know the querys database ID, you can start with a CQueryset object.
Dim oQuerySet as CQuerySet Set oQuerySet = New CQuerySet oQuerySet.Init REApplication.SessionContext
Both of these examples accomplish the same task. In either case, we have a reference to a query resultset. You can use a few properties that help to actually access the data from the result set: Property FieldCount FieldName FieldType FieldValue RowNum Returns The number of fields in the output of the query An array of the field names in the output An array of the field type (for example, Date, Double, Long, Memo, Text) An array of the actual data for the current row The number of the current row.
Code Sample
Debug.Print oQuerySet.FieldName(1) & " " & oQuerySet.FieldName(2) Do While Not oQuerySet.EOF
'Clean up
oQuerySet.CloseDown Set oQuerySet = Nothing
ESSENTIALS
47
1. Use the Create method to create a new query. This method displays the same Create Query form used in The Raisers Edge. The end-user can specify the name of the query and other information about the query. The Create method returns a False if the user clicks Cancel. You should abort your process in this case.The following table shows the parameters for the Create method. Parameter SearchType aFromProcess Name FormToCenter On sDescription lSystemID sDefaultQName Variable Type bbSearch Types String Object String Long String Description Determines the type of the query. For example, what types of records are included Each query stores from the area of the program it was created. You may put the name of your application here. The Create Query form displays itself centered over the object specified here. Optional: Allows you to input a default Description for the new query. Included in parameters, but not applicable. Optional: Allows you to input a default Query name for the new query.
2. To add the database IDs of the records you want to include in the query, use the AddRecord method and pass the ID as the only parameter. The AddRecord method checks to make sure it is not a duplicate ID and then adds it to the query. This is the only step required to add a record to the query. 3. To finish creating the query and write the information to the database, call the EndCreate method. Until this is called, the IDs are just stored in memory. EndCreate has three parameters: FormToCenterOn accepts an object. When EndCreate is called, it normally displays a Writing Static Records form while it is writing the IDs to the database. This parameter specifies the form on which you would like the Writing Static Records form to center itself. bCancel is an optional parameter that defaults to False if nothing is passed. If your code allows the end-user to cancel the creation of the query after the Create method is called, it is important to call the EndCreate method and pass True for this parameter. The query is not created, but this frees the memory used to track the IDs for the query. bNoUI is an optional parameter. For the program not to display the Writing Static Records, set this to True.
48
CHAPTER 1
This code sample loops through the records to find the ones you need if want a query of couples in the database who have different last names from each other.
Dim oRecord2 As CRecord Set oRecord2 = New CRecord Dim oRecord1 As CRecord Set oRecord1 = New CRecord Dim oRecords As CRecords Set oRecords = New CRecords oRecords.Init oAPI.SessionContext Dim oStaticQuery As CStaticQ Set oStaticQuery = New CStaticQ oRecord2.Init oAPI.SessionContext oStaticQuery.Init oAPI.SessionContext
'This will prompt the user for a Query Name but ' everything will already be filled in
If oStaticQuery.Create(SEARCH_CONSTITUENT, "Custom App", Nothing, _ "List of couples who have different last names", , _ "Spouses W\Different Last Names") Then For Each oRecord1 In oRecords
'This checks first to see if they even have a spouse on their record
If Val(oRecord1.Fields(RECORDS_fld_SPOUSE_ID)) > 0 Then oRecord2.Load oRecord1.Fields(RECORDS_fld_SPOUSE_ID) If oRecord1.Fields(RECORDS_fld_SPOUSE_ID) <> "" Then
'Compares the constituent's last name with the spouse's last name
If oRecord1.Fields(RECORDS_fld_LAST_NAME) <> _ oRecord2.Fields(RECORDS_fld_LAST_NAME) Then
'Once we have all our records in our query, ' we write the data to the database
oStaticQuery.EndCreate Nothing, False, False oStaticQuery.CloseDown Set oStaticQuery = Nothing Else
'This means the user canceled when entering the query name
MsgBox "No query created", vbOKOnly End If
ESSENTIALS
49
Report Objects
Report objects are a group of objects that work together to provide the ability to access Raisers Edge Reports and Mail functionality through code. Since the mail functions also use Crystal Decisions Crystal Reports, it makes sense to provide one set of objects that can be used to print reports and process mail functions. These objects appear in a hierarchy that represent the way they are accessed in The Raisers Edge. When you access Reports, you are presented with a list of categories, such as Financial Reports. After you click the category, you are given a list of the different types of reports in that category, such as the Gift Entry Validation. When you create a new report, a screen opens containing tabs on which you define parameters for a report. Report objects follow the same hierarchy; however, depending on the needs of your project, you can enter the object model from any object.and create each class independently. You do not directly create report objects as you do the other objects. In order to create a new ReportCategories or ReportCategory object, use the REServices object.
In this hierarchy, IBBReportCategories is a collection of IBBReportCategory objects. These represent the categories of Reports or Mail options present in The Raisers Edge (such as Financial Reports, Action Reports, or Letters in the Mail section). The next level is the IBBReportTypes and the IBBReportType objects; these represent the specific reports (such as the Gift Entry Validation and Action Detail Report, or Follow-up Letters in Mail). The last level of the hierarchy is the IBBReportInstances and IBBReportInstance objects. These correspond to the individual parameter files that you can save for each report. When you are at this level, you can allow your end-user to preview or print the report or even create a new set of parameters for the report type. The next three sections review each of these objects in detail.
50
CHAPTER 1
Parameter CategoryFilter
Description Optional: This is used to specify Reports, Mail, or both IBBReportCategory objects in the collection. This defaults to include Report IBBReportCategory objects only. Optional: This establishes how errors are handled when using the collection. If it is set to True, a log file is created containing any errors, but the program continues to process. If set to False (which is the default) a trappable error is raised. Optional: Use this to include IBBReportCategory objects in the collection an end-user has the security to view. If set to False, the collection includes all objects, regardless of the users security. However, at any point the end-user would still be unable to run a report they did not the security to access. This defaults to True. Optional: There are some IBBReportCategory objects that represent reports not accessed via Reports, such as query control reports or Global Add reports. If this is set to True (the default), only the IBBReportCategory objects that represent categories found in Reports are in the collection.
QueMode
Boolean
ShowMembersBasedOnSecuritySettin gs
Boolean
ShowCannedReportsOnly
Boolean
When you initialize the IBBReportCategories collection, you can use a For Each construct to loop through it or use the Item property to access the IBBReportCategory objects in the collection. If you enter the Report hierarchy directly from an IBBReportCategory object, you need to Init it first. Some Init parameters for the IBBReportCategory are similar to the parameters for the IBBReportCategories, but they work differently. The IBBReportCategory object contains a ReportTypes method. This returns a IBBReportTypes collection of IBBReportType objects. The ShowMembersBasedOnSecuritySettings and ShowCannedReportsOnly parameters filter the IBBReportType objects included in the IBBReportCategory.ReportTypes collection. Parameter SessionContext CategoryID Variable Type IBBSessionContext
EReR_ReportCategories
Description This is the same SessionContext used to initialize all objects. This is a Enum of all the different categories of reports using in the Raisers Edge.
ESSENTIALS
51
Parameter QueMode
Description Optional: This establishes how errors are addressed when using the object. If it is set to True, a log file is created containing any errors, but the program continues to process. If set to False (which is the default) a trappable error is raised. Optional: Use this to include only the IBBReportCategory.ReportTypes collection objects an end-user has the security to view. If set to False, the collection includes all objects, regardless of the users security. However, at any point the user is unable to run a report he did not have the security to access. This defaults to True. Optional: Some IBBReportType objects represent reports that are not accessed via Reports. If set to True (which is the default), only the IBBReportType objects that represent reports found in Reports is included in the IBBReportCategory.ReportTypes collection.
ShowMembersBasedOnSecuritySettings
Boolean
ShowCannedReportsOnly
Boolean
When you initialize the object, you can access the ReportTypes property to move farther down the hierarchy of Report objects. For more information, see the Report Objects Sample on page 54 for an example of how to create and use these objects. Refer to Programming Reference to learn more about the other properties and methods of these two objects.
Description This is the same SessionContext used to initialize all objects. This is a Enum of all Report categories so only ReportTypes that are a part of this category are included in the collection. Optional: This establishes how errors are addressed when using the object. If set to True, a log file is created containing any errors, but the program continues to process. If set to False (which is the default) a trappable error is raised.
QueMode
Boolean
52
CHAPTER 1
Parameter
ShowMembersBasedOnSecuritySettings
Description Optional: This includes only the IBBReportCategory.ReportTypes collection objects an end-user has the security to view. If this is set to False, the collection includes all objects, regardless of the users security. However, at any point the user is unable to run a report he does not have the security to access. This defaults to True. Optional: Some IBBReportType objects represent reports that are not accessed via Reports. If this is set to True (which is the default), only the IBBReportType objects that represent reports found in Reports are included in the IBBReportCategory.ReportTypes collection.
ShowCannedReportsOnly
Boolean
After initializing the IBBReportTypes collection, you can iterate through the collection or select an IBBReportType object by using the Item method. If you already know the type of report you want to access, enter the report hierarchy at the IBBReportType object. As always, call the Init method and provide a couple of parameters in order to access the correct report (see the following table). Parameter SessionContext CategoryID ShowOnlyMyReports Variable Type IBBSessionContext EReR_ReportCategories Boolean Description This is the same SessionContext used to initialize all objects. This is a Enum of all the Report types so you can specify which report to access. Optional: This establishes how errors are addressed when using the collection. If set to True, a log file is created containing any errors, but the program continues to process. If set to False (which is the default) a trappable error is raised.
When the IBBReportType object is initialized, you can access its read-only properties to get more information about this particular report. It also has a ReportInstances property so you can access the last levels of the Report hierarchy. Here, you can actually process a report.
ESSENTIALS
53
The IBBReportInstances object is a collection that represents all the parameter files for a particular type of report. As with the Report objects, it is created using the REServices object (see Report Objects Sample on page 54). When you create the object, use the Init method to initialize it. The following table shows the parameters for the Init method. Parameter SessionContext ReportTypesID Variable Type IBBSessionContext EReR_ReportCategories Description This is the same SessionContext used to initialize all objects. This is a Enum of all the Report types so only ReportInstances for the type specified here are included in the collection. Optional: This establishes how errors are addressed when using the object. If set to True, a log file is created containing any errors, but the program continues to process. If set to False (which is the default) a trappable error is raised. Optional: If set to True, the collection contains only IBBReportInstances objects that represent parameter files created by the end-user. If set to False (the default), all parameter files available to the end-user are represented in the collection.
QueMode
Boolean
ShowOnlyMyReports
Boolean
After you initialize the collection, you can use any standard process to iterate through the collection. If you use the IBBReportInstance object to enter the hierarchy, use the Init method. The following table shows the parameters for the Init method. Parameter SessionContext QueMode Variable Type IBBSessionContext Boolean Description This is the same SessionContext used to initialize all objects. Optional: This establishes how errors are addressed when using the object. If set to True, a log file is created containing any errors, but the program continues to process. If set to False (which is the default) a trappable error is raised.
After you initialize an IBBReportInstance, you can either load an existing parameter file or create a new one. To load an existing IBBReportInstance, all you need to know is the ReportParameterID, which is the database ID of the parameter file. After you use the Load method, or if you are creating a new parameter file, call the Process method. The following table explains the parameters for this method. Parameter Action ShowModal Variable Type EReR_ProcessOptions Boolean Description This is an Enum of the process options available. Optional: This determines if the Process form (this varies depending on the action) is displayed modally. This defaults to False. Optional: This determines which object the Process form displays.
FormToCenterOn
Object
54
CHAPTER 1
Parameter RunFromWeb
Description Optional: This is used internally for the The Raisers Edge for the Web. This parameter should be left blank.
The Process method supports a number of actions that are enumerated as EReR_ProcessOptions. These include ReR_ProcessOption_ShowParameterForm which shows the parameter form for the particular report type you are using. If you have not called the Load method, a new parameter form displays that allows the end-user to complete the parameters and save and run the report from the parameter form. If you have called the Load method, the form appears with the parameters already displayed, allowing an end-user to edit the parameters and run the report. If you do not want to display the parameters, you can use the other EReR_ProcessOptions to directly print, print preview, export, send as mail, or view the report layout. The Process method returns a Long integer which is a unique handle to a Crystal Report file. This is used internally by The Raisers Edge for the Web and can be disregarded. For more information about other properties and methods available, see Programming Reference. It is important that when you finish using an IBBReportInstance that you call the CloseDown method. Even though it may return False, indicating that it cannot be closed at this time, it sets an internal flag and cleans everything as soon as the end-user closes the report. For example, after you call the Process method with an action of Preview, you can call the CloseDown. When the user closes the preview window or exits the application, the object releases the resources it was using. However, you should make sure you do not need to access any property or method from the object. Once CloseDown is called, the object acts as if it is closed down, even if the preview window or parameter form still displays.
ESSENTIALS
55
With oReportInstance TreeView1.Nodes.Add "Type" & Str$(oReportType.ReportID), _ tvwChild, _ Str$(.Property(ReR_Property_ReportParameterNamesID)), _ .Property(ReR_Property_Name) .CloseDown End With Next oReportInstance Next oReportType Next oReportCategory End Sub
Use Report objects to display parameters for the report instance an end-user selects so she can change parameters. She can print, print preview, or save changes (or select any other option available) in Reports.
56
CHAPTER 1
When the parameter for displays, an end-user can do anything she would normally do in The Raisers Edge without additional code.
Private Sub TreeView1_DblClick() Dim lKey As Long
'This makes sure that they have chosen an ' Instance and not a Type or Category
If Left$(TreeView1.SelectedItem.Key, 8) = "Instance" Then Dim oReportInstance As IBBReportInstance
'This uses the Key from the parent (the Report Type) to specify what Type ' of report this is.
lKey = Int(Mid$(TreeView1.SelectedItem.Parent.Key, 5)) Set oReportInstance = REService.CreateReportInstance(lKey) oReportInstance.Init REApplication.SessionContext
'This uses the Key from the Instance to Load the correct parameter file
oReportInstance.Load Int(Mid$(TreeView1.SelectedItem.Key, 9))
'This displays the Parameter form, at this point the user can do ' anything available in the Raiser's Edge.
oReportInstance.Process ReR_ProcessOption_ShowParameterForm, False, Me
'At this point, we no longer need to access oReportInstance so we ' call CloseDown, it will not be able to close but will close as ' soon as the user closes the parameter form or exits the app.
oReportInstance.CloseDown End If End Sub
ESSENTIALS
57
The LoadCombo method in the CodeTablesServer is a simple way to load a Visual Basic combo box with the entries for a particular code table. The following table shows the parameters for this method and the following Code Sample on page 58 is an example using this method. Parameter oCombo lTableNumber bUseShort Variable Type Object ECodeTableNumbers Boolean Description This is the combo box you want to load. This is an Enum of all of code tables available in The Raisers Edge. Optional: Some code tables have short, long, or both descriptions. Normally, the long description is used. However, if you need to use the short description, set this to True. False is the default. Optional: In The Raisers Edge, you can mark table entries Inactive if they are not likely to be used anymore. If this is set to True (which is the default), only entries that are not flagged as Inactive appear. Optional: If set to True (which is the default), any entries in the combo box are removed before the combo loads.
bActiveOnly
Boolean
bClearCombo
Boolean
58
CHAPTER 1
Code Sample
Option Explicit Private moREService As REServices Private moCodeTablesServer As CCodeTablesServer Private Sub Form_Load()
'This loads the combo with the entries from the Marital Status table
moCodeTablesServer.LoadCombo Combo1, tbnumMaritalStatus, False, True, True End Sub Private Sub Form_Unload(Cancel As Integer) moCodeTablesServer.CloseDown set moCodeTablesServer = Nothing End Sub
If you provide database ID, you can use the GetTableEntryDescription method to get the table entry description. If you provide the table entry description, use the GetTableEntryID to obtain table entry IDs.
Dim lLong As Long Dim sString As String
'lLong will equal the database ID for the entry ' "Single" in the MaritalStatus table. However this number ' will vary from database to database.
lLong = oCodeTablesServer.GetTableEntryID("Single", tbnumMaritalStatus, False)
ESSENTIALS
59
Use the REService objects CreateServiceObject method to create an instance of the object. Call the Init method. Other than providing the usual SessionContext, you can also provide a reference to an existing CodeTablesServer object. This is not required, but if provided, speeds up the initialization process. As with the CodeTablesServer, it is best to place this in your Form_Load, so that it is available throughout the form. The CloseDown method can be placed in the Form_Unload to release all resources when you are finished. To display the maintenance form so that an end-user can to select, add, delete, and sort table entries, call the ShowForm method. Parameter lCodeTableID lFindItemData oFormToCenterOn Variable Type ECodeTableNumbers Long Object Description This is the ID for the particular code table you want to display. Optional: This is the database ID for the table entry you want to have focus when the form displays. Optional: This is a reference to the maintenance form you want to display.
Before you display this form, you can set two properties that influence how the form displays. If the ReadOnly property is set to True, the end-user is not able to use the form to add, delete or sort the table entries. If the ShowInactiveEntries property is set to True, the Inactive table entries are included on the form. The Canceled property returns a boolean telling you if the end-user selects to cancel the form. The SelectedItem property returns the database ID of the table entry the end-user selected. If no item is selected, it returns a 0. If an error occurs, the property returns -1. In The Raisers Edge, if an end-user double-clicks the Label for a table entry field, the maintenance form displays. The following Code Sample on page 60 shows an example of how this functionality might be implemented.
60
CHAPTER 1
Code Sample
Option Explicit Private moCodeTablesServer As CCodeTablesServer Private moTableLookupHandler As CTableLookupHandler Private Sub Form_Load()
'Since the TableLookupHandler uses a CodeTablesServer object, ' we can create it first.
Set moCodeTablesServer = REService.CreateServiceObject (bbsoCodeTablesServer) moCodeTablesServer.Init REApplication.SessionContext Set moTableLookupHandler = REService.CreateServiceObject(bbsoTableLookupServer)
'By setting sFindItemData, if there is already a table entry in ' the combo box, that entry will have focus, when the form is displayed.
moTableLookupHandler.ShowForm tbnumMaritalStatus, _ moCodeTablesServer.GetTableEntryID(Combo1.Text, tbnumMaritalStatus), Me
'If the user cancels the maintenance form then we don't want to change ' what is already in the combo box.
If Not moTableLookupHandler.Canceled Then 'This uses the SelectedItem property to fill in the Combo box. Combo1.Text = moCodeTablesServer.GetTableEntryDescription _ (moTableLookupHandler.SelectedItem, tbnumMaritalStatus, False) End If End Sub
With the TableLookupHandler object, you can add new table entries to the table throughout the program by using the AddEntry method. When this method is called, the new entry is immediately added to the database. The following table shows the parameters for this method and the following Code Sample on page 62 shows an example. Parameter bAddOnTheFly Variable Type Boolean Description This should be set to True so the new table entry immediately adds to the database.
ESSENTIALS
61
Parameter lCodeTableID
Description Optional: This is the code table number for which the table entry belongs. If this is not specified, the current code table set within TableLookupHandler is used. Optional: This is the short description for this table entry. Optional: This is the long description for this table entry. Optional: The AddEntry method calls the specified objects Refresh method.
62
CHAPTER 1
Code Sample
Private Sub Combo1_LostFocus() Dim sMsg as String If Len(Combo1.Text) > 0 Then With oCodeTablesServer
'GetTableEntryID will return a 0 if the current text is not in ' the table.
If .GetTableEntryID(Combo1.Text, tbnumMaritalStatus, False) = 0 Then sMsg = "Do you want to add '" & Combo1.Text & "' to the " & _ .TABLENAME(tbnumMaritalStatus) & " table?" If MsgBox(sMsg, vbQuestion + vbYesNo) = vbYes Then
'This adds the current text to the database and ' Refreshes Combo1. If the AddEntry is unsuccessful ' this will return False.
If Not oTableLookupHandler.AddEntry(True, _ tbnumMaritalStatus, , Combo1.Text, combo1) Then MsgBox "Unable to add entry", vbInformation + vbOKOnly End If Else
'If they don't want to add to the table, then they need to ' pick something that is already on the list.
Combo1.SetFocus End If End If End With End If End Sub
ESSENTIALS
63
First, use the REServices object to create a new instance of the AttributeTypeServer. After you create the object, call the Init method, passing a valid SessionContext. As with the other service objects, we recommend you place this in the Form_Load so these methods are available throughout your form. You must also call the CloseDown method when you finish using the object, preferably in the Form_Unload. Once the object is properly initialized, you can begin to use the object to gather information about attributes. The GetAttributeTypeID method requires 2 parameters. The first parameter is a String which is the attribute Category in The Raisers Edge. The second is an Enum of the different kinds of attributes (for example, Action, Fund, Package). The method returns a Long that is the database ID for this particular attribute. Once you know the attribute ID, you can use that ID to find out more information about the attribute. The opposite of this function is the GetAttributeTypeDescription. If you pass the attribute ID, it returns the attribute category as a String. Using the attribute ID, you can use the GetAttributeDataType method to find out what type of data is required for the Description of a particular attribute. This method returns a number that corresponds to a member of the bbAttributeTypes enum. The GetAttributeDataType method also accepts a boolean variable that is passed by reference, bUniqueRequirement. After the method is called, the variable sets to True if this attribute type allows only one attribute of this type per record. If the data type for the attribute is a table, you may need to get the code table ID for the table. With this, you use the Code Tables Server on page 56 and Table Lookup Handler on page 59 to simplify your coding. When the GetAttributeCodeTableID method passes through the attribute ID, it returns the code table ID for the table.
64
CHAPTER 1
In this code sample, a label with the attribute category (in this case Special Mailing Info) and either a combo box (if the attribute data type is table or boolean) or a text box (for all other data types) is displayed.
Option Explicit Private moCodeTablesServer As CCodeTablesServer Private moAttributeTypeServer As CAttributeTypeServer
Private Sub Form_Load() Dim lAttribute_ID As Long Dim bOnlyOneAllowed As Boolean REService.CreateServiceObject (bbsoCodeTablesServer) Set moCodeTablesServer = New CCodeTablesServer moCodeTablesServer.Init REApplication.SessionContext REService.CreateServiceObject (bbsoAttributeTypeServer) Set moAttributeTypeServer = New CAttributeTypeServer moAttributeTypeServer.Init REApplication.SessionContext With moAttributeTypeServer lAttribute_ID = .GetAttributeTypeID("Special Mailing Info", _ bbAttributeRecordType_CONSTIT_ADDRESS) Label1.Caption = .GetAttributeTypeDescription(lAttribute_ID)
'bOnlyOneAllowed will now be True or False depending on if this ' Attribute is allowed to be present more than once per record
Select Case .GetAttributeDataType(lAttribute_ID, bOnlyOneAllowed)
'If the Data Type is Boolean than we add Yes and No to the Combo box
Case bbAttribute_BOOLEAN Combo1.Visible = True Combo1.AddItem "Yes" Combo1.AddItem "No" Case bbAttribute_TABLEENTRY Combo1.Visible = True
'This uses the CodeTablesServer to the load the combo ' with all of the table entries
moCodeTablesServer.LoadCombo Combo1, _ .GetAttributeCodeTableID(lAttribute_ID), , True Case Else Text1.Visible = True End Select End With End Sub
ESSENTIALS
65
Annotation Form
In The Raisers Edge, end-users can annotate any of the top-level data objects. An annotation is a note that is attached to each record. The end-user can select to have the note display when that record is loaded.
By using the Annotation Form service object, you can easily add this functionality to your custom applications.
66
CHAPTER 1
To display the form, call the ShowAnnotationForm method, passing the data object the annotation will be attached to. If the data object that is passed does not support an Annotation form (for example, it is not a top-level object) a trappable error is raised. You also must pass the form for which you would like the Annotation form to display. This parameter is optional and if nothing passes, the form displays in the center of the screen. The Annotation form displays modally and the end-user has the same options available in The Raisers Edge. After you are finished using any Annotation forms in your project, call the CloseDown method to release all the resources being used by the process. It is very important to remember that if the end-user edits the annotation and clicks Save on the form, the new text is not saved to the database until you call the Save method for the data object. The following is a code sample of this.
Dim REService As REServices Set REService = New REServices REService.Init REApplication.SessionContext Dim oAnnotationForm As CAnnotationForm Set oAnnotationForm = REService.CreateServiceObject(bbsoAnnotationForm) oAnnotationForm.Init REApplication.SessionContext Dim oRecord As CRecord Set oRecord = New CRecord oRecord.Init REApplication.SessionContext oRecord.LoadByField uf_Record_CONSTITUENT_ID, 6 oAnnotationForm.ShowAnnotationForm oRecord, Nothing
'Any changes that the user made on the Annotation Form ' are not saved until this is called.
oRecord.Save
'Clean up.
oRecord.CloseDown Set oRecord = Nothing oAnnotationForm.CloseDown Set oAnnotationForm = Nothing
ESSENTIALS
67
Notepad Form
In The Raisers Edge, three of the top-level data objects, Constituent, Gift, and Event allow the end-user to enter multiple notepads for each record. These notepads all add via a common form. The Notepad form service object provides the opportunity to incorporate this functionality into your programs.
68
CHAPTER 1
Description Optional: This establishes how the VCR buttons on the form function. This is covered in detail in Programming Reference. Optional: If set to True (False is the default), the only options under the File menu are: Save, Save & Close, Properties, and Close. Optional: If this is set to True (False is the default), the user is able to view the Notepad information, but not edit it.
To display the Notepad form, call the ShowForm method. When an end-user enters all of the data on the form and has selects to save the notepad, the notepad information is not actually saved to the database. To save, call the parent records Save method. The following is a code example of how this can be implemented.
Dim REService As REServices Set REService = New REServices REService.Init REApplication.SessionContext Dim oRecord As CRecord Set oRecord = New CRecord oRecord.Init REApplication.SessionContext oRecord.LoadByField uf_Record_CONSTITUENT_ID, 6 Dim oNotepadForm As CNotepadForm Set oNotepadForm = REService.CreateServiceObject(bbsoNotepadForm) oNotepadForm.Init REApplication.SessionContext Set oNotepadForm.NotepadObjects = oRecord.Notepads
'The caption on the form will read "Notepad for Ms. Julie Marie Bach"
oNotepadForm.FormCaption = oRecord.Fields(RECORDS_fld_FULL_NAME)
'In this case, the form will displayed modally, with all File menu ' options and allow the user to edit the Note.
oNotepadForm.ShowForm Nothing
'The user's changes are not added to the database until this called.
oRecord.Save
'Clean Up!
oRecord.CloseDown Set oRecord = Nothing oNotepadForm.CloseDown Set oNotepadForm = Nothing
ESSENTIALS
69
Media Form
In The Raisers Edge, you can use the Media tab of a Constituent or an Event record to store various media files, such as documents, bitmaps (graphics), and video files. With the Media form object, you can incorporate this functionality into your custom applications.
70
CHAPTER 1
To actually display the Media form, you call the ShowForm method. The following table lists the parameters for this method. Once the user completes the form and selects Save, the media item information is not saved to the database yet. This is accomplished by calling the parent records Save method. The following code sample shows how to implement this. Parameter oFormToCenterOn oMoveServer Variable Type Object IBBMoveServer Description This is the object over which Media form displays. Optional: This establishes how the VCR buttons on the form function. This is covered in detail in Programming Reference.
To display the Media form, call the ShowForm method. When an end-user completes the form and selects to save the media item, information is not saved to the database. To save, call the parent records Save method.
ESSENTIALS
71
Code Sample
Dim REService As REServices Set REService = New REServices REService.Init REApplication.SessionContext Dim oRecord As CRecord Set oRecord = New CRecord oRecord.Init REApplication.SessionContext oRecord.LoadByField uf_Record_CONSTITUENT_ID, 6 Dim oMediaForm As CMediaForm Set oMediaForm = REService.CreateServiceObject(bbsoMediaForm) oMediaForm.Init REApplication.SessionContext Set oMediaForm.MediaObjects = oRecord.Media
'If this is not set, then a new Media item will be created.
oMediaForm.MediaObjectID = oRecord.Media.Item(1).Fields(MEDIA_fld_ID)
'The caption on the form will read "Media for Ms. Julie Marie Bach"
oMediaForm.NameForCaption = oRecord.Fields(RECORDS_fld_FULL_NAME)
'The user's changes are not added to the database until this is called.
oRecord.Save
'Clean Up!
oRecord.CloseDown Set oRecord = Nothing oMediaForm.CloseDown Set oMediaForm = Nothing
72
CHAPTER 1
Property Viewer
When using The Raisers Edge, different statistics are maintained behind the scenes. For example, when a Gift record is created, the date and the user name of the end-user creating the record are stored in the database. If an end-user wants to see this information, he can select File, Properties from the menu bar, and the form displays showing the properties for the particular record type. The fields shown on the Property form vary depending on the record type.
With the service object Property Viewer, you can display this form in your applications.
ESSENTIALS
73
5. Display the Properties form by calling the ShowForm method, passing the data object you want to see the properties for. You can also select the form to center the Properties form.
Dim REService As REServices Set REService = New REServices REService.Init REApplication.SessionContext Dim oRecord As CRecord Set oRecord = New CRecord oRecord.Init REApplication.SessionContext oRecord.LoadByField uf_Record_CONSTITUENT_ID, 6 Dim oPropertyViewer As IBBPropertyViewer Set oPropertyViewer = REService.CreateServiceObject(bbsoPropertyViewer) oPropertyViewer.Init REApplication.SessionContext
'This will display the properties for the Preferred Address ' for this Constituent. If the object that you pass doesn't ' support Properties, a trappable error will be raised.
oPropertyViewer.ShowPropertyForm oRecord.PreferredAddress, Me
'Clean Up!
oRecord.CloseDown Set oRecord = Nothing oPropertyViewer.CloseDown Set oPropertyViewer = Nothing
When you create the Property Viewer, you can use it repeatedly to display the Properties form for any type of record. You may want to place the code that creates a new instance of the IBBPropertyViewer in your Form_Load. Then, you can display the Properties form from anywhere on your custom form.
74
CHAPTER 1
Search Screen
The search screen (on the UI, this is called the Open screen) is used extensively throughout The Raisers Edge. For example, it is used any time the user needs to open a specific record or choose a query for a report. With the search screen object, you can incorporate this functionality into your project. The search criteria and filters change automatically based on the type of record, and you can also give your end-users the opportunity to create a new record.
ESSENTIALS
75
Before you display the search screen, you need to tell it what types of records you want to be available in the Find combo frame. Call the AddSearchType method to add at least one search type before displaying the form. If you want to have more than one type of record available, there are two syntax styles supported (see the following code sample). If you have used the object previously, you may want to call the ClearSearchTypes method to clear any existing search types.
'Method 1
oSearchScreen.AddSearchType SEARCH_CAMPAIGN oSearchScreen.AddSearchType SEARCH_FUND oSearchScreen.AddSearchType SEARCH_APPEAL
'Method 2
oSearchScreen.AddSearchType SEARCH_CAMPAIGN,SEARCH_FUND,SEARCH_APPEAL
Before you display the search screen form, you can set optional properties for the form. If you set the AllowAddNew property to True, when the form displays, there is an Add New button present. However, you must write the code that actually creates the new record. If you have added multiple search types, you can also set the DefaultSearchType property to determine the initial search type when the form displays. When you are ready to display the search screen, call the ShowSearchForm method. This displays the form modally and returns False if the end-user clicks Cancel. When the end-user clicks any button that closes the form (Open, Cancel or Add New), you need to find out what he has selected. The SelectedOption property returns the button the end-user selected. The enum bbSearchScreenOption lists options, simplifying your programing. If multiple search types were available, the SelectedSearchType property determines the search type the end-user selected. This returns a value from the enum bbSearchTypes. The SelectedDataObject property returns a reference to the actual data object the end-user selected. The following code shows an example of how you can use the search screen object.
Private Sub UsingTheSearchScreen() Dim REService As REServices Set REService = New REServices REService.Init REApplication.SessionContext Dim oSearchScreen As IBBSearchScreen Set oSearchScreen = REService.CreateServiceObject(bbsoSearchScreen) oSearchScreen.Init REApplication.SessionContext
With oSearchScreen .ClearSearchTypes .AddSearchType SEARCH_CAMPAIGN, SEARCH_FUND, SEARCH_APPEAL .DefaultSearchType = SEARCH_CAMPAIGN .AllowAddNew = True
76
CHAPTER 1
Case SEARCH_CAMPAIGN Dim oCampaign As CCampaign Dim oCampaignForm As CCampaignForm Set oCampaignForm = New CCampaignForm oCampaignForm.Init REApplication.SessionContext If oSearchScreen.SelectedOption = SRCH_FRM_OPEN Then Set oCampaign = oSearchScreen.SelectedDataObject Else Set oCampaign = New CCampaign oCampaign.Init REApplication.SessionContext End If
Set oCampaignForm.CampaignObject = oCampaign oCampaignForm.ShowForm True, Me, True oCampaignForm.CloseDown Set oCampaignForm = Nothing oCampaign.CloseDown Set oCampaign = Nothing Case SEARCH_FUND
'Clean Up!
oSearchScreen.CloseDown Set oSearchScreen = Nothing REService.CloseDown Set REService = Nothing End Sub
Using the search screen object properly in your custom applications not only simplifies your coding, but also provides a common interface for selecting records to your end-users. Other methods and properties available to use with this form are explained in Programming Reference.
ESSENTIALS
77
MiscUI
As the name implies, this object gives you access to miscellaneous forms and functions that are helpful in designing your projects in The Raisers Edge. For example, using the PromptForDataObject method provides your end-user with a simple form for selecting a record.
sDescriptionCaption
String
78
CHAPTER 1
Another helpful method is the Quick Find method. If you provide a search string and the record type you want to search, this method returns True if it was able to find a record with the search string. You can also pass two variables by reference, representing the name and the database ID of the matching record. The following table lists the parameters for this method. Parameter lObjectType Variable Type bbDataObjConstants Description This is an enum of the record types available for searching purposes. Note: Quick Find is designed to be used with constituents, gifts, campaigns, funds, appeals, events, and banks only. This is the string you are searching for. This is passed By Reference and is set to Name for the record that is found. This is passed By Reference and is set to the database ID for the record that is found. Optional: If this is set to True (which is the default) a message box provides the end-user with the option to use the search screen again if the search string is not found.
Two methods that work similarly to provide a way to display the UI form for any data object are the ShowUIForDataObject and the ShowUIForDataObjectO methods. ShowUIForDataObject accepts the database ID of the record you want to display, but ShowUIForDataObjectO is passed a reference to the data object you want to show. The following table shows the other parameters for these two methods. These work the same way for both methods. Parameter lDataObjectType bModal lCallingHwnd Variable Type bbDataObjConstants Boolean Long Description This is an enum of the record types available. If this is set to True the form is displayed modally. Optional: You can pass a handle to a form and the method verifies this form is not already loaded before displaying the new form. Optional: This establishes how the VCR buttons on the form function. This is covered in detail in Programming Reference. Optional: This is the object for which the UI form is centered.
oMoveServer FormToCenterOn
IBBMoveServer Object
ESSENTIALS
79
Code Sample
Dim sName As String Dim lID As Long Dim oRecord As CRecord Set oRecord = New CRecord oRecord.Init REApplication.SessionContext Dim REService As REServices Set REService = New REServices REService.Init REApplication.SessionContext Dim oMiscUI As IBBMiscUI Set oMiscUI = REService.CreateServiceObject(bbsoMiscUI) oMiscUI.Init REApplication.SessionContext
'This will display the UI form for a constituent, if the user has not selected ' a valid record by now then a blank form will be displayed.
oMiscUI.ShowUIForDataObjectO bbdataConstituent, oRecord, True, , , Me
'Clean Up!
oRecord.CloseDown Set oRecord = Nothing REService.CloseDown Set REService = Nothing oMiscUI.CloseDown Set oMiscUI = Nothing
These methods provide a simple way to incorporate powerful functions into your custom applications without writing a lot of new code. There are other methods available through the MiscUI object. These are documented in Programming Reference.
80
CHAPTER 1
'This makes sure that the object passed in has been initialized
If Not oIBBDataObject.Initialized Then oTopObject.Init REApplication.SessionContext End If
'Clean Up!
Set oIBBDataObject = Nothing End Sub
ESSENTIALS
81
You can set some of the properties through your program. For example, DisplayText, UserHidden, and UserRequired can be changed. The Save method saves those changes to the database. The following code shows an example of how you might use these properties to load an array of textboxes and accompanying labels and also allow your end-user to change the Display Text by double-clicking on the label. Notice we use the Using the IBBDataObject Interface on page 80 interface to return a reference to the IBBMetaField interface.
82
CHAPTER 1
Code Sample
Option Explicit Private moAction As CAction Private moMetaField As IBBMetaField Private moDataObject as IBBDataObject Private Sub Form_Load() Dim i As Integer Set moAction = New CAction moAction.Init REApplication.SessionContext
For i = 1 to moMetaField.Count
If moMetaField.FormatDescriptor(i) = fmtAMOUNT Then Text1(i).Text = "$" & Text1(i).Text End If End If Next i End Sub Private Sub Label1_DblClick(Index As Integer) Dim s As String s = InputBox("Enter the new Display Text")
ESSENTIALS
83
'Clean up!
Set moMetaField = Nothing set moDataObject = Nothing moAction.CloseDown Set moAction = Nothing End Sub
Transactions
In The Raisers Edge object model, there are a number of collections that support the use of transactions. Also, any object that implements IBBDataObject supports transactions. With transactions, you can add or remove any number of items from a collection or make changes to the fields in a data object temporarily until you decide to permanently make those changes. At any point in a transaction, you can select to undo the changes you made. Three methods and one property work together to provide the transactions functionality. The BeginCollectionTransaction (or BeginFieldsTransaction for data objects) method signals the collection or object you want to begin a transaction at this point. If later you select to undo the changes, the collection or object returns to exactly the same state it is in at this time. Calling the CommitCollectionTransaction (or CommitFieldsTransaction for data objects) method tells the collection or object you are finished with this transaction, and you want to make any changes made to the collection or object since the BeginCollectionTransaction became permanent. However, this does not make any changes to the database itself; those changes can be made only by calling the parent records Save method. To return the collection or the object to the state it was in when you started the transaction, call the RollbackCollectionTransaction (or RollbackFieldsTransaction for data objects) method. After calling this method, the collection or object is exactly as it was when the transaction began.
84
CHAPTER 1
When using collections, you can use the InTransaction property to check to see if the collection is in the middle of a transaction. This is important because if you call CommitCollectionTransaction or RollbackCollectionTransaction when there is no active transaction, an error is raised. The following code sample is an example of how these can be used.
Dim oRecord As CRecord Set oRecord = New CRecord oRecord.Init REApplication.SessionContext oRecord.LoadByField uf_Record_CONSTITUENT_ID, 3 Dim oIndividuals As CIndividuals Set oIndividuals = oRecord.Relations.Individuals oIndividuals.BeginCollectionTransaction oIndividuals.Remove oIndividuals.Item(1), False
'Any changes to the collection are not saved to the database until now.
oRecord.Save Set oIndividuals = Nothing oRecord.CloseDown Set oRecord = Nothing
Custom Parts
VBA enables you to go a step further. In addition to creating saved parts, you can use VBA to create custom parts for your custom views. Custom parts are similar to saved parts, yet give you unlimited flexibility to create and customize parts of custom screens to fit the particular needs of your organization. While saved parts can incorporate any of the functionality included in Custom View, your custom parts can include any feature you wish to create. Once you create a custom part, you can make it available for all your users to include in custom views they create.
ESSENTIALS
85
Standard custom views are read-only. However, you can use VBA to create custom parts that enable your users to edit constituent information when viewing constituent records in custom view mode.
Public Sub VBACustomView(ByRef oView As IBBCustomView) Set oView = New CHelloWorldPart End Sub
Now create the class module for the custom view. To add the class, in the Project - System box, right-click System and select Insert, Class Module. Paste in the sample code below and name the class module CHelloWorldPart.cls. This sample demonstrates a custom part that will display the message Hello World a text link over a background color you specify. When a user clicks the text link, the custom part calculates the total amount of cash gifts donated by a constituent. You must place your implementation (both design-time and runtime) in the class instantiated in the system macro you created. The design-time implementation can contain information to help custom view users when they use the custom part while creating a custom view. For example, you may want to explain that the calculation of cash gifts can be performed only at runtime. The runtime implementation contains the actual functionality of the custom part when the end user views a constituent record in a custom view that incorporates it.
86
CHAPTER 1
Because Custom View uses HTML as its rendering engine, it is helpful to be familiar with HTML Styles, Dynamic HTML, and Cascading Style Sheets. Knowledge of XML can also be very helpful in creating custom parts.
Option Explicit Implements IBBCustomView Implements IBBCustomViewDesign Implements IBBCustomViewEvents Private moHost As IBBCustomViewHost Private moRec As CRecord Private WithEvents moDIV As HTMLDivElement
' Withevents so we can catch user ' events on our custom part such as ' "click"
' This method is called when an end user opens a constituent record ' and selects this custom view
Private Sub IBBCustomView_Init(ByVal oHTMLContainerElement As MSHTML.IHTMLElement, _ ByVal oHost As BBREAPI7.IBBCustomViewHost, ByVal sParameterString As String)
' This is the data object representing the current constituent record
Set moRec = moHost.CurrentDataObject
' Custom View uses HTML as its rendering engine, so we need to be familiar ' with dynamic HTML/CSS/Styles. This line gives us programmatic access ' to the DIV element that is encapsulating our custom part ' Note: Remember to set a reference to the HTML Object Library so we will have ' early-bound access to HTML elements
Set moDIV = oHTMLContainerElement
' Example: using a style property to change the background color of our part
moDIV.Style.backgroundColor = "aqua"
' Call the routine that will handle rendering our custom part's content
renderCustomPart End Sub Private Sub IBBCustomView_CloseDown()
' Called when end user closes the constituent record, ' good time to do clean-up
Set moDIV = Nothing Set moRec = Nothing Set moHost = Nothing End Sub
ESSENTIALS
87
' This method is invoked only when the end user is interacting with the ' custom view in design-mode (that is, from Configuration)
Private Sub IBBCustomViewDesign_CloseDown()
' Called when end user closes the custom view designer
End Sub Private Property Get IBBCustomViewDesign_Description() As String
' Not implemented in The Raiser's Edge 7.5; reserved for future use
End Property Private Sub IBBCustomViewDesign_Init(ByVal oSC As BBREAPI7.IBBSessionContext)
' This method is invoked when a user opens a custom view in which our custom ' part is sited in design mode.
End Sub
This is the actual description of the custom view that displays for our node in the treeview under the custom parts category in the Additional Objects box in the Custom View Designer
Private Sub IBBCustomViewDesign_RenderDesignTimeContent(ByVal oHTMLContainerElement As _ MSHTML.IHTMLElement, ByVal sParameterString As String) ' This method is used to display information about our custom part in the designer. ' Typically, this will just be some informative text. For example, you can explain that ' in ' in design mode, the part cannot process information and that this can be done in ' runtime ' Note the use of dynamic HTML styles. oHTMLContainerElement.innerHTML = "<SPAN style='font-size:20pt;_ color:purple'>Hello world - I am in design mode</SPAN>" End Sub
88
CHAPTER 1
Private Function IBBCustomViewDesign_ShowParameterForm(sParameterString As String) _ As Boolean ' When a user clicks the "parameter" glyph in the designer, this method is invoked. ' Typically, you would show some sort of front-end form to get criteria from the user. ' Then, you store the criteria as a string (hint: use XML). The custom view engine will ' store this string for you and make it available at runtime (when an end user opens an ' actual constituent record). At that point, you would interrogate the string and apply ' criteria appropriately. End Function Private Sub IBBCustomViewEvents_AfterSave()
Private Function IBBCustomViewEvents_onHTMLEvent(ByVal oHTMLEvent As MSHTML.IHTMLEventObj)_ As Boolean ' This is a general purpose routine that can be used as a catch-all for all HTML events ' that are fired on your custom part. Ideally, you should use direct "WithEvents" ' references to ' HTML elements in your custom part (see declaration of moDIV variable at the top ' of this class), but this can be used as well. End Function
' If bCancel is set to true, then closing of the form by the end user is ' overridden (typically due to some business rule violation)
End Sub
' When multiple custom views are available, the constituent form allows the ' end user to cycle between them. If you set bCancel to true here, you ' will override the view change and the current view will remain active.
End Sub
ESSENTIALS
89
Private Sub renderCustomPart() ' Show our initial interface, which is a link that when clicked displays ' the total amount of cash this constituent has donated ' First inject our "link." We use an HTML SPAN element and set its font to underlined ' blue to give the appearance of a link moDIV.innerHTML = "Hello world " & "<SPAN ID=cashLink style='text-decoration:_ underline;font-size:9pt;color:blue;cursor:hand'>Click me to calculate total cash</SPAN>"
' Now hook up our EventVariable (so we can trap the click)
Set moCashLink = moDIV.all("cashLink") ' At this point, we have a variable (moCashLink) ready to respond to the click event ' See the moCashLink_onclick() routine for the code that will be executed when our link ' is clicked End Sub
90
CHAPTER 1
Private Function moCashLink_onclick() As Boolean ' The user just clicked our link, so update our custom part to display total cash showTotalCash End Function Private Sub showTotalCash()
'For our sample we will display the total amount of cash donations given by
' this constituent Dim oGift As CGift Dim cTotal As Currency cTotal = 0 For Each oGift In moRec.Gifts
moDIV.innerHTML = "<SPAN style='font-size:20pt;font-family:impact;color:green'>_ Total Cash:" & FormatCurrency(cTotal) & "</SPAN>" End Sub
ESSENTIALS
91
To use the new custom part, click Configuration in the Raisers Edge bar and click the Custom View link. In Custom View, custom parts are available from the Fields screen, at the bottom of the Additional Objects box. When you drag our sample into a custom view you are creating, here is what you see in design mode.
Note the descriptive text you invoked using DHTML styles. When the custom part is added to a custom view called test, and that custom view is used to view a constituent record, here is how the part appears. Note the specified aqua background and the HTML SPAN element that gives the appearance of a link.
92
CHAPTER 1
When you click the link, the custom part processes the constituents gifts and returns the total value of cash gifts.
For more information about using the Custom View Designer, see the Custom View Guide.
VBA
Contents _____________________________________________________________________
What is VBA? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94 API vs. VBA . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95 The VBA Environment. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95 Accessing the VBA Environment. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95 Opening the IDE . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96 A Quick Tour . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96 The Project Window . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97 The Code Window. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97 The Properties Window. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97 The Forms Designer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98 A Note About Saving Your Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98 Macros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99 Types of Macros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99 Active Object Macros and The Raisers Edge Code Wizard . . . . . . . . . . . . . . 100 Creating the Active Object Macro. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100 Debugging the Active Object Macro. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101 Running the Active Object Macro. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101 Standard Macros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101 Data Object Macros. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103 Query and Export Macros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105 Macro Samples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106 Setting Defaults . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107 Adding Notepad Records . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108 The Active Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109 The REApplication Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110 The Active Data Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111 Active Process Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119 VBA DLL Macros. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 138 The DLL Tool . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 138 Creating DLL Macros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 138 Debugging DLL Macros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 139 Running DLL Macros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140 Customizing RE:Anywhere for Remote Access Using VBA . . . . . . . . . . . 140 The RE:Anywhere for Remote Access VBA Object Hierarchy . . . . . . . . . . . .141 VBA DLL Macro Permissions and Memory . . . . . . . . . . . . . . . . . . . . . . . .142 Debugging the VBA DLL Macro and Run-Time Errors . . . . . . . . . . . . . . . . . .142 Sharing Code Between RE:Anywhere for Remote Access and The Raisers Edge...144 VBA Code Samples. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145
94
CHAPTER 2
This section introduces the Visual Basic for Applications (VBA) implementation in The Raisers Edge. With the optional module VBA for Advanced Customization, the experienced developer can use macros to leverage the power of The Raisers Edge programmatically from within the program shell. This section builds upon many concepts introduced in other sections of this guide. Be sure to familiarize yourself with the material discussed in the Essentials chapter. Please remember.... We provide programming examples for illustration only, without warranty either expressed or implied, including, but not limited to, the implied warranties of merchantability and/or fitness for a particular purpose. This article assumes that you are familiar with Microsoft Visual Basic and the tools used to create and debug procedures. Blackbaud Customer Support can help explain the functionality of a particular procedure, but they will not modify, or assist you with modifying, these examples to provide additional functionality. If you are interested in learning more about The Raisers Edge optional modules VBA and API, contact our Sales department at [email protected].
What is VBA?
This section provides a general, high-level overview of VBA.
Macros
This section discusses the different types of macros, how they are built, and how they are executed.
What is VBA?
Although this guide assumes you are somewhat familiar with VBA, a short explanation of what Visual Basic for Applications is and how it can be used within The Raiser's Edge helps you get started. For more information about the applications, see the Essentials chapter.
VB A
95
VBA for Advanced Customization is the premier development technology for rapidly customizing and integrating packaged applications. VBA offers a sophisticated set of programming tools based on Microsoft Visual Basic, enabling developers to create solutions meeting specific business needs in much less time than it would take to build them from scratch. You can use VBA to enhance and customize The Raisers Edge to meet your organizations needs in countless ways: Modifying application behavior. Modifying the way The Raisers Edge works to match your organizations business rules and processes. Automating repetitive tasks. Combining sets of common manual tasks into a series of actions that can be executed over and over. Extending application functionality. Adding features to The Raisers Edge that are not available out of the box. Integrating with other applications. Working seamlessly with other VBA-enabled software to integrate a line of business applications. Accessing data. Exchanging data with remote databases and applications and delivering results directly to the desktop.
96
CHAPTER 2
A Quick Tour
The integrated programming environment runs in its own window. Advanced debugging features, property and code editing features (including compile time syntax checking) an enhanced object browser, and code organization and tracking make the VBA IDE a powerful platform from which to develop code.
VB A
97
This section provides an overview of some of the major features of VBA and how they apply to programming The Raisers Edge.
98
CHAPTER 2
To export code: 1. Select the instance you want to export (for example, Active Constituent). 2. From the menu bar, select File, Export File. 3. Name the file and select the location you want to save it to.
To import code: 1. From the menu bar, select File, Import File. 2. Browse to the file you want to import. 3. Select the file and click OK.
VB A
99
Macros
If you perform a task repeatedly in The Raisers Edge, you can automate the task with a macro. A macro is a series of steps stored in a VBA module or VBA DLL, that can be run whenever you need to perform the task. The Raisers Edge provides two methods for creating and editing macros. The first method is accessed through The Raisers Edge shell from the Tools, Visual Basic for Applications menu bar. The second method uses a program separate from The Raisers Edge, RE7VBA.EXE. The process of creating macros through either means is similar, but each method has its own advantages. For our purposes, macros created through The Raisers Edge shell are referred to as VBA Macros. Those created through the RE7VBA.EXE program are referred to as VBA DLL Macros.
Types of Macros
Through VBA you can create four different types of macros.
Standard Macros
With standard macros, you can perform specific functions from the program shell. For example, you could write a macro that automates the printing of a set of End of Day, End of Month, or End of Year reports. You first create the reports for each group, and then create the macro to automate the printing process. Your end-users can select the macro and run all the reports with the click of a button. For more information about standard macros, see Standard Macros on page 101.
100
CHAPTER 2
The code wizard simplifies the process of creating Active Object macros. After selecting a set of events to respond to, the code wizard generates code that includes useful comments and error checking. For example, if you need to enforce a rule that all constituent IDs are 10 digits long you can follow these steps to enforce the business rule. 1. Select Constituent from the record type list. 2. Mark Constituent_BeforeSave. Unmark the other project checkboxes. 3. The code appears in the VBA window.
VB A
101
4. The comments explain the parameters that are available. oRecord is the project record being saved. The record has not been stored in the database, but the oRecord object contains all information defined for the unsaved record. oGLProject is the class that manages projects in The Raisers Edge. When you set oGLProject to oRecord you can use Intellisense. With the bCancel parameter you can cancel the save event. Add the following code after the If statement:
If Len(oConstituent.Fields(RECORDS_fld_CONSTITUENT_ID)) <> 10 Then MsgBox "Constituent ID must be 10 digits" bCancel = True End If
5. Click Save and test the macro. Enter a constituent with a 12 digit constituent ID. A message appears and the save event is canceled. Active objects are divided into three groups: Data objects include all the top level objects in the list. Some examples are ActiveAction, ActiveAppeal, ActiveCampaign, ActiveConstituent, ActiveFund, ActiveGift, ActiveJob, ActiveMembership and ActiveSpecialEvent. These objects have events tied to opening, closing, saving, and deleting a record. Process objects include ActiveBatch, ActiveImport, ActiveMail and ActiveReport. These objects have events for starting and ending a process. In addition, Import and Batch allow you to interact with each record as it is validated. The REApplication object which represents the application. Here, you can write code that occurs when the application is opened or closed. Use the bCancel parameter to cancel the close event of The Raisers Edge. The available events vary based on the type of object selected. For more information about specific objects, see The Active Objects on page 109.
Standard Macros
A standard macro is a block of code you can execute from anywhere in The Raisers Edge shell. Creating macros for your end-users opens the door to a wide range of options that simplifies work and increases efficiency.
102
CHAPTER 2
You can create macros in the System_Macros module in the System project only when you are logged into The Raisers Edge as Supervisor. These macros are available to all users. However, within your macro code you can access the current user name and limit who can actually run the macro. Macros written in the User_Macros module in the User project are available only to the user who created them. Next, create a public subroutine in the System_Macros or User_Macros module. Standard macros cannot have any parameters. Keep in mind that the name of your macro is displayed to your end-users, so it should be clear from the name what the macro does.
VB A
103
104
CHAPTER 2
You can create System_Macros macros only when you are logged into The Raisers Edge as Supervisor. These macros are available to all users. However, within your macro code you can access the current user name and limit who can actually run the macro. Macros written in the User_Macros module in the User project are available only to the user who created them. Next, create a public subroutine in the System_Macros or User_Macros module. Data object macros must have 1 parameter of type IBBDataObject. Without this parameter, the program does not recognize your macro as a Data object macro.
Data Object macros are available from any data object supporting a data object macro. For example, even though a macro is designed for the constituent data object, it is still be available when the end-user looks at the macro list from a gift record. Therefore, you need to make sure that data object being passed in is of the correct type for that particular macro.
VB A
105
106
CHAPTER 2
When a macro is attached to a query or export, the macro is fired for each row in the result set, plus once when the process begins and again when the process ends.
Macro Samples
The following two code samples demonstrate basic macro concepts. The first, Setting Defaults on page 107 is a data object macro that sets the default phone type for a record depending on its key indicator. The second, Adding Notepad Records on page 108, uses a number of VBA objects and features. It is a standard macro that prompts the end-user for a constituent and then displays the Notepad UI to add a notepad record. More samples are included in the RE7\Help\Samples directory on each workstation.
VB A
107
Setting Defaults
This is a simple example to add default information to different record types. It demonstrates two important ideas. First, the Data Object macro is available from several different data entry forms, so it is important to check the data object type before using any of its properties. Second, you have access to the data object and all its child collections and classes, so you can add, edit, and delete information when necessary.
Public Sub SetDefaults(oRecord As IBBDataObject)
'Setting the oRecord = oConstit is not necessary ' but it allows Inteli-sense to function for the object
Set oConstit = oRecord With oConstit
108
CHAPTER 2
Dim ReService As REServices Set ReService = New REServices ReService.Init REApplication.SessionContext Dim oQuickSearch As IBBMiscUI 'Search for the constituent using the Quick Search form Set oQuickSearch = ReService.CreateServiceObject(bbsoMiscUI) oQuickSearch.Init REApplication.SessionContext lID = oQuickSearch.PromptForDataObject(SEARCH_CONSTITUENT, _ "Constituent", "Search for a Constituent; Enter Last Name first")
'If a constituent is found, then create a new notepad for the constituent and open the new notepad form.
If lID > 0 Then Dim oRecord As CRecord Set oRecord = New CRecord oRecord.Init REApplication.SessionContext Dim oNotepadForm As CNotepadForm Set oNotepadForm = New CNotepadForm oNotepadForm.Init REApplication.SessionContext oRecord.Load lID
With oNotepadForm Set .NotepadObjects = oRecord.Notepads .FormCaption = oRecord.Fields(RECORDS_fld_FULL_NAME) .ShowForm Nothing, , True End With
VB A
109
oRecord.Save oRecord.CloseDown Set oRecord = Nothing oNotepadForm.CloseDown Set oNotepadForm = Nothing Else MsgBox "Constituent not found." End If oQuickSearch.CloseDown Set oQuickSearch = Nothing ReService.CloseDown Set ReService = Nothing End Sub
110
CHAPTER 2
UIOpening Event
Using the UIOpening event, you can execute a section of code whenever an end-user begins a Raisers Edge session. For instance, you can present your end-user with a reminder as she begins or connect to another applications database to transfer data between applications. The UIOpening event fires immediately after the shell appears on the screen, but before the Home page displays.
UIClosing Event
Using the UIClosing event, you can execute a section of code just before The Raisers Edge completely closes. It gives you the chance to close down a connection to another applications database or to check and make sure the end-user has correctly completed a specific task. With the UIClosing event, you can also stop the shell from closing by setting the bCancel variable to True. This stops the program from closing and returns the end-user to The Raisers Edge shell.
VB A
111
112
CHAPTER 2
The AfterDelete Event on page 118Occurs after a top level object is deleted Please remember.... The example code provided is abbreviated and does not contain all the necessary error trapping. You need to add a reference to the Microsoft DAO Object Library to access the database objects, and a reference to the Microsoft Outlook Object Library to access the objects used in the email example.
VB A
113
'Before we save the gift record, ' 1) Determine if this will make the person a Major Donor ' by checking the amount given ' 2) See if we need to mark the constituent record with ' the Constituent Code, Major Donor ' 3) Check the MajorDonor.Mdb to see if the Director of ' Development needs to be notified ' ' The module level variables, mbAddMajorDonorCC and mbNotifyDOD ' will be used in the After_Save event
Dim bHasMajorDonorCC As Boolean Dim oGift As CGift Dim oConstituentCode As CConstituentCode
'This is not necessary, but allows you to use the Intellisense ' feature, which you don't get with the late bound oRecord.
Set oGift = oRecord If oGift.Fields(GIFT_fld_Amount) > 1000 Then
'Look through the person's constituent codes to see if they are ' marked as a major donor
For Each oConstituentCode In oGift.Constituent.ConstituentCodes If oConstituentCode.Fields(CONSTITUENT_CODE_fld_CODE) = _ "Major Donor" Then bHasMajorDonorCC = True Exit For End If Next oConstituentCode
114
CHAPTER 2
'We'll only want to add the Major Donor constituent ' code if it's not already there
mbAddMajorDonorCC = (Not bHasMajorDonorCC)
'Check the MajorDonor database to see if this person is already there, ' if they are not, then notify the DoD
Dim oMDB As Database Dim oMajorDonor As Recordset
Set oMDB = OpenDatabase("d:\MajorDonors.mdb") Set oMajorDonor = oMDB.OpenRecordset("Constituents") With oMajorDonor .Index = "PrimaryKey" .Seek "=", lDatabaseID
'Clean up
oMajorDonor.Close Set oMajorDonor = Nothing oMDB.Close Set oMDB = Nothing Else mbNotifyDOD = False mbAddMajorDonorCC = False End If Set oGift = Nothing Set oConstituentCode = Nothing End Sub
VB A
115
In this code sample, we use the AfterSave event to notify the Director of Development of any gifts over $1000.00. This code also updates MajorDonors.Mdb and adds the Major Donor constituent code to the constituent record. The code below is placed in the AfterSave() event of the ActiveGift object.
Public Sub Gift_AfterSave(oRecord As Object)
gifts over $1,000 will designate a constituent as a Major Donor. the person is a Major Donor, then 1) We need to add the donor to our MajorDonors.Mdb 2) If it's a new major donor, we need to notify the Director of Development 3) We need to add the Constituent Code of Major Donor if it's not already there
If
'The module level variable mbNotifyDOD was set in the Before_Save Event ' Since we only notify the DoD for new Major Donors, we know we need to ' add the donor to the MajorDonors.Mdb
Dim oGift As CGift Set oGift = oRecord If mbNotifyDOD then
116
CHAPTER 2
.Body = oGift.Constituent.Fields(RECORDS_fld_FULL_NAME) & " (" & _ oGift.Constituent.Fields(RECORDS_fld_CONSTITUENT_ID) & _ ") has given a gift of " & _ Format$(oGift.Fields(GIFT_fld_Amount), "$###,##0.00") & _ " to the " & _ oGift.Fields(GIFT_fld_Fund) & ". Please call to thank them." .Send 'Sends the email without user intervention End With Set oMailItem = Nothing Set oOutlook = Nothing
With oMajorDonor .AddNew .Fields("Donor_ID") = oGift.Constituent.Fields(RECORDS_fld_ID) .Fields("Donor_Name") = oGift.Constituent.Fields(RECORDS_fld_FULL_NAME) .Fields("Total_Amount") = oGift.Fields(GIFT_fld_Amount) .Update End With
'Clean up
oMajorDonor.Close Set oMajorDonor = Nothing
VB A
117
'Add the constituent code. 'The module level variable mbAddMajorDonorCC was set in the Before_Save Event
If mbAddMajorDonorCC Then Dim oConstituentCode As CConstituentCode Set oConstituentCode = oGift.Constituent.ConstituentCodes.Add oConstituentCode.Fields(CONSTITUENT_CODE_fld_CODE) = "Major Donor" oGift.Constituent.Save End If Set oConstituentCode = Nothing Set oGift = Nothing End Sub
118
CHAPTER 2
In this example, we display a message indicating the constituent to be deleted is a recent donor. Place this code sample in the BeforeDelete event of the ActiveConstituent record.
Private Sub Constituent_BeforeDelete(oRecord As Object, bCancel As Boolean) Dim lResult As Long Dim oMDB As Database Dim oMajorDonor As Recordset
If Not .NoMatch Then lResult = MsgBox("This constituent is a major donor. _ Are you sure you want to delete this record?", vbYesNo) If lResult = vbNo then bCancel = True End if End With oMajorDonor.Close Set oMajorDonor = Nothing oMDB.Close Set oMDB = Nothing
End Sub
VB A
119
This example opens the Microsoft Access database called MajorDonors.Mdb and deletes a record if it exists. This code should be placed in the AfterDelete event of the ActiveConstituent object.
Private Sub RaisersEdgeRecord_AfterDelete(lDatabaseID As Long, sImportID As String) Dim oMDB As Database Dim oMajorDonor As Recordset Set oMDB = OpenDatabase("d:\MajorDonors.mdb") Set oMajorDonor = oMDB.OpenRecordset("Constituents") With oMajorDonor .Index = "PrimaryKey" .Seek "=", lDatabaseID If Not .NoMatch Then .Delete End With oMajorDonor.Close Set oMajorDonor = Nothing oMDB.Close Set oMDB = Nothing End Sub
120
CHAPTER 2
In the following sections, we use the Batch events to maintain a batch history database. It keeps track of each gift batch posted; including who posted it, the total gift amounts posted, and the total gift amounts that were exceptions. The BeforePost Event The BeforePost event fires once, just before the process to commit the records to the database begins. This way, you can set up any processes you want in place for processing the batch. For example, if you are transferring data from the batch to an external database, you can use this event to connect to that database.
VB A
121
The event passes sBatchName. This is a string that represents the batch number of the batch in progress. It also passes lBatchType, from the enum bbVBABatchTypes, which tells you if this is a constituent, gift, or time sheet batch. The last parameter is bCancel that, if set to True, discontinues the entire committing process.
Private Sub BatchVBARecord_BeforePost(ByVal sBatchName As String, _ ByVal lBatchType As bbVBABatchTypes, _ bCancel As Variant) On Error GoTo BatchVBARecord_BeforePost_Error
'moDB is a module level Database variable ' (requires a reference to a Microsoft DAO Library)
Set moDB = OpenDatabase("d:\BatchHistory.mdb")
'Don't let them run batch unless they can access the history file
mbMaintainHistory = False MsgBox "Error opening BatchHistory.MDB. Please see your system administrator"
The BeforePostRecord Event The BeforePostRecord event fires just before each row in the batch is committed to the database. This way, you can access the underlying data object after it has been created, but before it adds to the database. You can pass some of this information to a separate database or you can verify that specific Business Rules for your organization have been met. If the rules are not met, you can stop this record from being committed to the database. The event passes sBatchName. This is a string that represents the batch number of the batch in progress. It also passes lBatchType, from the enum bbVBABatchTypes, which tells you if this is a constituent, gift, or time sheet batch. The next parameter is oDataObject. This variant is either of type CRecord, CGift, or CTimeSheet, depending on the batch type. You can access the entire object model for that object type at this time. You can change information, use VB to make a calculation and add that information to the record, or check to make sure all the information is entered according to your organizations Business Rules. If you do not want this particular record to add to the database, you can set bCancel equal to True. The record does not add to the database and lists as an exception on the Exception Report. You can even set sExceptionMessage equal to the reason for the exception so it prints on the Exception Report and shows your end-user what is wrong with the record. Any time a record is flagged as an exception, The HandleException Event on page 122 fires.
122
CHAPTER 2
To continue with the previous example, here we use the BeforePostRecord event to total the dollar amount of cash gifts posted and other gifts posted.
Private Sub Batch_BeforePostRecord(ByVal sBatchName As String, _ ByVal lBatchType As bbVBABatchTypes, _ oDataObject As Variant, _ bCancel As Variant, _ sExceptionMessage As Variant) Dim oGift As CGift
'Since this is a Gift batch, oDataObject is returned as a CGift object ' Creating a CGift object is not necessary, but allows the Intelisense ' feature of VBA to recognize the object.
Set oGift = oDataObject With oGift If .Fields(GIFT_fld_Type) = "Cash" Then mcurPostedCash = mcurPostedCash + .Fields(GIFT_fld_Amount) Else mcurPostedOther = mcurPostedOther + .Fields(GIFT_fld_Amount) End If End With Set oGift = Nothing End If End Sub
The HandleException Event The HandleException event fires whenever a record in the batch is flagged as an exception. This way, you can change the record to correct the cause of the exception and try again. If you transferred information for each record to an external database in the BeforeCommitRecord event, you may want to remove that information using this event. The event passes sBatchName. This is a string that represents the batch number of the batch in progress. It also passes lExceptionCode, from the enum bbVBABatchExceptionCodes, which tells you the reason for the exception. The next parameter is oDataObject. The variant is either the type CRecord, CGift, or CTimeSheet, depending on the batch type. You can access the entire object model for that object type at this time. The last parameter is bTryAgain. If set to True, The Raisers Edge attempts to commit the updated record to the database again. It is important to make sure the cause of the exception has been corrected so there is no possibility of an end-user getting caught in a loop.
VB A
123
Using the HandleException event, you can total the dollar amount of cash gifts and other gifts that are exceptions.
Private Sub BatchVBARecord_HandleException(ByVal sBatchName As String, _ ByVal lExceptionCode As _ bbVBABatchExceptionCodes, _ oDataObject As Variant, _ bTryAgain As Variant)
Const DEFAULT_FUND = 99 -------------------------------------------------------------------------------'mbMaintainHistory is a module level boolean variable If mbMaintainHistory Then 'Since this is a Gift batch, oDataObject is returned as a CGift object ' ' Creating a CGift object is not necessary, but allows the Intelisense feature of VBA to recognize the object.
With oGift
'We can check the exception codes and try again if we can ' correct the problem
If lExceptionCode = bbBex_FundNotFound AND _ .Fields(GIFT_fld_Fund) <> DEFAULT_FUND Then 'In this case, we have a default fund set up to catch gifts ' ' where the fund was deleted between the time the batch was created and when it was posted
'Set bTryAgain and the system will again try to post the gift bTryAgain = True
124
CHAPTER 2
Else 'Sum the exception amounts If .Fields(GIFT_fld_Type) = "Cash" Then mcurExceptionCash = mcurExceptionCash + _ .Fields(GIFT_fld_Amount) Else mcurExceptionOther = mcurExceptionOther + _ .Fields(GIFT_fld_Amount) End If End If End With Set oGift = Nothing
End If
End Sub
The AfterPost Event The AfterPost event fires once after the entire committing process is complete. This way, you can clean up any objects or connections to other databases you use while the batch processes. You may also want to use this event to begin the acknowledgement process for gifts or to prepare a welcome letter for new constituents. The event passes sBatchName. This is a string that represents the batch number of the batch in progress. The event also passes two longs; lNumRecsPosted and lNumExceptions. These represent the number of records that add successfully and the number of records that are exceptions, respectively.
VB A
125
To complete our example, you can add a row to the BatchHistory table (including the current date and batch name), the posted and exception totals from the BeforePostRecord and HandleException events, and the current User ID.
Private Sub BatchVBARecord_AfterPost(ByVal sBatchName As String, _ ByVal lNumRecsPosted As Long, _ ByVal lNumExceptions As Long) Dim oBatch As Recordset If mbMaintainHistory Then
'Update the BatchHistory database with the details of the current batch
With moDB Set oBatch = .OpenRecordset("BatchHistory") With oBatch .AddNew .Fields("RunDate") = Now() .Fields("BatchName") = sBatchName .Fields("PostedCash") = mcurPostedCash .Fields("PostedOther") = mcurPostedOther .Fields("ExceptionCash") = mcurExceptionCash .Fields("ExceptionOther") = mcurExceptionOther .Fields("UserID") = REApplication.SessionContext.CurrentUserID .Update End With oBatch.Close Set oBatch = Nothing End With End If moDB.Close Set moDB = Nothing End Sub
126
CHAPTER 2
In the following example, we create two text files. One stores the name and social security number of imported records; the other accepts records that are exceptions. The BeforeImport Event The BeforeImport event fires once, just before the importing of the records to the database begins. You can start any process you want to have in place while importing. For example, if you are also importing some of this data into a separate database, you can use this event to connect to that database. The event passes sImportName. This is a string that represents the name of the import in progress. It also passes lImportType, from the enum bbVBAImportTypes, which tells you the type of the import, such as Constituent or Constituent Phone. The last parameter is bCancel which, if set to True, discontinues the importing process.
Public Sub ImportVBARecord_BeforeImport(ByVal sImportName As String, _ ByVal lImportType As bbVBAImportTypes, _ ByRef bcancel As Variant)
'For constituent import files we need to provide a list of constituents ' imported and those that were exceptions. This routine opens the two text files ' that will hold then name and SSN of the records processed.
'mlExcFile and mlImpFile are module level variables ' that hold the two file handles
mlExcFile = FreeFile Open "C:\Exc_" & sImportName & ".IMP" For Append As mlExcFile mlImpFile = FreeFile Open "C:\Imp_" & sImportName & ".IMP" For Append As mlImpFile End If End If End Sub
The BeforeImportRecord Event The BeforeImportRecord event fires just before each row in an import file is imported into the database. You can access the underlying data object after it is created, but before it is added to the database. You may use this to send some information to a separate database. You can use it to verify that some specific Business Rules for your organization have been met. If the specific rules have not been met, you can stop the record from being imported into the database. The event passes sImportName. This is a string that represents the name of the import in progress. It also passes ImportType, from the enum bbVBAImportTypes, which tells you the type of the import, such as Constituent or Constituent Phone. The next parameter is oDataObject; the type of this variant depends on the import type. You can fully access the entire object model for that object type at this time. You can change information, use VBA to make a calculation and add that information to the record, or check to make sure all the information is entered following your organizations Business Rules.
VB A
127
If you do not want this particular record to add to the database, you can set bCancel equal to True. The record is not added to the database and lists as an exception on the Exception Report. You can even set sExceptionMessage equal to reason for the exception so it prints on the Exception Report and informs your end-user what is wrong with this record. Any time a record is flagged as an exception, the The HandleException Event (Active Import) on page 127 event fires. Here, we write the Name and Social Security Number of the imported record to a text file.
Public Sub ImportVBARecord_BeforeImportRecord(ByVal sImportName As String, ByVal lImportType As bbVBAImportTypes, _ oDataObject As Variant, _ ByRef bCancel As Variant, _ ByRef sExceptionMessage As Variant) Dim oRec as CRecord 'mlImpFile is a module level variable; a file handle ' for the imported records file
If mlImpFile > 0 Then Set oRec = oDataObject Print #mlImpFile, oRec.Fields(RECORDS_fld_FULL_NAME) & "," & _ oRec.Fields(RECORDS_fld_SOCIAL_SECURITY_NO) Set oRec = Nothing End If End Sub
The HandleException Event (Active Import) The HandleException event fires whenever a record in an import is flagged as an exception. You change the record to correct the cause of the exception and try again. If you transferred information for each record to an external database in the BeforeImportRecord event, you may want to remove that information using this event. The event passes sImportName. This is a string that represents the name of the import in progress. It also passes lExceptionCode, from the enum bbVBAImportExceptionCodes, which tells you the reason for the exception. The next parameter is oDataObject, the data type of this variant depends on the type of import, such as Constituent or Constituent Phone. You can fully access the entire object model for that object type at this time. The last parameter is bTryAgain. If set to True, The Raisers Edge attempts to import the updated record to the database again. It is important to make sure the cause of the exception has been fixed, so there is no possibility of your end-user getting caught in a loop.
128
CHAPTER 2
Here, we write the Name and Social Security Number of the exception record to a text file.
Public Sub ImportVBARecord_HandleException(ByVal sImportName As String, _ ByVal lExceptionCode As bbVBAImportExceptionCodes, _ oDataObject As Variant, ByRef bTryAgain As Variant) Dim oRec As CRecord 'mlExcFile is a module level variable; a file handle for the exception file If mlExcFile > 0 Then Set oRec = oDataObject Print #mlExcFile, oRec.Fields(RECORDS_fld_FULL_NAME) & "," & _ oRec.Fields(RECORDS_fld_SOCIAL_SECURITY_NO) Set oRec = Nothing End If
End Sub
The AfterImport Event The AfterImport event fires once after the entire importing process is complete. You can clean up any objects or connections to other databases you use while the import processes. You may want to use this event to start a process to send a letter to new constituents who have been imported or send an email reminding the staff that phone numbers have been updated. The event passes sImportName. This is a string that represents the name of the import in progress. The event also passes two longs; lNumRecsImported and lNumExceptions. These represent the number of records that add successfully and the number of records that are exceptions, respectively.
VB A
129
For this example, we close the text files and inform the end-user that the import files have been created.
Public Sub ImportVBARecord_AfterImport(ByVal sImportName As String, _ ByVal lNumRecsImported As Long, _ ByVal lNumExceptions As Long) If mlExcFile > 0 Then Close mlExcFile Close mlImpFile
130
CHAPTER 2
In this example, we allow end-users to run the Gift Detail and Summary and Gift Entry Validation reports only after 5:00 PM (when we are sure gift entry has been completed for the day).
Private Sub ReportsVBARecord_BeforeProcess(ByVal lReportType As Long, _ ByVal sParamName As String, _ ByVal lAction As Long, _ bCancel As Boolean, ByVal Reserved As Variant)
Case ReR_GiftDetailandSummaryReport, ReR_GiftEntryValidation If Time() < "5:00 PM" Then MsgBox "This report can only be run after 5:00 pm." bcancel = True End If
End If
End Sub
The AfterProcess Event Reports and Mail support the same VBA events. The AfterProcess event fires after the mail or report function finishes processing and displays its output. If the function does not have output and No Records Meet Criteria displays when processing is done, the AfterProcess event does not fire. The AfterProcess event passes four parameters. The first is lReportType/lMailType, a long that can be used with the enum EReR_ReportTypes to determine the type of report or mail function being run (such as the Gift Entry Validation report or Labels mail task). The next parameter is sParamName. This is a string that represents the name of the actual parameter file used for the report or mail function. Next is lAction, a long that, when used with the enum EReR_ProcessOptions tells you if the end-user is printing, print previewing, or exporting the report or mail function. The last parameter is sExportFileName, which if the Action performed was exporting, contains the full path for the export file name.
VB A
131
In this example, after exporting Todays Reports, a copy is sent by email to the Director of Development using the Microsoft Outlook Object Library.
Private Sub ReportsVBARecord_AfterProcess(ByVal ByVal ByVal ByVal ByVal On Error GoTo ehSendReport lReportType As Long, _ sParamName As String, _ lAction As Long, _ sExportInfo As String, _ Reserved As Variant)
'You will need to set a reference to Microsoft Outlook 9.x Object Library
Dim oOutlook As Outlook.Application 'This starts outlook. Dim oMailItem As MailItem
132
CHAPTER 2
VBA Support in Query Querys VBA support attaches a specially defined VBA macro to a query. When various operations are performed using the query (such as Run, Refresh, or Export), this special macro is called. By selecting Tools, Run Macro from the query menu bar, the end-user can run the macro associated with the active query without having to perform another operation in query. Please remember.... Due to the fact that query results are displayed as needed on the Results tab, for the macro to run completely through all query results, the end-user needs to scroll to the end of the query or run the macro by selecting Tools, Run Macro from the query menu bar. Using VBA, you can add customized user fields to a querys output. This feature works in concert with creating a macro. On the Output tab, the end-user can select a special field, which applies to output only. This is called a VBA user field. You can write a macro to populate this field with data from VBA. The data shows up anywhere in The Raisers Edge that query results are displayed and processed (such as Query, Export, and the search screen) Remember, it is important to note this field cannot be used as a filter. An example of this feature is to add custom fields to The Raisers Edge search screen to pull data from external databases. Also, when exporting data directly from Query to Crystal Decisions Crystal Reports, VBA provides the ability to add fields if you have performed calculations in VBA. This way, the data gets to Crystal in a useful format. Creating a Query Macro To create a query macro, create a Public Sub in either the User_Macros or System_Macros module. This subroutine can accept only one parameter that is an IBBQueryRow object (for more information, see the Programmers Reference in the help file). When the macro is run, either by running the query or accessing the macro directly from Tools, Run Macro on the query menu bar, it is called (Number of Rows in the query) two times. The first time it is called, it returns a .BOF = True (Begin Of File) condition. This allows you to perform any initialization operations. The event is then called for each row in the query. It is important to remember nothing you do here affects membership in the query. That is determined by the criteria of the query. The macro is called the last time with a .EOF = True (End Of File) condition so you can perform any shutdown or cleanup operations. The following code sample is an example of this.
'This sample requires a reference to Excel Object Library.
Option Explicit Private moExcel As Excel.Application Private moWorksheet As Excel.Worksheet Public Sub SendQueryResultsToExcel(oRow As IBBQueryRow)
If oRow.BOF Then
'Opens Excel
Set moExcel = CreateObject("Excel.Application") moExcel.Visible = True
VB A
133
'Fills the first row with the field names from the query
Dim lHeads As Long For lHeads = 1 To oRow.FieldCount - 1 moWorksheet.Cells(1, lHeads) = oRow.FieldName(lHeads) Next lHeads
'Clean up
Set moWorksheet = Nothing Set moExcel = Nothing Else
Refer to the IBBQueryRow (for more information, see the Programmers Reference in the help file) object for additional properties you may find helpful when you are moving through the results of the query. VBA User Field (Query) One VBA user field is included in the field list per query type. If this field is selected as an output field, it is a read/write field you can manipulate using the VBA macro. This is the only field in the query results the end-user has write access to during query processing. Query does not put anything in this field. To populate this field, a VBA macro must be used. Remember the VBA user field lists in the Available Fields box only once per query type. However, it can be selected more than one time and follows standard naming conventions. This way, more than one user defined field can be specified per query. Code Sample This macro illustrates an example of using BOF (Begin of File), EOF (End of File), and the VBA user field. The one parameter for the macro must be of type IBBQueryRow (for more information, see the Programmers Reference in the help file). For a constituent query with three output fields selected (in the following order): Constituent Information 1. Constituent Information, Name 2. Constituent Information, Age
134
CHAPTER 2
'This code will be processed once (at the beginning of ' the query result set).
MsgBox "Begin processing" ElseIf oQueryRow.EOF Then
'This code will be processed once (at the end of the ' query result set).
MsgBox "End processing" Else
When the query runs: 1. The message Begin processing with an OK button displays. When the end-user clicks OK, the query continues. 2. For each row, if the second query field (Age) is greater than 55, the third query field (VBA user field) is set to Senior citizen discount. 3. The message End processing with an OK button displays.
Remember when viewing query results on the Results tab, the EOF (End of File) does not fire unless the end-user scrolls to the end of the result set. EOF fires when the end-user reaches the end of the result set, or if the query is closed. VBA Support in Export Like Query, Exports VBA support attaches a specially designed VBA macro to an export. When the export is run, this macro is called. You can add customized user fields to your export using VBA. This is accomplished by attaching a macro to the export. On the Output tab of the export record, the end-user can select a special field that applies to output only. This is called a VBA user field. You can write a macro to populate this VBA user field with data from another database or perform a calculation that is contained in the export file. This feature can only be used with flat style exports. For more information about flat style exports, see the Query & Export Guide.
VB A
135
Creating an Export Macro To create an export macro, create a Public Sub in either the User_Macros or System_Macros module. This subroutine can only accept 1 parameter that is an IBBExportRow (for more information, see the Programmers Reference in the help file) object. When the export is run, the macro is called (Number of Rows in the export) two times. The first time it is called, the macro returns a .BOF = True (Begin Of File) condition. This allows you to perform any initialization operations. The event is then called for each row in the export. The macro is called the last time with a .EOF = True (End Of File) condition so you can perform any shutdown or cleanup operations. The following code sample is an example of this.
Option Explicit Private Private Private Private Private moExcel As Excel.Application moWorksheet As Excel.Worksheet msLetters() As String mlNumLetters() As Long mlMaxLetters As Long
If oRow.BOF Then
'This clears the module-level variables so we ' can run this macro multiple times
Erase msLetters() Erase msLetters()
mlMaxLetters = 0
'Opens Excel
Set moExcel = CreateObject("Excel.Application") moExcel.Visible = True
136
CHAPTER 2
'Fill in Worksheet
For lCount = 1 To mlMaxLetters moWorksheet.Cells(lCount + 1, 1) = msLetters(lCount) moWorksheet.Cells(lCount + 1, 2) = mlNumLetters(lCount) Next lCount
'Clean up
Set moWorksheet = Nothing Set moExcel = Nothing
'We found a match so we increment the counter for that letter 'and exit the sub and go to the next record in the export.
mlNumLetters(lCount) = mlNumLetters(lCount) + 1 Exit Sub End If Next lCount
ReDim Preserve msLetters(1 To mlMaxLetters) ReDim Preserve mlNumLetters(1 To mlMaxLetters) msLetters(mlMaxLetters) = oRow.Field(2) mlNumLetters(mlMaxLetters) = 1 End If End Sub
Refer to the IBBExportRow object (for more information, see the Programmers Reference in the help file) for additional properties you may find helpful when moving through the results of the export. VBA User Field (Export) One VBA user field is included in the field list per group defined for the export type. If this field is selected as an output field, it is a read/write field you can manipulate using the VBA macro. This is the only field in the export file the end-user has write access to during export processing. Export does not put anything in this field. To populate this field, a VBA Macro must be used.
VB A
137
Remember the VBA user field can be selected more than one time per group and follows standard naming conventions. This way, more than one user defined field can be specified per group. Code Sample The following macro shows an example of using BOF, EOF, and the VBA user field. Remember all properties of the IBBExportRow are defined in the Programmers Reference in the help file. For an export with three output fields selected (in the following order): Biographical 1. Constituent name 2. Age 3. VBA User Field
Public Sub UserExportMacro(oExportRow As IBBExportRow) Const NAME_FIELD = 1 Const AGE_FIELD = 2 Const VBA_FIELD = 3 If oExportRow.BOF Then
'This code will be processed once (at the beginning of the file). ' Here you would connect to an external database or run setup code
MsgBox "Begin processing" ElseIf oExportRow.EOF Then
'This code will be processed once (at the end of the file). ' Here you would disconnect/close external databases if needed or run ' end of processing code
MsgBox "End processing" Else
When the export runs: 1. The message Begin processing with an OK button displays. When the end-user clicks OK, the export continues. 2. For each row, if the second export field (Age) is greater than 55, the third export field (VBA user field) is set to Senior citizen discount. 3. The message End processing with an OK button displays.
138
CHAPTER 2
Create a new projectLaunches VBA and creates a new project. This is the easiest way to create RE7 DLLs. Open a projectOpen an existing project for editing. Show VBALaunches VBA without creating a new project. ExitCloses the program.
VB A
139
After you click Create a new project, the VBA IDE loads with a project shell. The shell includes one module, MacroClass1, containing variable declarations and initialization code. Do not make changes to this code. You can begin writing macros at the bottom of the MacroClass1 module.
140
CHAPTER 2
VB A
141
The RE:Anywhere application object implements the REWebApplicationMT interface, which exposes the following methods. SessionContext As IBBSessionContext: the SessionContext is your hook into API, and can be used to initialize other API objects. For more information, see the API Essentials Guide. WebContext As IBBWebContext: an interface that provides access to certain functionality specific to RE:Anywhere. The IBBWebContext interface exposes the following methods. VBAContext As IBBVBAContext: an interface that provides access to certain functionality provided to help you customize RE:Anywhere using VBA. The IBBVBAContext interface exposes the following properties and methods. AppendMessage(sMessage As String, [sDelimiter As String]): Using AppendMessage you can pass text to the Web context that displays when the current process is complete. The content of the message clears at the beginning of any VBA-enabled process and accumulates over calls made to AppendMessage during the process. When the process ends, the contents of the message display to the RE:Anywhere user in a message box. You can include in the message text basic HTML tags such as bold, italics, or a simple HTML table if you want. The message box sizes dynamically to accommodate the HTML it receives (however, only to a certain point). GetMessage As String: returns the current contents of the message. ClearMessage: clears the contents of the message. Error As IBBVBAError: information about any run-time error encountered during the execution of your VBA code. This is used by RE:Anywhere to handle run-time errors robustly.
142
CHAPTER 2
The IBBVBAError interface exposes the following properties and methods. Number As Long: the error number encountered by VBA at run-time. Description As String: the description of the error encountered by VBA at run-time. Source As String: a string identifying the source of the error encountered by VBA at run-time. Clear: allows the contents of the VBA error object to be cleared. Please note... The IBBVBAError interface exists so that information about run-time errors can communicate to RE:Anywhere. It is most likely you are not going to use this in your VBA code.
VB A
143
To debug an RE:Anywhere VBA DLL macro, log on to the RE:Anywhere Web server with administrative rights. Then, open the code using the RE:Anywhere VBA DLL Tool, on the server that hosts RE:Anywhere. Open the project properties screen by selecting Tools, [Project Name] Properties from the menu bar. On the Debugging tab of that dialog, select Start browser with URL and enter the URL of the login page of RE:Anywhere (using the host machines name to identify it in the URL). For example, if RE:Anywhere is hosted on a Web server named CLIENTWEB1, the URL used on the Debugging tab should be http://CLIENTWEB1/REWeb70/login.asp. You should access the Web application locally. You do not want the execution to pass through any proxy server or firewall. Set breakpoints as necessary and select Run, Start Project from the menu bar to launch the debugger. Please remember... RE:Anywhere needs to be able to read the interfaces of a VBA DLL from its file in order to access the DLL in debug mode. This means that any time a new interface or public object is added to the project, the DLL must be recompiled before that interface or object can be debugged. Debugging is interfered if you recompile a project to a new filename without changing the project name and class name and if you do not unregister the old DLL. For example, you are working on a VBA DLL macro project named WebTest, the name of your macro class is CWebTest1, and you compile it to C:\WebTest.dll. When you launch the debugger, RE:Anywhere looks up the ProgID WebTest.CWebTest1 in the registry to find out that it is stored in C:\WebTest.dll. If the project is rebuilt to C:\WebTestNew.dll without unregistering the old C:\WebTest.dll, RE:Anywhere most likely still finds the old C:\WebTest.dll (which is still associated with the WebTest.CWebTest1 ProgID in the registry). If your breakpoints are not being hit when debugging, this is probably the cause. If a bug in your VBA code results in a run-time error, a message box displays with a description of the error identifying the VBA DLL that raised it. If the event that raised the error has a parameter allowing you to cancel the event (for example, BeforeSave), it is not cancelled. However, code in downstream events is not executed. For example, if a run-time error occurs in the IBBWebConstituent_BeforeSave code, any change made to the constituent (either by the user or by the code up to the point of failure) saves to the database. However, if there is also code in IBBWebConstituent_AfterSave, it does not execute. An exception to this is the code in IBBWebActiveBatch_BeforePostRecord and IBBWebActiveBatch_HandleException. If there is a run-time error in one of these two methods, their code executes up to the point where the error occurs (for as many times as there are records or exceptions in the batch). The cancel flag in IBBWebActiveBatch_BeforePostRecord does not take effect even if set to True before the run-time error occurs. If a run-time error occurs in either of these methods, IBBWebActiveBatch_AfterPost does not execute. Please note... Blackbaud strongly encourages you to write robust VBA code that handles errors correctly. Otherwise, memory leaks could result from unreleased resources. All code examples should include proper error handling. Errors can be reported back to the RE:Anywhere UI using the IBBVBAContext.AppendMessage method. If you have questions, send an email to [email protected] or call 1-800-468-8996 for support assistance.
144
CHAPTER 2
Sharing Code Between RE:Anywhere for Remote Access and The Raisers Edge
Since the implementation of VBA DLL macros differs between The Raisers Edge and RE:Anywhere, you may think you have to write all your code twiceonce in the Active objects of VBA and once in the IBBWebActive methods of the RE:Anywhere DLL. If you want to code the same business logic into The Raisers Edge and RE:Anywhere, you can use any third-party COM-enabled development tool (for example, Visual Basic) to write a COM object that implements your business logic, and call into that object from both The Raisers Edge and RE:Anywhere VBA DLL macros. For example, you want to add a particular Constituent Code to any constituent before a constituent is saved. First, you can use Visual Basic to create a class (for example, CMyBusinessLogic) with a public method that adds the constituent code. Remember, this code can be called from either The Raisers Edge or RE:Anywhere. The following code is an example of this.
Public Sub DoConstituentBeforeSave(ByRef oRecord As CRecord, ByVal bFromREAnywhere As Boolean, ByRef bCancel As Boolean, Optional ByRef oWebContext As IBBWebContext) With oRecord.ConstituentCodes.Add() .Fields(CONSTITUENT_CODE_fld_CODE) = "My Code" .Fields(CONSTITUENT_CODE_fld_DATE_FROM) = Format(Now(), "mm/dd/yyyy") End With If bFromREAnywhere Then oWebContext.VBAContext.AppendMessage "BeforeSave: Constituent Code Added." Else MsgBox "Constituent Code Added.", vbOKOnly, "BeforeSave" End If
VB A
145
The project containing this class needs references to Blackbaud Raisers Edge 7 Objects and Blackbaud RE:Anywhere VBA Interfaces. The project is then compiled into a DLL, which is then placed on both The Raisers Edge client workstations and on the Web server hosting RE:Anywhere. Then, when writing The Raisers Edge and RE:Anywhere VBA DLL macros, each needs only a reference to the business logic DLL and the following code:
' in RE:
Public Sub ActiveConstituent(oRecord As Object, bCancel As Boolean) Dim oMyBusinessLogic As CMyBusinessLogic Set oMyBusinessLogic = New CMyBusinessLogic oMyBusinessLogic.DoConstituentBeforeSave oRecord, False, bCancel Set oMyBusinessLogic = Nothing End Sub
' in RE:Anywhere:
Private Sub IBBWebActiveConstituent_BeforeSave(oRecord As Object, bCancel As Boolean) Dim oMyBusinessLogic As CMyBusinessLogic Dim oWC As IBBWebContext Set oWC = REWebApplication.WebContext Set oMyBusinessLogic = New CMyBusinessLogic oMyBusinessLogic.DoConstituentBeforeSave oRecord, True, bCancel, oWC Set oMyBusinessLogic = Nothing Set oWC = Nothing End Sub
146
CHAPTER 2
New Notepad
Using this macro, you can select a constituent and add a notepad record. Please remember....
We provide programming examples for illustration only, without warranty either expressed or implied, including, but not limited to, the implied warranties of merchantability and/or fitness for a particular purpose. This article assumes that you are familiar with Microsoft Visual Basic and the tools used to create and debug procedures. Blackbaud Customer Support can help explain the functionality of a particular procedure but they will not modify, or assist you with modifying, these examples to provided additional functionality. If you are interested in learning more about The Raisers Edge optional modules VBA and API, contact our Sales department at [email protected].
INDEX
147
Index
A
accessing, see opening active data objects 111 active data objects events 111 active object macro creating 100 debugging 101 defined 99 running 101 active objects 109 active process objects 119 active X attributes grid 41 controls, defined 37 data grid 39 phones/email/links grid 44 adding child objects 30 data objects 20 notepad records 108 records using data object 20 afterdelete event 118 afterimport event 128 afterpost event 124 afterprocess event 130 aftersave event 114 annotation form 65 API library type 10 reference, set 11 session context 12 API vs VBA 95 attribute type server 62 attributes grid, active X 41
C
child objects adding 30 collection types 22 defined 21 deleting 30 sorting collections 31 top collection 26 top view collection 28 view collection 28 closedown 13 closerecord event 117 code sample data objects 16 explanation 4 media form 71 query objects 46 report objects 54 table lookup handler 60 user interface objects 36 VBA 145 code tables server 56 code window 97 code wizard 100 collection child object child 22 programming 21 sorting 31 standard child 22 top 26 top view 28 view 28 data object, filtering 32 report object categories 49 instances 52 types 51 COM automation objects 5 creating active object macro 100 data object macro 104 DLL macros 138 Export macro 135 export macros 105 Query macro 132 query macros 105 standard macros 102 static queries 46 Custom View 84 customizing RE:Anywhere 140
B
Batch afterpost event 124 beforepost event 120 beforepostrecord event 121 handleexception event 122 beforedelete event 117 beforeimport event 126 beforeopen event 112 beforepostrecord event 121 beforeprocess event 129 beforesave event 113
D
data entry forms 35 data grid active X 39
148
INDEX
data object macro creating 104 debugging 105 defined 99, 103 running 105 data objects adding 20 code samples 16 database ID 15 defined 6, 13 deleting 20 filtering 32 hierarch 14 integrity 20 loading 15 top level 14 updating 18 validation 20 database ID 15 debugging active object macro 101 DLL macros 139 export macros 105 query macros 105 RE:Anywhere 142 standard macro 102 deleting child objects 30 data objects 20 records using data object 21 DLL macros 138 documentation map 4
H
handleexception event 122, 127 hierarchy, data object 14
I
IBBDataObject interface 80 IBBMetaField interface 81 Import afterimport event 128 beforeimport event 126 beforeimportrecord event 126 handleexception event 127 init 13 initializing objects closedown method 13 defined 13 init method 13 integrity, data objects 20 interfaces 5
L
library type API 10 defined 7 early bound objects 7 VBA 8 loading data objects 15
E
early bound objects 7 errors 33 Export code sample 137 creating a macro 135 VBA support 134 VBA user field 136 export macros creating 105 debugging 105 defined 105 running 106
M
macro samples adding notepad records 108 setting defaults 107 macros 99 Mail afterprocess event 130 beforeprocess event 129 media form code sample 71 defined 69 misc UI 77 models, object 6
F
fields property 19 filtering data object collections 32 forms designer 98
N
notepad form 67
O
objects
INDEX
149
active 109 data 111 process 119 REApplication 110 child adding 30 collection types 22 defined 21 deleting 30 sorting collection 31 top collection 26 top view 28 view collection 28 collections 21 data 13 defined 6 early bound 7 filtering 32 IBBDataObject interface 80 initializing 5, 13 models 5, 6 releasing 5, 13 service 5 session context 11 user interface 5 report annotation form 65 attribute type server 62 categories collection 49 code sample 54 code tables server 56 defined 49 instances collection 52 media form 69 misc UI 77 notepad form 67 property viewer 72 search screen 74 table lookup handler 59 types collection 51 service defined 44 query objects 45 user interface code sample 36 data entry forms 35 defined 34 showing standard forms 35 opening query objects 45 standard child collection 25 VBA 95
Q
Query code sample 133 creating macro 132 user field 133 VBA support 132 query and export macros 99 query macros creating 105 debugging 105 defined 105 running 106 query objects code sample 46 creating static queries 46 defined 45 opening 45 processing result set 45
R
Raisers Edge, The code wizard 100 search screen 16 RE:Anywhere customizing 140 debugging DLL macro 142 DLL macro permissions 142 memory 142 object hierarchy 141 run-time errors 142 sharing code with Raisers Edge 144 REApplication object 110 record adding using data objects 20 deleting using data objects 21 references API 11 VBA 9 releasing objects closedown 13 defined 13 init 13 report objects annotation form 65 attribute type server 62 categories collection 49 code sample 54 code tables server 56 defined 49 instance collection 52
P
phones/email/links grid, active X 44 processing query result set 45
150
INDEX
media form 69 misc UI 77 notepad form 67 property viewer 72 search screen 74 table lookup handler 59 type collection 51 Reports afterprocess event 130 beforeprocess event 129 running active object macro 101 data object macro 105 DLL macros 140 export macros 106 query macros 106 standard macro 103
U
UI closing event 110 UI opening event 110 UI, see user interface updating data objects 18 standard child collection 24 user field 133 user interface objects code sample 36 data entry forms 35 defined 34 showing standard form 35 using documentation 4
S
sample code, see code sample search screen 74 service objects 5 defined 44 query 45 session context API 12 defined 11 VBA 12 setting defaults, macro samples 107 sorting, collections 31 standard child collection accessing 25 defined 22 navigating 23 structure 23 updating 24 standard macro debugging 102 defined 99 running 103
V
validation, data objects 20 VBA code samples 145 code window 97 defined 94 forms designer 98 macros 99 opening 95 project window 97 properties window 97 reference, set 9 session context 12 type library 8 VBA in Batch 120 VBA vs API 95 Visual Basic for Applications, see also VBA 95
T
table lookup handler code samples 60 defined 59 top level data objects 14 transactions 5 translations 83 type, library defined 7