Thursday, 15 January 2009

InfoPath 2007: Showing/hiding optional sections

At the moment I am developing InfoPath forms. As most of the Microsoft products, the first version of a product is, let's say, crap. The second version, starts to look like what it should be, and the third version is more or less what it should be. Well, InfoPath 2007 is the second version of the InfoPath products, so it's not always that intuitive.
I worked with optional sections in InfoPath 2007, and I really hate the way of dealing with hiding/showing these sections based on other actions (e.g. selecting 'yes' for a field value, which should result in showing an optional section for additional information to fill in). So I decided to make life a bit easier and may be you like it as well, so I shared this. I created in VSTA an object that do the show/hide actions for me.

1. Public enumeration
First of all, create a public enumeration for targeting optional sections. Place it straight under the Namespace tag (not in the FormCode class). By the way, in this example, we assume that the namespace is called MyCustomNamespace.

'''
''' Public enumeration for targeting optional sections
'''

'''
Public Enum TargetSection
PartnerDetails
CurrentStudyDetails
ClubMemberships
End Enum

2. Create object class
Create an object class to hold optional section items. This class contains a couple of private fields, one public property and some (public) methods.
The ViewContext represents the Context Id, XPath represents the XPath query string and XOptional is the Control Id. All are generated by InfoPath. The target section is a value of the enumeration, created in step one.

''' <summary>
''' Class for easy hide and show of Optional Sections
''' </summary>
''' <remarks></remarks>
Public Class OptionalSection

Private mViewContext As String
Private mXPath As String
Private mXOptional As String
Private mTargetSection As TargetSection
Private mVisible As Boolean

Public ReadOnly Property TargetSection() As TargetSection
Get
Return mTargetSection
End Get
End Property

''' <summary>
''' Constructor
''' </summary>
''' <param name="viewContext">Context id</param>
''' <param name="xPath">XPath query string</param>
''' <param name="xOptional">Control id</param>
''' <param name="targetSection">Pinpoint to target optional
''' section</param>
''' <remarks></remarks>
Public Sub New(ByVal viewContext As String, ByVal xPath As String, _
ByVal xOptional As String, _
ByVal targetSection As TargetSection)
mViewContext = viewContext
mXPath = xPath
mXOptional = xOptional
mTargetSection = targetSection
mVisible = False
End Sub

''' <summary>
''' Show the optional section
''' </summary>
''' <param name="myForm">The current FormCode class</param>
''' <remarks>Check if section is already visible. Infopath crashes if
''' it is already visible</remarks>
Public Sub Show(ByVal myForm As MyCustomNamespace.FormCode)
If Not mVisible Then
myForm.CurrentView.ExecuteAction( _
ActionType.XOptionalInsert, mXOptional)
mVisible = True
End If
End Sub

''' <summary>
''' Hide the optional section
''' </summary>
''' <param name="myForm">The current FormCode class</param>
''' <remarks>Check if section is already invisible. Infopath crashes
''' when trying to hide an invisible</remarks>
Public Sub Hide(ByVal myForm As MyCustomNamespace.FormCode)
If mVisible Then
Dim node As XPathNavigator = _
myForm.MainDataSource.CreateNavigator() _
.SelectSingleNode(mXPath, myForm.NamespaceManager)

myForm.CurrentView.SelectNodes( _
node, node, mViewContext)
myForm.CurrentView.ExecuteAction( _
ActionType.XOptionalRemove, mXOptional)
mVisible = False
End If
End Sub
End Class

3. Add some code
Add the following imports at the top of the source code:

Imports System.Collections.Generic

Create a private variable in the FormCode class that holds a list of optional section objects:

Private OptionalSections As List(Of OptionalSection)

4. Create optional section list
Create a method in the FormCode classto create a list of the just created optional section object. In this example, the form contains three optional sections which are needed to be shown/hide. For each optional section you need to show/hide, just create an object in this place and add it to the object list.

'''
''' Create and fill the optional sections list
'''

'''
Private Sub CreateOptionalSectionList()
OptionalSections = New List(Of OptionalSection)
OptionalSections.Add( _
New OptionalSection("CTRL31", _
"/my:MyFields/my:PersonalInformation/my:Partner", _
"Partner_33", _
TargetSection.Partner))
OptionalSections.Add( _
New OptionalSection("CTRL111", _
"/my:MyFields/my:Education/my:CurrentStudies/my:CurrentStudy", _
"CurrentStudy_121", _
TargetSection.HuidigeStudieDetails))
OptionalSections.Add( _
New OptionalSection("CTRL282", _
"/my:MyFields/my:Others/my:ClubMemberships", _
"ClubMemberships_269", _
TargetSection.Verenigingen))
End Sub

The information of the parameters, you can find at the advanced tab on the section properties dialog. Here you can find the ViewContext and the XmlToEdit for xOptional. The XPath string is easy to get. Right-click on your group on the data source list (this group represents the optional region) and select Copy XPath in the context menu. The XPath query is now on your clipboard.

Call the CreateOptionalSectionList during the InternalStartup method of the FormCode class.
5. GetOptionalSection

Create a function to retrieve the optional section you are interested in:



''' <summary>
''' Get the target optional section
''' </summary>
''' <param name="section">Target optional section</param>
''' <returns>Optional Section object</returns>
''' <remarks></remarks>
Private Function GetOptionalSection( _
ByVal section As TargetSection) As OptionalSection

For Each os As OptionalSection In OptionalSections
If os.TargetSection = section Then
Return os
End If
Next
Return Nothing
End Function

6. Show/hide optional section

Now you are ready for easily showing and hiding optional regions. For example, if the field MemberOfClub changed (in this example a Boolean field), you want to fire an event for showing or hiding a corresponding region where the user can fill in his club membership information.



''' <summary>
''' Show or hide the ‘Club membership’ optional section
''' </summary>
''' <param name="sender"></param>
''' <param name="e"></param>
''' <remarks></remarks>
Public Sub MemberOfClub_Changed( _
ByVal sender As Object, ByVal e As XmlEventArgs)

Try
If e.NewValue = "true" Then
GetOptionalSection(TargetSection.ClubMemberships).Show(Me)
Else
GetOptionalSection(TargetSection.ClubMemberships).Hide(Me)
End If
Catch ex As Exception
End Try
End Sub

This is the way I am dealing with a lot of optional sections I want to show/hide.

2 comments:

westerdaled said...

I like the way your blog is layed out and your ethos- you've reminded me to update mind.

I am building a form with around 30 separate check boxes on the left and for each checked. I want a newly visible section to appear whereby the user is able to select (or check) one or more items from a (sp)list.

I could use your approach using VSTA but doesn't that stop using the browser enabled form.

Robje said...

Thanks.
I am not sure. You can add some code to your form, but the limitations are more on control type level. You cannot use all controls on a InfoPath form when you want to use it in a browser.
But programming should be a problem. It will be more deploying the dll and so, but I don't have experience with that yet.