Novell is now a part of Micro Focus

Extending the NDS Schema with VB6 and Novell's ActiveX Controls

Articles and Tips: article

NANCY MCLAIN
Senior Technical Writer
Developer Information

01 Jan 2000


Describes a sample application that uses Microsoft's Visual Basic 6 and Novell's ActiveX controls to extend the NDS schema. The NWDir and NWSess controls can help you rapidly and simply develop powerful NDS applications.

Introduction

This Developer Note describes a sample application that uses Microsoft's Visual Basic 6 and Novell's ActiveX controls to extend the NDS schema.

NDS is a Directory Service that allows you to easily manage your network resources. Simply put, the NDS Directory is a globally accessible, distributed database of objects that represents network resources, such as network users, servers, printers, print queues, and applications. This database is extensible, which means that you can customize the NDS base schema so that the resulting database suits the particular needs of your company or your applications. Novell's ActiveX controls, NWDir and NWSess, make it easy to write applications that extend the schema.

This article discusses a sample application I wrote with VB6 and NWDir to extend the schema. If you'd like more information about Novell's NDS ActiveX controls, follow the series of articles Morgan Adair is writing about the NDS controls. His series started in June, 1999, with an article titled, "Programming with the Novell Controls for ActiveX and Visual Basic: Getting Started." These articles are intended to help a person with basic Visual Basic experience start developing to Novell's NDS. In fact, I used his sample code in my application to connect and login to an NDS tree. You can download Morgan's articles from http://www.developer.novell.com.

Overview of the Application

I wrote the sample application as if I were writing an application that would make an administrator's ability to extend the schema easy. This way, I could write sample code that would help you understand how to enable your own application to extend the schema.

The application performs five tasks:

  • Connect to an NDS tree.

  • Login to an NDS tree.

  • Create a new field in the NDS schema.

  • Assign this new field to a pre-existing layout in the NDS schema.

  • Create a new layout for the schema and assign fields to it (including any you've created).

I designed the user interface around a toolbar I created and five forms. I used the forms for Login and Connect that are discussed in the "Programming with the Novell Controls for ActiveX and Visual Basic" Developer Notes series. You can also use this series to take the corresponding DeveloperNet University courses athttp://www.developer.novell.com.

Every form contains the toolbar so that the user can easily navigate from each form. In addition, the ExtendSchema, CreateField, AssociateField, and CreateLayout forms have a place on the top to display which tree the user has connected to. This label lets users always know which tree they are working with. If a user is not connected, this box is blank.

In addition to the forms, I created two modules: Globals.bas and PubFuncs.bas. Globals.bas contains the global variables and PubFuncs.bas contains the public functions. Most of the time I called a function from two or more forms, I made it a public function and put in the PubFuncs.bas module.

The ActiveX Controls Used in the Application

Almost every form in the Extend Schema application uses the same controls:

  • Toolbar.

  • ImageList.

  • NWSess (Novell).

  • NWDir (Novell).

In order to get a toolbar that would let me customize its size, I used the Microsoft Windows Common Controls 5 (SP2). The ImageList controls the images on the toolbar.

The NWSess and the NWDir controls allow the application to interact with NDS. These controls enable the user to login or connect to an NDS tree and establish a session. The NWDir control also helps the user manage the NDS schema. For more information about the NWSess and the NWDir controls, see Morgan Adair's series about Visual Basic and the ActiveX controls.

Let's look the modules and forms and the code behind them.

Global Variables

Because I needed a toolbar, NDS connections, and NDS fields on every form, I used some global variables. The code shown below defines these variables.

Option Explicit

Public defaultTree As String            'Holds the name of the tree we're using as default.

Public Button As MSComctlLib.Button

Public associateField As New NWFieldType 'Holds the name of the newly created field 

to be associated

                                        'with a layout

Public flgNewFieldCreated As Boolean    'A signal that a new field has been created

Public flgConnected As Boolean          'A signal that the user has connected to a tree

defaultTree is first assigned a value for the tree name when the user connects to a tree in either the ConnectedTrees or the Login form. Every other form then uses this value to display the name of the tree the user is using as the default connection.

Button is assigned a value each time the user presses a button on the tool bar.

associateField is first assigned the newly created field name in the CreateField form after the user creates the new field. It's used in the AssociateField form so that the user can associate the new field with a pre-defined layout.

flgNewFieldCreated is first assigned a value in the CreateField form after the user has created the field. The toolbar function checks this flag to verify that users have created a field before they try to associate the new field with a layout.

flgConnected is assigned a value in the Connection and Login forms after the user has connected to a tree. The toolbar function checks this flag to make certain that users have connected to a tree before they try to create a field or layout.

The Public Functions

The following public functions are located in the PubFuncs.bas module:

  • GetConnectedTrees (used in the AssociateField and CreateLayout forms)

  • SetContext (used in the CreateField, AssociateField and CreateLayout forms)

  • Toolbar (used in all forms)

GetConnectedTrees Sample Code

The ConnectedTrees and Login forms both rely heavily on the GetConnectedTrees public function. GetConnectedTrees is shown in the sample code below.

Public Function GetConnectedTrees(lstConnectedTrees As ListBox, NWSess1 As NWSess) 

As Integer

     Dim connectedTree As NWNetworkName

      

     'Display the trees the client's connected to

     For Each connectedTree In NWSess1.ConnectedTrees

          lstConnectedTrees.AddItem connectedTree

     Next connectedTree



      'Select the top tree as default

     If lstConnectedTrees.ListCount > 0 Then

          lstConnectedTrees.Selected(0) = True

          GetConnectedTrees = 0

          

     End If

     

End Function

As this code shows, GetConnectedTrees relies on the NWSess control. The NWSess control helps you establish and manage network sessions. This control contains several properties. This function relies on the properties defined in the list below.


Connected Trees

Returns all connected NDS trees as a collection.

DefaultFullName

Sets and returns the user's default NDS tree and context.

When GetConnectedTrees and the Login forms load, they call the public function GetConnectedTrees with these parameters:

  • lstConnected Trees The list box on the ConnectedTrees form that lists all the trees the user is connected to.

GetConnectedTrees returns an integer that both forms check to see if the user has successfully connected to any NDS trees.

The GetConnectedTrees function uses a For loop to go through NWSess1's ConnectedTrees collection and lists each connected tree in the list box. It then selects the tree listed first in the list box as the user's default tree. Both forms let users select another tree to be the default, if they want to.

SetContext Sample Code

The CreateField, AssociateField and CreateLayout forms call the SetContext function to set the user's default context for the form. Each form must have the default context set. SetContext is shown in the sample code below.

Public Function SetContext(NWSess1 As NWSess, NWDir1 As NWDir)



    'Set context to root of the tree the user has selected

     NWSess1.DefaultFullName = "NDS:\\" + defaultTree + "\[Root]"

     NWDir1.FullName = NWSess1.DefaultFullName

End Function

When the forms load, they call SetContext with the NWSess1 and NWDir1 parameters. NWSess1 and NWDir 1 are instances of the NWSess and the NWDir controls.

SetContext sets NWSess's DefaultFullName property to the user's context in the default tree. NWSess1's DefaultFullName property uses the defaultTree global string that contains the name of the tree the user selected. SetContext then sets the NWDir's FullName property to the name returned by NWSess's DefaultFullName property.

The code has now set the default context so the user can extend the schema in the proper tree.

ToolBar Sample Code

Every form calls the ToolBar function. Including the toolbar on every form enables the user to easily navigate the application. The ToolBar function is shown in the following sample code.

Public Function ToolBar(ByVal Button As ComctlLib.Button)

        Select Case Button.Key

          Case "Connect"

              Call unloadForms(ByVal Button)

              frmConnectedTrees.Show

          Case "Login"

              Call unloadForms(ByVal Button)

              frmLogin.Show

          Case "Field"

              If flgConnected = False Then

                    MsgBox "You must first log in."

                    Call unloadForms(ByVal Button)

                    frmLogin.Show

              Else

                    frmExtendSchema.MousePointer = vbHourglass

                    Call unloadForms(ByVal Button)

                    frmCreateField.Show

              End If

          Case "Layout"

              If flgConnected = False Then

                    MsgBox "You must first log in."

                    Call unloadForms(ByVal Button)

                    frmLogin.Show

              Else

                    Call unloadForms(ByVal Button)

                    frmCreateLayout.Show

              End If

          Case "Associate"

          

              If flgConnected = False Then

                    MsgBox "You must first log in."

                    Call unloadForms(ByVal Button)

                    frmLogin.Show

               Else

                    If flgNewFieldCreated = False Then

                         MsgBox "You must first create a new field"

                         frmCreateField.Show

                    

                    Else

                         frmExtendSchema.MousePointer = vbHourglass

                         Call unloadForms(ByVal Button)

                         frmAssociateField.Show

                    End If

              End If

          Case "Cancel"

              Call unloadForms(ByVal Button)

              frmExtendSchema.Show

          Case "Exit"

              Call unloadForms(ByVal Button)

          End Select

        

End Function



Private Sub unloadForms(ByVal Button)

          Unload frmConnectedTrees

          Unload frmLogin

          Unload frmCreateField

          Unload frmCreateLayout

          Unload frmAssociateField





     If Button.Key <> "Cancel" Then<
          Unload frmExtendSchema

     End If



End Sub

The toolbar public function is fairly standard Visual Basic code. The code does some error-checking to ensure that users have connected to a tree, and that they have created a new field before they try to associate it with a layout.

The Exit and Cancel Functions

The Exit and Cancel functions on the toolbar are very basic. The Cancel function unloads any loaded forms and returns the user to the ExtendSchema form. The Exit function unloads the form and exits the application.

The ExtendSchema Form

The ExtendSchema form is the first form the user sees. It is also the form loaded when the user clicks on the cancel button.

Figure 1 shows the ExtendSchema form with the controls and attributes labeled.

Figure 1: The ExtendSchema form controls and attributes.

The ExtendSchema Form Sample Code

The ExtendSchema form uses the Novell ActiveX controls when it loads to check if the user has connected to a tree. If the user has connected to a tree, the code calls the public function SetContext with the NWSess1 and NWDir 1 parameters. The SetContext function is described in the "SetContext Sample Code" section.

The ExtendSchema form's code displays the default tree name at the top of the form. When the user clicks a button on the toolbar, the code calls the toolbar's public functions. The toolbar's code is explained in the "ToolBar's Sample Code" section.

Option Explicit





Private Sub Form_Load()

     'Check to see if you're connected to a tree.

     'If so, set the default context

        If flgConnected Then

         Call SetContext(NWSess1, NWDir1)

          'Display the tree name

         lblTreeName = defaultTree

    End If

        

    frmExtendSchema.MousePointer = vbDefault

End Sub



Private Sub tlbSchema_ButtonClick(ByVal Button As ComctlLib.Button)

    frmExtendSchema.MousePointer = vbHourglass 'The forms sometimes take a while to load

    Call ToolBar(ByVal Button)

End SubThe ConnectedTrees Form

The ConnectedTrees Form

Users select the tree whose schema they want to extend in this form. If users have already logged into the trees they're connected to, they can use this form to select a default tree. If they haven't already logged into the tree, they must use the login form, which the code automatically loads. Figure 2 shows the ConnectedTrees form with its controls and attributes labeled.

Figure 2: The ConnectedTrees form controls and attributes.

This form has a list box that contains all of the trees the user is connected to. In the list box, the users select the tree they want to extend the schema on. That tree then becomes their default connection for this application. The code behind this form is shown below.

The ConnectedTrees Form Sample Code

Option Explicit



Private Sub Form_Load()

Dim intConnected As Integer

    

    'List the connected trees in the list box

    intConnected = GetConnectedTrees(lstConnectedTrees, NWSess1)

    

    'If there are no connected trees, go to login

    If intConnected <> 0 Then<
          frmLogin.Show

          Unload frmConnectedTrees

    Else

          flgConnected = True

    End If

    

End Sub



Private Sub cmdUseTree_Click()

    

    'Set the tree to use to the selected tree in the list

    defaultTree = lstConnectedTrees.Text

    

    'It could take awhile, so change to hourglass

    frmConnectedTrees.MousePointer = vbHourglass

    

    'Go to the Extend the schema form

    frmExtendSchema.Show

    

    Unload frmConnectedTrees

End Sub



Private Sub lstConnectedTrees_DblClick()

    cmdUseTree_Click

End Sub





Private Sub tlbSchema_ButtonClick(ByVal Button As ComctlLib.Button)

    frmConnectedTrees.MousePointer = vbHourglass 'The forms sometimes take a 

while to load

    Call ToolBar(ByVal Button)

End Sub

The ConnectedTrees relies on the public function GetConnectedTrees in the PubFuncs module. This function is described earlier in this article in the "GetConnectedTrees Sample Code."

Establishing a connection with an NDS tree is merely a matter of selecting the appropriate tree. The user must already be logged into the tree. The selected tree name is then assigned to a global variable that is used to build the user's context for each form.

When the user presses the Use Selected Tree command button, or double-clicks a tree in the list box, the code assigns the user's selected tree to the global variable defaultTree. This tree then becomes the default tree for the application until the user selects, or logs into, a different tree. The code then sets a global flag, flgConnected, to True.

The code checks flgConnected whenever the user pushes a button on the toolbar. If flgConnected is False, the user is not connected to a tree, and a msgBox pops up with the message, "You must first log in." When the user presses the OK button, the code loads the login form.

The Login Form

On this form, users login to an NDS tree whose schema they want to extend. Users use this form, if they haven't logged into, the NDS tree they want to use yet. Figure 3 shows the Login form with its controls and attributes labeled.

Figure 3: The Login form controls and attributes.

This form has a list box that contains all of the trees the user can log into. In the list box, the user selects the tree to extend the schema on. The user enters the user name and password in the appropriate text boxes on the form and press Login. If the users successfully logs in, that tree becomes the default connection for this application. The code behind this form is shown below.

The Login Form Sample Code

Private Sub Form_Load()

Dim intConnected As Integer

    

     'List the connected trees in the list box

     intConnected = GetConnectedTrees(lstAvailableTrees, NWSess1)

    

     'If there are no connected trees, go to login

     If intConnected <> 0 Then<
          frmLogin.Show

          Unload frmConnectedTrees

     End If

End Sub



Private Sub cmdLogin_Click()

     Dim intLoggedIn As Integer

     

     'Set the tree to use to the selected tree in the list box

     defaultTree = lstAvailableTrees.Text

     

     'Log in

     intLoggedIn = NWSess1.Login(NWSess1.DefaultFullNameFromTreeName(defaultTree), 

          txtUserName.Text, txtPassword.Text, False)

     If intLoggedIn Then

          flgConnected = True

          frmExtendSchema.Show

          Unload frmLogin

     Else

          MsgBox "Unable to log in to server " + defaultTree

     End If

        

End Sub



Private Sub tlbSchema_ButtonClick(ByVal Button As ComctlLib.Button)

     frmLogin.MousePointer = vbHourglass 'The forms sometimes take a while to load

     Call ToolBar(ByVal Button)

End Sub

Like the ConnectedTrees form, this form calls the public function GetConnectedTrees when it loads. GetConnectedTrees then lists all the available trees in the form's list box.

The Login form relies on the NWSess control to establish the network session. It uses the NWSess control's Login method and passes it the following parameters:

  • DefaultFullNameFromTreeName(defaultTree) This is a method that returns the full name of the user's default context in the tree it's passed. The defaultTree variable is a global string and is assigned the name of the tree the user has highlighted.

  • txtUserName.Text The user's login name entered in UserName text box.

  • txtPassword.Text The user's password entered in the Password text box.

  • False This is a Boolean value for ShowUI. It determines whether the login user interface is shown. False means that the login will be silent. In other words, the login dialog won't pop-up, and the user will log in using the name and password entered in the form.

The following line accomplishes the Login:

intLoggedIn = NWSess1.Login(NWSess1.DefaultFullNameFromTreeName(defaultTree), 

         txtUserName.Text, txtPassword.Text, False)

If the user successfully logs in, the code assigns the user's selected tree to the global variable defaultTree. This tree then becomes the default tree for the application until the user selects, or logs into, a different tree. The code also sets a global flag, flgConnected, to True. As discussed in the ConnectedTrees form, the flgConnected flag determines whether the user is connected to a tree, or whether the user must first log in.

After users either connect to or login to a default tree, they can choose to create a new NDS schema field or NDS schema layout. They can also choose to associate a newly-created field to a pre-existing layout.

The CreateField Form

Users create new fields for the NDS schema on this form. When they press the Create New Field button on the toolbar, the application checks whether they're connected to a default tree. If they're not, it pops up a message box telling them they must log into a tree, then loads the Login form.

Figure 4 shows the CreateField form with its controls and attributes labeled.

Figure 4: The CreateField form controls and attributes.

On this form, the user enters the new field's name in the text box, selects the appropriate syntax and chooses whether the field is single-valued or not. Then, all the user has to do is press the Create the Field button. The code then creates the new field. The sample code behind this form is shown below.

The CreateField Form Sample Code

Option Explicit





Dim newField As New NWFieldType

Dim added As Boolean

Private Sub Form_Load()



         'Set the NDS Context so we can use the right tree

         Call SetContext(NWSess1, NWDir1)

         

          'Display the tree name

         lblTreeName = defaultTree

        

         'Select default syntax

         optOctetString = True



End Sub



Private Sub cmdCreateField_Click()

     'Assign the field name

     newField.Name = txtFieldName.Text

     

     'Assign the field syntax

     Call GetSyntax

     

     'Assign the field constraints.

     'Right now, you can only determine

     'whether the field is single-valued.

     Call GetConstraints

     

     'Add the field name to the schema

     added = (NWDir1.FieldTypes.Add(newField))

     If added Then

          MsgBox "Added field:" + newField.Name

          flgNewFieldCreated = True

     Else: GoTo ErrorHandler

     End If

     associateField.Name = newField.Name

Exit Sub

ErrorHandler:

     If Err.Number = 615 Then

          MsgBox newField.Name + "has already been added to the schema." + vbCrLf

     ElseIf Err.Number = 672 Then

          MsgBox "You have insufficient rights to the directory. + vbCrLf"

     Else

          MsgBox "Unable to add field:" + txtFieldName.Text

     End If

End Sub



Private Sub GetSyntax()

     'Determine which syntax has been chosen

     'and assign it to the field

     Select Case True

          Case optBackLink.Value

              newField.Syntax = SYN_BACK_LINK

          Case optBoolean.Value

              newField.Syntax = SYN_BOOLEAN

          Case optCEString.Value

              newField.Syntax = SYN_CE_STRING

          Case optCIList.Value

              newField.Syntax = SYN_CI_LIST

          Case optCIString.Value

              newField.Syntax = SYN_CI_STRING

          Case optClassName.Value

              newField.Syntax = SYN_CLASS_NAME

          Case optCounter.Value

              newField.Syntax = SYN_COUNTER

          Case optDistinguishedName.Value

              newField.Syntax = SYN_DIST_NAME

          Case optEmailAddress.Value

              newField.Syntax = SYN_EMAIL_ADDRESS

          Case optFaxNumber.Value

              newField.Syntax = SYN_FAX_NUMBER

          Case optHold.Value

              newField.Syntax = SYN_HOLD

          Case optInteger.Value

              newField.Syntax = SYN_INTEGER

          Case optInterval.Value

              newField.Syntax = SYN_INTERVAL

          Case optNetAddress.Value

              newField.Syntax = SYN_NET_ADDRESS

          Case optNumericString.Value

              newField.Syntax = SYN_NU_STRING

          Case optObjectACL.Value

              newField.Syntax = SYN_OBJECT_ACL

          Case optOctetList.Value

              newField.Syntax = SYN_OCTET_LIST

          Case optOctetString.Value

              newField.Syntax = SYN_OCTET_STRING

          Case optPath.Value

              newField.Syntax = SYN_PATH

          Case optPOAddress.Value

              newField.Syntax = SYN_PO_ADDRESS

          Case optPrString.Value

              newField.Syntax = SYN_PR_STRING

          Case optReplicaPointer.Value

              newField.Syntax = SYN_REPLICA_POINTER

          Case optStream.Value

              newField.Syntax = SYN_STREAM

          Case optTelNumber.Value

              newField.Syntax = SYN_TEL_NUMBER

          Case optTime.Value

              newField.Syntax = SYN_TIME

          Case optTimeStamp.Value

              newField.Syntax = SYN_TIMESTAMP

          Case optTypedName.Value

              newField.Syntax = SYN_TYPED_NAME

          Case optUnknown.Value

              newField.Syntax = SYN_UNKNOWN

          Case Else

              MsgBox "Please choose a syntax."

     End Select

End Sub



Private Sub GetConstraints()

     'Determine if the field is to be single-valued

     'and assign that value to the field.

     'If and when the control supports the other

     'constraints, add them as check boxes and handle them here.

     If chkSingleValued Then newField.SingleValued = True

End Sub



Private Sub tlbSchema_ButtonClick(ByVal Button As ComctlLib.Button)

     frmCreateField.MousePointer = vbHourglass 'The forms sometimes take a 

while to load

     Call ToolBar(ByVal Button)

End Sub



Private Sub txtFieldName_KeyPress(KeyAscii As Integer)

     'Check to see if user has pressed Enter. If so, shift the focus to the syntaxes

     If KeyAscii = 13 Then

          Call optOctetString.SetFocus

     End If

End Sub

As the code shows, the CreateField form depends upon Novell's NWDir ActiveX control. NWDir contains a number of collections, objects and properties to help you manage NDS entries and the schema. This form depends upon the NWFieldTypes collection of NWFieldType objects. It also uses the NWFieldTypes Add method.

As with most of the forms, when it loads, it calls the public function SetContext to set the default context for the form. It also displays the default tree name at the top of the form. The form does one other thing on loading, it selects the Octet String Syntax as the default syntax in case the user doesn't select a syntax. The octet string syntax is generic enough to handle most data types.

When you create a new NDS field type, you create an NWFieldType object. In order to create this object, you must assign values to these properties:


Name

The name of the new field type.

Removable

Determines whether the field type can be removed. (Read only.)

SingleValued

Determines whether a field of this type can have multiple values.

Syntax

The NDS Syntax identifier for this field type.

SyntaxName

The NDS Syntax name represented in string format.

TypeName

The data type used to determine how to decode field data. (Read only.)

The CreateField form lets the user define this information by filling out the form. The user need only enter the new field's name, select a syntax from the syntax option buttons, and determine whether the new field type is single-valued or not.

The NWDir control supports all of the NDS syntaxes and lets you select anyone of them for your new field. However, currently it only supports a few of the NDS field constraints, and only one of the constraints it supports is writable.

To briefly review, you can use the NDS field constraints to appropriately restrict the information the field can store and to constrain NDS and NDS client operations on that data The constraints are:

  • Hidden

  • Public Read

  • Server Read

  • Nonremovable

  • Operational

  • Read Only

  • Write Managed

  • Sync Per Replica

  • Schedule Sync Never

  • Sync Immediate

  • Single Valued

  • Sized

  • String

Of these constraints, NWDir supports only the Removable and Single Valued constraints. The Removable constraint is read-only, so the user can't set it. Removable is automatically set to TRUE for any field that a user creates, meaning that the field type can be removed. The only constraint the user can set is Single Valued. Selecting Single Valued on the form sets the Single Valued property to TRUE, meaning that the field type can have only one value, not multiple values.

When the user clicks the Create the Field command button, the code assigns the name entered in the txtFieldName text box to the new field's Name property. It then uses a Switch statement to determine which syntax option box is selected and assigns that syntax to the new field's Syntax property. The code also checks to see whether the user has checked the Single Valued check box and assigns the appropriate value to the new field's Single Valued property. Because the Removable and Type Name properties are read-only, the code doesn't assign any values to them.

The code then adds the new field to the NWFieldTypes collection with the following line:

added = (NWDir1.FieldTypes.Add(newField))

The code does some simple error-checking in order to tell the user whether the field type has been successfully added to the schema or not.

The last things the code does is to set a global flag that the user has created a new field and to set the global variable associateField to the newly created field. associateField holds the new field in case the user wants to associate the new field with a layout already contained in the schema. The user can use the AssociateField form to do this.

The AssociateField Form

On this form, users associate a field they just created with pre-defined layouts. When they press the Associate Field with Layout button on the toolbar, the application checks whether they're connected to a default tree. If they're not, it pops up a message box telling them they must log into a tree, then loads the Login form.

The toolbar function then checks the global flag flgNewFieldCreated to see if the user has created a new field before selecting to associate the field with a layout. If the user hasn't created a new field yet, a message box pops up stating that a new field must be created. The code then loads the CreateField form automatically.

Figure 5 shows the AssociateField form with its controls and attributes labeled.

Figure 5: The AssociateField form controls and attributes.

This form has a list box that contains a list of the predefined layouts in a list box. In the listbox, Users select the layout that they want to associate the new field with and presses the Associate the Field command button. Their new field is then associated with the layout they've selected. The sample code behind this form is shown below.

The AssociateField Form Sample Code

Option Explicit



Private Sub Form_Load()



          'Set the NDS Context so we can use the right tree

          Call SetContext(NWSess1, NWDir1)

          

          'Display the tree name

          lblTreeName = defaultTree

          

          'Fill the Layout list box with the schema layouts

          Call GetLayouts

  

 End Sub

Private Sub GetLayouts()

     Dim intIndex As Integer

     Dim Layout As NWLayoutDescription

     

     'Display the Layouts in the schema

     For Each Layout In NWDir1.Layouts

         lstLayouts.AddItem Layout.Name

         'For convenience, select Top as the default layout

          If Layout.Name = "Top" Then

                lstLayouts.Selected(intIndex) = True

          End If

          intIndex = intIndex + 1

     Next Layout

End Sub



Private Sub cmdAssociate_Click()

     Dim intIndex As Integer

     Dim Layout As NWLayoutDescription

     Dim flgFieldAdded As Boolean

     Dim flgNoError As Boolean

     

     flgNoError = True

     

     'Associate the appropriate fields to the new layout

      Do While intIndex <> lstLayouts.ListCount <
          If lstLayouts.Selected(intIndex) Then 

              For Each Layout In NWDir1.Layouts   'Go through the Layouts collection 

to find the matching

                                                    'layout

                    If lstLayouts.List(intIndex) = Layout.Name Then

                         flgFieldAdded = 

NWDir1.Layouts(Layout.Name).Fields.Add(associateField) 'Add the new

                                                                               'field

                         If flgFieldAdded = False Then

                                 flgNoError = False  'This flag saves the error 

                         End If

                         Exit For

                    End If

               Next Layout

          End If

          intIndex = intIndex + 1

     Loop

     If flgNoError Then  'If there was no error, all the fields were added.

          MsgBox "Field added to the layout."

     Else

          MsgBox "A field couldn't be added to the layout."

     End If

       

End Sub



Private Sub tlbSchema_ButtonClick(ByVal Button As ComctlLib.Button)

     Call ToolBar(ByVal Button)

End Sub

As the code shows, the AssociateField form depends upon Novell's NWDir ActiveX control. This form depends upon the NWLayoutDescriptions collection of NWLayoutDescription objects, and the NWFieldDescriptions collection ofNWFieldDescription objects. It also uses the NWFieldDescriptions Add method.

When the form loads, the public function SetContext sets the default context for the form. The form displays the default tree name at the top. The form then calls GetLayouts to fill the list box.

The GetLayouts function uses a for loop to fill the list box with the layouts contained in NWDir's NWLayoutDescriptions collection. While it steps through the collection, it also checks for the Layout named "Top". When the code finds "Top," it highlights Top as the layout chosen by default. In this form, I chose Top as the default layout only as a matter of convenience.

When the user clicks the Associate the Field command button, the code executes a Do While loop to go through the list in the lstLayouts list box, looking for the layout the user selected. When the code locates a selected layout, it then uses a nested For loop to look through NWDir's NWLayoutDescriptions collection, for the matching layout. When it finds the proper layout, it adds the field, held by the global associateField, to the layout collection. The line to add the field is shown below:

flgFieldAdded = NWDir1.Layouts(Layout.Name).Fields.Add(associateField)

The new field was assigned to the associateField global at the end of CreateField.

If the field can't be added to the layout for some reason, flgNoError saves the fact that an error occurred and causes a message box to pop up saying that a field couldn't be added to the layout.

Please remember that my application contains only rudimentary error-checking. A better solution would be to tell the user which layout caused the error.

The CreateLayout Form

Users create a new layout on this form. They can associate fields with the new layout, even fields they've created on the CreateField form. When they press the Create New Layout button on the toolbar, the application checks whether they're connected to a default tree. If they're not, it pops up a message box telling them they must log into a tree, then loads the Login form.

Figure 6 shows the CreateLayout form with its controls and attributes labeled.

Figure 6: The CreateLayout form controls and attributes.

On this form, the user enters the new layout's name in the Layout Name text box, selects the layout the new layout will be based on (or inherit from), and chooses any additional fields they want to add to the new layout. They also select whether other objects can be created from the layout (the layout's effective) and whether those objects will be containers. Then, all they have to do is press the Create New Layout Description Button. The code then creates the new layout. The sample code behind this form is shown below.

The CreateLayout Form Sample Code

The code for this form is longer and more complex than any of the previous forms. So, it is probably easier to look at it in sections. The code to load this form is much more complex than the code to load the previous forms; so, we'll look at it as a separate section. The code that handles a selected layout is the second code section, and the code that actually creates the layout is the final code section.

Option Explicit

Dim NewLayout As New NWLayoutDescription

Dim layoutAdded As Boolean, fieldsAdded As Boolean



Private Sub Form_Load()

                  

          'Display the tree name

          lblTreeName = defaultTree

          

          'Set the NDS Context so we can use the right tree

          Call SetContext(NWSess1, NWDir1)

                  

          'Fill the Layout list box with the schema layouts

          frmCreateLayout.MousePointer = vbHourglass

          Call GetLayouts

          

          'Fill the Fields list box with the fields

          Call GetFields

          

          frmCreateLayout.MousePointer = vbDefault

    

End Sub



Private Sub GetLayouts()

     Dim intIndex As Integer

     Dim Layout As NWLayoutDescription

     

     'Display the Layouts in the schema

     For Each Layout In NWDir1.Layouts

          lstBasedOn.AddItem Layout.Name

          'Because Everything inherits at least from Top, we'll select Top

          If Layout.Name = "Top" Then

            lstBasedOn.Selected(intIndex) = True

          End If

          intIndex = intIndex + 1

     Next Layout

End Sub



Private Sub GetFields()

     Dim Field As NWFieldType

     

    'List the fields into the Field list box

     For Each Field In NWDir1.FieldTypes

          lstFields.AddItem Field.Name

     Next Field

     

     'Because all objects inherit from Top, remove Top's select

     'From the list of possible Fields to select

     Call RemoveTopFields

    

End Sub



Private Sub RemoveTopFields()

     Dim intIndex As Integer

     Dim Field As NWFieldDescription

     

     Do While intIndex <> lstFields.ListCount + 1<
         For Each Field In NWDir1.Layouts("Top").Fields

              If lstFields.List(intIndex) = Field.Name Then

                  lstFields.RemoveItem (intIndex)

              End If

          Next Field

     intIndex = intIndex + 1

     Loop

End Sub

As the above code shows, the CreateLayout form depends upon Novell's NWDir ActiveX control. NWDir contains a number of collections, objects and properties to help you manage NDS entries and the schema. This form depends upon the NWLayoutDescriptions collection of NWLayoutDescription objects. It also uses the NWLayoutDescriptions Add method.

As with most of the forms, when it loads, it calls the public function SetContext to set the default context for the form. It also displays the default tree name at the top of the form. The form does a few other things on loading, it:

  • Lists all of the layouts in the schema in the Layout Based On list box.

  • Lists all of the fields in the schema in the Additional Fields to associate with this layout list box.

  • Removes from the list box all of the fields that the new layout will inherit from the layout it's based on.

After the code sets the context, it calls the GetLayouts function to fill in the Layout Based On list box. This function uses a For loop to step through the NWLayoutDescriptions collection and add them to the list box. Because every layout must inherit at least from the "Top" layout, the code selects the "Top" layout as the default selection.

The Form_Load code then calls the GetFields function. The GetFields function also uses a For loop to set through the NWfieldTypes collection and add them to the list box. When it has added all the fields to the list box, it calls the RemoveTopFields function.

The code must remove all fields associated with Top so that the user can't select them to add to their new layout. NDS could generate an error if the user tries to add fields the layout's already inherited. Removing any fields associated with Top prevents this error. Because all objects must inherit from at least Top, the code always removes the fields associated with Top. Later, the code also removes any fields associated with any layout the user has selected to base the new layout on.

The RemoveTopFields function uses a Do While loop to step through the list box. A nested For loop compares each field in Tops Fields property with the field name in the list box. To access Top's Fields property, you use the NWLayoutDescriptions collection, passing it the layout's name instead of the index. The code looks like this:

For Each Field in NWDir1.Layouts("Top").Fields...

When the code finds a field name that matches one of the field names in Top's Fields properties, it deletes it from the list box.

The form is now loaded and waiting for user input. By filling out this form, the user fills out a template to create a new layout. A new layout requires values for the following properties:


Name

The layout's name.

BasedOn

The name of the layout that the layout is based on (inherits from).

Fields

The fields attached to this layout.

Container

Specifies whether the layout is a container.

Createable

Specifies if an entry of this layout can be created.

ImageIndex

The index key used to locate a layout bitmap image. (Read only.)

Removable

Specifies whether the current layout can be removed. (Read only.)

Both the ImageIndex and Removable properties are read-only. The user can't set them. Users can remove layouts they create.

To review, NDS objects have class flags associated with them. These class flags define the operations NDS allows on the object class. Usually, you can set these flags:

  • Container Flag.

  • Effective Flag.

  • Auxiliary Flag.

  • Non-removable Flag.

Currently, Novell's ActiveX controls allow you to only determine whether your layout is effective (you can create objects from it) and whether objects created from the layout will contain other objects.

The CreateLayout form lets users create new layouts by:

  • Entering the layout's name in the Layout Name text box.

  • Selecting a layout to base the new layout on in the Layout Based On list box.

  • Selecting fields to associate with the new layout in the Additional fields to associate with this layout list box.

  • Clicking the check boxes to determine whether objects can be created from the layout and whether those objects will contain other objects.

When the user selects a layout to base their new layout on, the code immediately removes any fields the new layout would inherit from the layout the user has selected. This includes all of the fields the selected layout has inherited from other layouts. In other words, the code walks the inheritance chain and removes all possible inherited fields. This prevents the user from selecting any fields the layout already has and generating a possible NDS error.

Private Sub lstBasedOn_Click()



     'It could take awhile, so change to hourglass

     frmCreateLayout.MousePointer = vbHourglass

     

     'Remove the fields our layout inherits from the layout its based so

     'the user can't select fields the layout already has.

     'This avoids an NDS error

     

     Call RemoveInheritedFields

     

     'Change the cursor back

     frmCreateLayout.MousePointer = vbDefault

     

     

End Sub

Private Sub RemoveInheritedFields()

     Dim intIndex As Integer

     Dim inheritedLayout As New NWLayoutDescription

     Dim Field As NWFieldDescription

     

     'Compare the fields associated with the layout our layout is inheriting from

     '(based on) to the fields in the field box. If the fields match, our layout is

     'inheriting that field already. If we allow the user to select that field, NDS

     'will generate an error. So remove all fields our layout inherits. Top's

     'fields are already gone.

     

     If lstBasedOn.Text <> "Top" Then<
          inheritedLayout.Name = lstBasedOn.Text

          Do Until inheritedLayout.Name = "Top"

              Do While intIndex <> lstFields.ListCount + 1<
                   For Each Field In NWDir1.Layouts(inheritedLayout.Name).Fields

                         If lstFields.List(intIndex) = Field.Name Then

                              lstFields.RemoveItem (intIndex)

                         End If

                    Next Field

                    intIndex = intIndex + 1

              Loop

              inheritedLayout.Name = NWDir1.Layouts(inheritedLayout.Name).BasedOn

          Loop

     End If

 

End Sub

The RemoveInheritedFields code walks the inheritance chain to determine which fields the new layout will inherit. To do this, it uses a Do Until loop with a nested Do While loop that has a nested For loop.

The looping starts by setting inheritedLayout.Name to the highlighted layout name in the lstBasedOn list box. The inheritedLayoutName always holds the name of the layout we are looking at in the inheritance chain. The code starts with the highlighted layout in the lstBasedOn list box and compares the fields in the lstFields list box with the fields associated with the layout in inheritedLayoutName. When it is through comparing all the fields in the lstFields list box, the code sets inheritedLayout's Name property to the name of the layout in the BasedOn property and searches through the lstField list box for all the fields associated with that layout. The code continues setting inheritedLayout's Name property to the name in its BasedOn property until the Top layout is reached. The code then quits looping.

The actual loops work like this:

  1. The Do Until loop loops until inheritedLayout.Name matches "Top." At this point, the code stops searching the NWLayoutDescriptions collection because Top's fields are already removed from the list box.

  2. The nested Do While loop walks through the lstFields list box.

  3. The nested For loop searches through inheritedLayout.Name's Fields property for field names that match the names in the lstFields box and removes any matching field names.

  4. When all the fields in inheritedLayout.Name's Fields properties have been compared, inheritedLayout.Name is set equal to its BasedOn property and the looping begins again.

When the looping is done, all the fields the new layout will inherit have been removed from the lstFields list box so that the user can't select them. The form is again waiting for user input.

The next code actually creates the layout.

Private Sub cmdCreateLayout_Click()

     

     'Assign the Layout Name

     NewLayout.Name = txtLayoutDescName.Text

     

     'Assign the inheritance

     NewLayout.BasedOn = lstBasedOn.Text

     Call SetLayoutFlags

     

     'Add the layout to the schema

     layoutAdded = (NWDir1.Layouts.Add(NewLayout))

     

     'Check if the layout was successfully added to schema and tell user

     If layoutAdded Then

          MsgBox "Added Layout:" + NewLayout.Name

          Call AssociateFields

     Else

        MsgBox "New Layout" + NewLayout.Name + "wasn't added."

     End If

     

End Sub



Private Sub SetLayoutFlags()



     ' If the objects can be created from the layout, set the creatable property

     ' to True. The default is False

     If chkCreatable <> True Then<
          NewLayout.Createable = False

     End If

     

     ' If objects created from the layout are containers, set the container property

     ' to True. The default is False

     If chkContainer = True Then

          NewLayout.Container = True

     End If

     

End Sub

Private Sub AssociateFields()

     Dim intIndex As Integer

     Dim Field As NWFieldType

     Dim FieldDescription As New NWFieldDescription

     

 'Associate the appropriate fields to the new layout

     Do While intIndex <> lstFields.ListCount<
          If lstFields.Selected(intIndex) Then

               For Each Field In NWDir1.FieldTypes

                    If lstFields.List(intIndex) = Field.Name Then

                         FieldDescription.Name = lstFields.List(intIndex)

                         fieldsAdded = 

NWDir1.Layouts(NewLayout.Name).Fields.Add(FieldDescription)

                         Exit For

                    End If

               Next Field

          End If

          intIndex = intIndex + 1

     Loop

     If fieldsAdded Then

          MsgBox "Added fields to " + NewLayout.Name

     End If

     

End Sub



Private Sub txtLayoutName_KeyPress(KeyAscii As Integer)

     'Check to see if user has pressed Enter. If so, shift the focus to the 

Based On box

     If KeyAscii = 13 Then

          Call lstBasedOn.SetFocus

     End If

End Sub



Private Sub lstBasedOn_DblClick()

     lstBasedOn_Click

End Sub





Private Sub tlbSchema_ButtonClick(ByVal Button As ComctlLib.Button)

        Call ToolBar(ByVal Button)

End Sub

When the user clicks the Create the New Layout Description command button, the code assigns the name entered in the txtLayoutDescName text box to the new layout's Name property. It then assigns the layout name highlighted in the lstBasedOn list box to the new layout's BasedOn property. The code then calls the SetLayoutFlags function.

SetLayoutFlags determines whether the chkCreatable and chkContainer check boxes are checked and sets the new layouts Createable and Container fields appropriately.

The code then adds the new layout to the NWLayoutDescriptions collection with the following line:

layoutAdded = (NWDir1.Layouts.Add(NewLayout))

The code does some basic error checking to ensure that the layout has been added to the schema.

The new layout description isn't quite finished, however. The code must still associate any additional fields to the new layout. So, the code calls the AssociateFields function.

The Associate Fields function uses a Do While loop to walk through the listFields list box, looking for selected fields. The user can select multiple fields, so the Do While loops through the entire list box. When the code finds a selected field name, it uses an embedded For loop to look through the NWFieldTypes collection for a matching field name. When it finds the correct field, it adds that field to the new layout with the following line:

fieldsAdded = NWDir1.Layouts(NewLayout.Name).Fields.Add(FieldDescription)

When the code has found all of the selected fields in the lstFields list box, it exits the loops. If it appears that the fields have been successfully added, a message box pops up and informs the user that the fields have been added to the new layout.

Again, remember that I did extremely basic error checking. A better method would be to ensure that every field is added successfully and inform the user of any fields that were not added.

The NDS schema now contains the new layout description.

Conclusion

I created the Extend Schema application primarily to show how your applications could extend the NDS schema using Visual Basic and Novell's ActiveX controls. The NWDir and NWSess controls are extremely powerful and help you rapidly and simply develop powerful NDS applications.

If you want more information about Novell's ActiveX controls, see the series of articles Morgan Adair is writing about the NDS controls, starting in June, 1999. You can download these articles from Developer Notes at http://www.developer.novell.com. You can also take the course that corresponds to these articles at Novell's DeveloperNet University on the same website.

You can download the source code for my Extend Schema application at:

http://developer.novell.com/education/library.html

* Originally published in Novell AppNotes


Disclaimer

The origin of this information may be internal or external to Novell. While Novell makes all reasonable efforts to verify this information, Novell does not make explicit or implied claims to its validity.

© Copyright Micro Focus or one of its affiliates