Monday, March 29, 2010

Creating a Multiple selection List Box in browser enabled InfoPath form

InfoPath 2007 browser enabled forms (a part of InfoPath Form services) does not support Multi-select List Box control. Read some other InfoPath Form services limitations here: http://office.microsoft.com/en-us/infopath/HA102040851033.aspx

I had a tough time explaining and convincing clients about this and they got another excuse of blaming it all on Microsoft, so I finally decided to solve this problem on my own.

The overall solution looks like this:
a. Create a  custom Data structure in InfoPath 2007 browser enabled form.
b. Bind a Repeating Table with our custom Data Structure and tweak the look and feel to make it look more like a Multi-select List box.
c. Use Form Load event for programmatically loading Multi-Select List options.
d. On Submit button click, Save the multi-selected List box items as a semi-colon separated string in another InfoPath text field.

I will explain the above solution Step-by-Step with snapshots to make it more easier to implement.

a. Create a Data Structure in InfoPath 2007: See the snapshot below:

i) In this case, my field name for storing the values selected in the multi-select List items is WorkcenterAssignment. You can give any name you want to the Group and the field. Values selected by the user will be stored in this field.

ii) For MultiSelectOptions, follow the same structure as mentioned below:
-MultiSelectOptions(Repeating Group) - Will be applied as a repeating table in Step b.
    - selectedOption (True/False Boolean field) - This will be converted into Checkbox on the UI
    - optionDescription (Text string field) - This will be used for storing the multi-select List Item options

b. Bind a Repeating Table with the Data Structure
i) Right click on the above schema -> Choose Repeating table as follows:

ii) It will look like this on the InfoPath form:
iii) Format it to look more like a multi-select ListBox on your form:
Note: You can also wrap it inside a Table with a fixed width and fixed height, so that Listbox items can act like scroll bar for options)

c. Programmatically loading Multiple items in the List Box from SharePoint List/Library:
Now that our schema and control are all set, we need to load values into multiselect Listbox control.
The key event here is the InfoPath form load event. 

The strategy here is to bind the repeating table created above programmatically using Visual Studio for Applications. I prefer to write the code in C# .NET.

Here is my Form Load event, I call the generic method LoadMultiSelectListBox() method, copy the code below:

 public void FormEvents_Loading(object sender, LoadingEventArgs e)
        {
            try
            {
                this.Errors.DeleteAll();

                    XPathNavigator root = MainDataSource.CreateNavigator();
                    XPathNodeIterator listItems = root.Select("/my:Request/my:WorkCenterAssignments/my:MultiSelectOptions", NamespaceManager);

                    if (listItems.Count == 1)
                    {
                        //Load Multi-Select List Box
                        LoadMultiSelectListBox();
                    } 
            }
            catch (Exception exp)
            {
                ThrowException(exp);
            }
        }



Here is the code for Loading Multi-Select items into the repeating table programmatically. I first need to get all my List item options from my SharePoint List/Library, I have created a XML Receive Data connection in InfoPath, you can alternatively get all the options from SharePoint List/Library using the SharePoint object model, copy code below:

        private void LoadMultiSelectListBox()
        {
            try
            {
                this.Errors.DeleteAll();

                //Query Get Approvers From SharePoint List data connection
                FileQueryConnection queryConnection = (FileQueryConnection)this.DataConnections["WorkCenterXML"];
                queryConnection.Execute();

                //Get all nodes from the Work Center XML
                XPathNavigator connectionNav = DataSources["WorkCenterXML"].CreateNavigator().SelectSingleNode("//rs:data", this.NamespaceManager);
                XPathNodeIterator allItems = connectionNav.Select("//z:row", this.NamespaceManager);

                //Iterate via all records
                foreach (XPathNavigator item in allItems)
                {
                    AddMultiSelectListItems(item);
                }

                //Delete the first row of InfoPath Repeating table programmatically
                if (GetCurrentXPathNav("/my:Request/my:WorkCenterAssignments/my:MultiSelectOptions[1]") != null)
                {
                    GetCurrentXPathNav("/my:Request/my:WorkCenterAssignments/my:MultiSelectOptions[1]").DeleteSelf();
                }
            }
            catch (Exception exp)
            {
                ThrowException(exp);
            }
        }

This method adds values into our custom repeating table data structure:

        private void AddMultiSelectListItems(XPathNavigator item)
        {
            try
            {
                this.Errors.DeleteAll();

                //Get Reference to Multi-Select List Options
                XPathNavigator Item = GetCurrentXPathNav("/my:Request/my:WorkCenterAssignments/my:MultiSelectOptions");

                //Create a new Item node for Multi-Select List items
                XPathNavigator newItemNode = null;

                if (Item != null)
                {
                    //Clones the new item w.r.t repeating table structure
                    newItemNode = Item.Clone();
                }

                //Get Reference to Option description
                XPathNavigator optionDescription = newItemNode.SelectSingleNode("/my:Request/my:WorkCenterAssignments/my:MultiSelectOptions/my:optionDescription", this.NamespaceManager);

                //Set Option description values
                optionDescription.SetValue(item.GetAttribute("ows_Workcenter", String.Empty));

                //Add a new Item in the Multi-Select List options
                Item.InsertAfter(newItemNode);

                optionDescription = null;
                newItemNode = null;
            }
            catch (Exception exp)
            {
                ThrowException(exp);
            }
        }

Updated as on 5th April, 2010: GetCurrentXPathNav is a generic method used to get the handler on the particular node/field in InfoPath, copy the code below:

///
/// Get XPath Navigator gets the Navigator object for the passed in XPath expression.
/// This method should be used as a helper function at all times
///
/// XPathNavigator
public XPathNavigator GetCurrentXPathNav(String XPath)
{
    XPathNavigator DataSource = MainDataSource.CreateNavigator();
    XPathNavigator XPathNav = DataSource.SelectSingleNode(XPath, NamespaceManager);
    return XPathNav;
}

Preview your InfoPath form, your control should load all the options dynamically and look like this:

d. Saving the values selected from the Multi-select List box item on saving the form:
Assume user selects multiple items, we will have to save the values selected in our Multi-select Listbox control using a separate field in the InfoPath form, in this example, we created the WorkcenterAssignment text field in Step a.

Again we will use the programming approach to iterate through the repeating table nodes, identify the list items selected and store them as semi-colon (;) separated values in the WorkcenterAssignment InfoPath field.

WorkcenterAssignment can be further promoted as a SharePoint column using Property Promotion feature in the InfoPath form. To know more on publishing and deployment of InfoPath forms, see my detailed post on Publishing and Deploying browser based InfoPath forms


Here is the code to Save the selected Multi-select Listbox items:

private void SaveMultiSelectListItems()
        {
            try
            {
                this.Errors.DeleteAll();

                XPathNavigator root = MainDataSource.CreateNavigator();
                XPathNodeIterator listItems = root.Select("/my:Request/my:WorkCenterAssignments/my:MultiSelectOptions", NamespaceManager);

                String optionsSelected = String.Empty;
                StringBuilder selectedListItems = new StringBuilder(String.Empty);

                int counter = 0;

                while (listItems.MoveNext())
                {
                    optionsSelected = listItems.Current.SelectSingleNode("my:selectedOption", NamespaceManager).Value;

                    //Check whether the Check box against the option was selected or not
                    if (Boolean.Parse(optionsSelected) == true)
                    {
                        if (counter == 0)
                        {
                            selectedListItems.Append(listItems.Current.SelectSingleNode("my:optionDescription", NamespaceManager).Value);
                        }
                        else
                        {
                            selectedListItems.Append("; " + listItems.Current.SelectSingleNode("my:optionDescription", NamespaceManager).Value);
                        }

                        //increment the counter
                        counter++;
                    }

                    //Clear variable
                    optionsSelected = String.Empty;
                }

                //Set all the multi-selected List Box values
                GetCurrentXPathNav("/my:Request/my:WorkCenterAssignments/my:WorkcenterAssignment").SetValue(selectedListItems.ToString());
            }
            catch (Exception exp)
            {
                ThrowException(exp);
            }
        }


I think the above code is self-explanatory. This is how we simulate a Multi-select List control using the repeating table model and dynamic options loading in a browser enabled InfoPath form.

Feel free to share your comments.


References: InfoPath Team blog