Friday, September 21, 2007

Requiring the Contact Selector in InfoPath 2007

The contact selector control in InfoPath makes implementing a user/group lookup field quite easy. However, there are a few downsides. Ben Walters has done a very nice job of listing the upsides and downsides to using this control in his post, Contact Selector the Good and the Bad.

The one aspect of InfoPath (2003) that prevented company wide deployment at my employer was the need to have the InfoPath client in order to fill out a form. Now that Microsoft has implemented Forms Server and allowed the filling of forms via a web browser, many of my colleagues have been banging at my door for custom InfoPath solutions.

The requirement that I have been presented with was to use the Contact Selector control, to make it required, and to only allow one value. After reading the above post by Ben, which references some validation code posted in a comment on the InfoPath Team blog, it became apparent there was no easy solution for browser-based forms.

Below I offer my solution:
  • Domain trust form
  • No digital certificate

public void InternalStartup()
{
 EventManager.FormEvents.Loading +=
  new LoadingEventHandler(FormEvents_Loading);
 EventManager.XmlEvents["/my:myFields/my:contactSelector"].Changed +=
  new XmlChangedEventHandler(contactSelector_Changed);
}

public void FormEvents_Loading(object sender, LoadingEventArgs e)
{
 XPathNavigator mainDS = this.MainDataSource.CreateNavigator();
 if (this.New)
 {
  XPathNavigator contSel =
   mainDS.SelectSingleNode("/my:myFields/my:contactSelector",
    NamespaceManager);
  this.Errors.Add(contSel, "ContactSelectorError",
   "You must select a contact.");
  return;
 }
}

public void contactSelector_Changed(object sender, XmlEventArgs e)
{
 if (e.Site.SelectChildren(XPathNodeType.Element).Count == 1)
 {
  try
  {
   this.Errors.Delete("ContactSelectorError");
  }
  catch { }
 }

 if (e.Site.SelectChildren(XPathNodeType.Element).Count > 1)
  this.Errors.Add(e.Site, "ContactSelectorError",
   "Only one contact can be selected.");

 if (e.Site.SelectChildren(XPathNodeType.Element).Count < 1)
  this.Errors.Add(e.Site, "ContactSelectorError",
   "You must select a contact.");
}

Some notes:
  • "/my:myFields/my:contactSelector" is the XPath to the Contact Selector control. This is the main group, i.e. in my example, it would be:
    <contactSelector>
      <Person>
        <DisplayName />
        <AccountId />
        <AccountType />
      </Person>
    </contactSelector>
  • "ContactSelectorError" is the name of the error being added/deleted. It is not displayed to the user, but rather the internal error name for code references.
  • I only add the error in the loading event for new forms, since an existing form would already have these fields required. An alternative to this.New would be to test the Contact Selector control for a value, and add the error if it's value was equal to the empty string ("").
  • In the function "contactSelector_Changed", you must have a try/catch around the delete statement, because if you attempt to delete an error that does not exist, an error will be thrown.
  • If you have multiple contact selector controls on your form, you will need a seperate "onChanged" event for each control, and I would suggest simply changing the name of the error from "ContactSelectorError" to something like "ContactSelectorErrorEmployee", and ensure each contact selector control has a unique name for its error. You will also need as many "this.Errors.Add" lines in the loading event as you have contact selector controls you want validated.
Enjoy!
--andrew

8 comments:

  1. Great Post Andrew.
    Great Help too !

    ReplyDelete
  2. Thanks for the pointer Andrew, I was playing around with the validating event with no joy & thought that due to not being able to force a postback on the Contact Selector it wasn't worth bothering trying the changed event until I came across your post.

    ReplyDelete
  3. Dear Andrew

    I hope you are well. You mentioned the following

    Domain trust form
    No digital certificate

    The form throws an error if i do not put full trust and digital certificate. What´s the workround to solve this issue. I look forward to hearing from you. My email is rsvacchi@googlemail.com

    Cheers

    Rod

    ReplyDelete
  4. Hi Andrew,
    You might find this no code solution useful for next time....
    Alana

    http://www.myriadtech.com.au/blog/Alana/Lists/Posts/Post.aspx?ID=13

    ReplyDelete
  5. Thanks Alana, you're solution is a very elegant no-code option!

    ReplyDelete
  6. I modified this to clean it up a bit. I think as written it could add multiple errors to the stack, say by adding 2 contacts, then going back and adding a third. I also update the logic for detecting errors to avoid the empty catch.


    void ApprovingManager_Changed(object sender, XmlEventArgs e)
    {
    try
    {
    // get the count of managers
    int managerCount = e.Site.SelectChildren(XPathNodeType.Element).Count;

    // If there is an existing error, clear it out to prepare for an error condition
    // and ensure that there is only one error on the stack
    if (this.Errors.GetErrors(_approvingManagerErrorName).Length > 0)
    {
    this.Errors.Delete(_approvingManagerErrorName);
    }

    // Check for and react to too few or too many errors
    if (managerCount > 1)
    {
    this.Errors.Add(e.Site, _approvingManagerErrorName, Properties.Resources.TooManyApprovingManagerError);
    }
    else if (managerCount < 1)
    {
    this.Errors.Add(e.Site, _approvingManagerErrorName, Properties.Resources.MissingApprovingManagerError);
    }
    }
    catch (Exception ex)
    {
    ProcessException(ex);
    }
    }

    ReplyDelete
  7. Hi Andrew, the contact selector in Infopath 2010 is not working on the browser as in 2007 version. do you know if this is a bug? I tried everything you mentioned.
    thanks

    ReplyDelete
  8. atavi,
    Unfortunately, I am unfamiliar with InfoPath 2010. I have recently changed jobs, and technology focus so I no longer am dealing with SharePoint on a day-to-day basis. The last version of SharePoint/InfoPath that I used regularly was 2007.

    You might check this forum post on MSDN for more information regarding the Contact Selector in InfoPath 2010: http://social.msdn.microsoft.com/Forums/en-US/sharepointinfopath/thread/fb18b122-402a-457e-9779-c4c32432acaf/

    ReplyDelete