A client recently experienced an issue with InfoPath where they had a repeating table containing a drop down list containing a large number of items. This worked fine for a small number of rows in the repeating table, however memory usage increased and performance degraded as more rows were added.

A workaround, which actually becomes an improvement, is to add a button which launches a dialog that displays a list to select an item from. This has the benefit of not copying the drop down list contents for every row, but also provides an opportunity to enhance the lookup functionality with sorting, filtering, grouping etc.

To hook it all together:

1. Create the user.htm, user.js, taskpane.htm and taskpane.js files as defined below
2. Add user.htm, user.js, taskpane.htm and taskpane.js as Resource files
3. Add a button next to the field/textbox to populate with the selected lookup value. Add an event handler to the button.
4. Include the functions listed below in script.js and copy the code in btnPersonLookup to your lookup button’s event handler
5. Set taskpane.htm as the custom task pane. Go to Tools -> Form Options, Advanced tab. Select taskpane.htm from the list in Task pane location.
6. Modify the reference to “my:Person” in selectSingleNode in btnPersonLookup – this should refer to the field or textbox that you want the selected value to go.
7. Give it a go.

Create a lookup HTML page (this one is for selecting from a list of users):

user.htm

<html>
<head>
 <meta http-equiv="MSThemeCompatible" content="Yes" />
 <title>Select User</title>
 <script src="user.js" language="JavaScript"></script> 
</head>
<body class="dialogBody" onload="OnLoad()" onkeypress="OnKeyPress()">

 <div class="section">
  <div id="text_instructions" class="textbox instructions">
   Select a user from the list below.
  </div>
 </div>

 <div class="section">
  <div>
   <select class="multiLineSelect" name="selUsers" id="selUsers" size="11" onpropertychange="btnOk.disabled=(-1==this.selectedIndex)" ondblclick="SelectUser()"></select>
  </div>
 </div>

 <div class="buttons section">
  <button id="btnOk" type="submit" onclick="SelectUser()">OK</button>
  <button id="btnCancel" onclick="CloseDialog()">Cancel</button>
 </div>

</body>
</html>

Create a Javascript file – user.js – this is referenced by user.htm

user.js

var L_DEFAULTUSERVALUE_Text = "";
var L_DEFAULTUSERNAME_Text = "(None)";

function OnLoad()
{
 if (window.dialogArguments)
  {
  selUsers.innerHTML = "." + window.dialogArguments.useroptions;
  selUsers.outerHTML = selUsers.outerHTML;
  }
}

function SelectUser()
{
 if (false == btnOk.disabled)
  {
  var oUser = selUsers[selUsers.selectedIndex];
  window.dialogArguments.selected   = true;
  window.dialogArguments.selectedUserId = oUser.value;
  window.dialogArguments.selectedUserName = L_DEFAULTUSERNAME_Text != oUser.text ? oUser.text : "";
  CloseDialog();
  }
}

function CloseDialog()
{
 try
  {
  parent.opener = "top";
  parent.close();
  }
 catch (ex)
  {
  window.close();
  }
}

function OnKeyPress()
{
 switch (event.keyCode)
  {
  case 13: /*ENTER*/
   SelectUser();
   break;

  case 27: /*ESC*/
   CloseDialog();
   break;
  }
}


Create a HTML file to load as the default task pane for the form, taskpane.htm, and include this in the HEAD tags

<script src="TaskPane.js" language="JavaScript"></script>

Create another Javascript file, TaskPane.js and include the following function.

function LaunchDialog(sUrl, iWidth, iHeight, oArgs)
{
 window.showModalDialog(sUrl, oArgs, "dialogWidth:"+iWidth+"px; dialogHeight:"+iHeight+"px; edge:Raised; center:Yes; help:No; resizable:No; status:No;");
}

Extra script.js functions

function btnPersonLookup::OnClick(eventObj)
{  
 var oArgs = new Object();
 oArgs.XDocument = XDocument;
 oArgs.selected = false;
 var oNode = eventObj.Source;
 var personNode;
  
 oArgs.useroptions = GetUserOptions('ServiceNo', 'Person_Name', oArgs.selectedUserId, true);
  
 XDocument.View.Window.TaskPanes.Item(0).HTMLDocument.parentWindow.LaunchDialog("user.htm", 400, 345, oArgs);

 if (true == oArgs.selected)
 {  
  oNode.selectSingleNode('my:Person').text =  oArgs.selectedUserName;
 }
}

var g_oXmlUsers = null; 
var g_sUsersOptions = "";
var g_aOptionLookup = new Array();

var L_sSelectUser_Text = "Select user...";
var L_sNoUser_Text = "(None)";

function GetUserOptions( sUsersValueName, sUsersDisplayName, sValue, fFromDialog)
{

 if (g_oXmlUsers == null)
 {
  try
  {
   //original design called a web service, but opted to load from secondary data source rather than
   //repeated calls to web service
   //g_oXmlUsers = callMethod( "GetPersonNames", "http://webserver/webservice/service.asmx", SOAP_GETUSERS_URN, null, null, false );
   //g_oXmlUsers.ownerDocument.setProperty( "SelectionNamespaces", 'xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:z="http://schemas.microsoft.com/sharepoint/soap/directory/"');   
   
   //set the DOM object to the secondary Data Source "GetPersonNames"   
   g_oXmlUsers = XDocument.DataObjects("GetPersonNames").DOM
   g_oXmlUsers .setProperty ('SelectionNamespaces','xmlns:dfs="http://schemas.microsoft.com/office/infopath/2003/dataFormSolution" xmlns:dsf="http://schemas.microsoft.com/office/infopath/2003/dataFormSolution"');
  }
  catch (e)
  {
   g_oXmlUsers = null;
  }
 }
 
 if (g_sUsersOptions == "")
  // the XPath //Persons gets the repeating elements containing the Person Name and User ID - in our
  // scenario the secondary data source was a web service that returned a DataSet
  g_sUsersOptions = BuildOptionsList( g_oXmlUsers, "//Persons", "", sUsersValueName, sUsersDisplayName, "?");
  
 var sUsersOptions = '<option value="">{0}</option>'.replace("{0}", L_sNoUser_Text) +g_sUsersOptions;
 sUsersOptions = sUsersOptions.replace('<option value="'+sValue+'"', '<option selected="true" value="'+sValue+'"');
  
 return sUsersOptions;
}

function BuildOptionsList( xResults, xPath, sPrefix, sValueName, sDisplayName, sValue )
{
 try
 {
  var xNodes = xResults.selectNodes( xPath );
  if( 0 == xNodes.length)
   throw( 0 );

  var xNode;
  var sOptionList = "";
 
  while( xNode = xNodes.nextNode() )
  {
   var sOptionValue = xNode.childNodes[1].text;
   var sOptionDisplay = xNode.childNodes[0].text;;
 
   if( sOptionDisplay )
   {
    g_aOptionLookup[sOptionValue] = sOptionDisplay; // needed for displaying full names in errors etc., as they are not stored in the DOM!
    sOptionList += '<option value="' + sOptionValue + '"' + (sOptionValue == sValue ? ' selected="selected"' : '') +'>' + sOptionDisplay + '</option>';
   }
  }
 
  return( sOptionList );
 }
 catch( e )
 {
  return( "" );
 }
}