SharePoint Lookup field: how does it work and how to add JavaScript event handler function to it?

October 26th, 2009 | Categories: JavaScript, SharePoint, SharePoint Lookup field

Due to a lot of comments and questions to my last year’s post about Attaching functions to SharePoint form fields concerning especially lookup fields on which the demo was made, I’ve decided to take a deeper dive into SharePoint’s Lookup Field and how to manipulate it client-side, since it behaves different in different situations. On the image below you can see two lookup fields – rendered different.

image

Even though new SharePoint is about to go public beta I’ll still show how to handle this in SharePoint 3.0 or 2007


How does it work?

Before explaining how to attach a functon or event handler to a SharePoint lookup field let’s take a look on how SharePoint lookup field is rendered in HTML and which is its behavior – considering client-side.

Lookup to a list with less than 20 items:

If your lookup field is reading from a list that has 19 or less items, the lookup field is rendered like a normal HTML dropdown field (HTML <SELECT> tag). The options in the select tag present possible choices. The text of the option is the lookup value, the value of the option is the lookup ID which SharePoint uses to properly link to the item.

image

The select has a title that equals the field display name, so you can easily use the getField function that is published here. So you can also easily bind an “onchange” event to it.

Lookup to a list with 20 or more items

When a list that the lookup field is reading from a list with 20 and more items, the story becomes more complicated. But only in IE. In FireFox and other browsers the lookup field with 20+ items still gets rendered as a SELECT element. In IE the story starts:

When a Lookup field has 20+ items in IE it gets rendered as a simulation of a combo box (SELECT). It is rendered as an INPUT text box with an image beside. The reason for this is to enable “auto suggest”/filtering feature as you type values in the INPUT textbox.

image

The image next to input element is pretty straightfoward. It has an “onclick” event that triggers the select.

The input field on the other hand has a lot of event handlers bound to it (keypress, keydown, keyup, etc. shown in the screenshot from IE Developer Tools below) that enable the combo box simulation. The input field also has a “Title” attribute that has a value of the field display name.

image

One of the attributes of the input field is also choices which contains all the ids and values of the lookup separated with the “|” character.

image

The first time you press a key or click the image next to the input field, a special function creates a SELECT element and positions it under the INPUT element. Each additional keypress when typing into the input field (typing in the lookup value) functions already attached to the input re-create the input with filtered values. The generated SELECT’s ID is determined with an attribute “opt” on the Input field.

 image

The problem on DOM

This DOM presents an issue to attach an “onchange” event to it since the input field already has so many onkeyup, onkeydown, onkeypress,… events. And since the Select element is generated on the fly it doesn’t exist when the page is rendered and there is only one SELECT element for all the lookup fields with 20+ items in the page. So we can’t attach an event handler to it. At least not easily.

How to attach an event handler to a lookup field?

The good news is that the re IS a way to add a function to trigger when the lookup field value has changed. The input field has another attribute – “optHid” which reveals the ID of a hidden input field that contains the selected lookup ID. That on the other hand is free of any event handlers and we can abuse it.

image

Since we won’t be typing into a hidden field we can’t add an “onkeyup” or “onkeypress” event to it. But we can add an “onpropertychange” event to it. I’ve read somewhere that this is working only in IE, but that’s ok since we are modifying the behavior for IE. Because the hidden field doesn’t have the title attribute we need to refference it indirectly through the input:

document.getElementById(getField('input','[field_display_name']').optHid)

Enough theory – let’s make a practical example

Let’s make a simple example. When you change a lookup field its ID and text should be coppied to another text field.

Because the SharePoint field can be rendered in two different ways we need to predict both. What we will need first is slightly modified function getField.

function getField(fieldType,fieldTitle) {  
    var docTags = document.getElementsByTagName(fieldType); 
    for (var i=0; i < docTags.length; i++) { 
        if (docTags[i].title == fieldTitle) { 
            return docTags[i]; 
        }     }
    return false; 
}

The modification is in bold. What this change does is that the function returns false if it can’t find the specified element. Like that we can use it to specify if the field is INPUT (with 20+ items) or SELECT (with 19- items).

function copyLookupIdAndTxt() {
    if(getField('select','20+ Lookup'))
    {
        //if lookup has 19 or less items - SELECT
        lookupField = getField('select','20+ Lookup');
        lookupSelectedItem = lookupField.options[lookupField.selectedIndex];
        getField('input','Title').value = lookupSelectedItem.value + "-" + lookupSelectedItem.text;
    }
    else
    {
        //if it has 20 or more items - INPUT
        lookupFieldText = getField('input','20+ Lookup');
        lookupFieldId = document.getElementById(lookupFieldText.optHid);
        getField('input','Title').value = lookupFieldId.value + "-"+lookupFieldText.value;
    }
}

 
Next we create a function that will attach the created function to the lookup field (select or hidden input)

function addHandler() {

    if(getField('select','20+ Lookup'))
    {
        getField('select','20+ Lookup').onchange = function() { copyLookupIdAndTxt() }
    }
    else
    {
        document.getElementById(getField('input','20+ Lookup').optHid).onpropertychange = function() { copyLookupIdAndTxt() }
    }
}

 
and finally to make sure the handler gets added let’s add this to the spbodyonloadfunctionnames array

_spBodyOnLoadFunctionNames.push('addHandler');


and the function is adapted to SharePoint’s lookup field.

To recap: the entire code is as follows:

<script type="text/javascript">
function getField(fieldType,fieldTitle) {
      var docTags = document.getElementsByTagName(fieldType);
      for (var i=0; i < docTags.length; i++) {
          if (docTags[i].title == fieldTitle) {
              return docTags[i];
          }
      }
      return false;
} 

function copyLookupIdAndTxt() {
    if(getField('select','20+ Lookup'))
    {
        //if lookup has 19 or less items - SELECT
        lookupField = getField('select','20+ Lookup');
        lookupSelectedItem = lookupField.options[lookupField.selectedIndex];
        getField('input','Title').value = lookupSelectedItem.value + "-" + lookupSelectedItem.text;
    }
    else
    {
        //if it has 20 or more items - INPUT
        lookupFieldText = getField('input','20+ Lookup');
        lookupFieldId = document.getElementById(lookupFieldText.optHid);
        getField('input','Title').value = lookupFieldId.value + "-"+lookupFieldText.value;
    }
}

function addHandler() {
    if(getField('select','20+ Lookup'))
    {
        getField('select','20+ Lookup').onchange = function() { copyLookupIdAndTxt() }
    }
    else 
    { 
        document.getElementById(getField('input','20+ Lookup').optHid).onpropertychange = function() { copyLookupIdAndTxt() }
    }
}

_spBodyOnLoadFunctionNames.push('addHandler');
</script>