Using SPServices with jQueryUI’s Autocomplete Function on InfoPath Forms in SharePoint

Yes, that title says using jQueryUI on InfoPath forms in SharePoint. InfoPath forms are fantastic, except when they just aren’t quite able to do what you want. While InfoPath can help you take your SharePoint list forms to a whole new level, there may be times when you want to add some behavior to those forms which InfoPath simply doesn’t offer.

Guess what? jQuery and jQueryUI can come to the rescue in some of those cases, just as it can with the standard list forms.

I recently worked with a client to do a relatively simple thing, but it was a huge hit. They had a SharePoint list had a Single line of text column, to which the user could add whatever they wanted. However, there was a desire to make suggestions as the user typed, based on the contents of a different list, which had thousands of items.

A dropdown doesn’t make sense in that situation, because there are simply too many items. They also wanted to show matches to what the user had typed, regardless where those characters occurred in the list item.

This is a perfect situation in which to use the idea of autocomplete. We all are familiar with this idea, if not the actual term. You probably see it in action every single day as you use a search engine. As you type your search term(s), the search engine shows suggestions, probably based on some fairly sophisticated algorithms.

Here’s an example from Bing:

image

and from Google:

image

(I can’t help but point out that Google was more on the mark in guessing that I was searching for SPServices, but hey, who’s counting?)

When InfoPath forms are rendered in the browser, they are built of ordinary markup just like any other page, albeit fairly complicated markup, driven by a lot of script.  That doesn’t mean that you can’t add some script of your own as well to add additional capabilities. There are a few peculiarities to this situation, though, which you need to handle.

Here’s a slightly dumbed down version of the script we ended up with.

window.onload = function() {
  window.setTimeout(readyCall, 1000);
}

function readyCall(){

  var externalParties = [];

  $().SPServices({
    operation: "GetListItems",
    listName: "External Parties",
    CAMLViewFields: "",
    async: false,
    completefunc: function (xData, Status) {
      $(xData.responseXML).SPFilterNode("z:row").each(function() {
        externalParties.push($(this).attr("ows_Title"));
      });
    }
  });

  //<input tabIndex="0" title="" class="q_zwfUqJo2fRthHnM4_0 as_zwfUqJo2fRthHnM4_0 b9_zwfUqJo2fRthHnM4_0" id="ctl00_m_g_a226da68_1383_40e3_8410_1ada27d49dcf_FormControl0_V1_I1_T2" aria-invalid="true" style="position: relative;" onfocus="return (TextBox.OnFocus(this, event));" onblur="return (TextBox.OnBlur(this, event));" onpropertychange="return (TextBox.OnPropertyChange(this, event));" type="text" OriginalId="V1_I1_T2" FormId="ctl00_m_g_a226da68_1383_40e3_8410_1ada27d49dcf_FormControl0" ViewDataNode="3" direction="ltr" wrapped="true" ScriptClass="TextBox" VCARD_NAME="91161f891e59461042587839b2504693728ce05a" ?=""/>
  $("input[id$='FormControl0_V1_I1_T2'], input[id$='FormControl0_V1_I1_T3']").autocomplete({
    source: externalParties,
    minLength: 3
  });
}

When InfoPath forms load in the browser, they don’t load with the rest of the page, but instead they are loaded slightly afterward in what amounts to an asynchronous load. Because of that, using $(document).ready(), our trusted jQuery friend, doesn’t work. Instead, as you can see in lines 1-3, we simply wait for 1000 milliseconds (1 second) before we run our script. We found that this was an adequate amount of wait time for our particular form; you might need to adjust this.

In lines 9-19, we use my SPServices library to call the Lists Web Service, using the GetListItems operation. This operation simply reads items from the list based upon the criteria you specify. Once we have the data, we push each of the Title column values into an array called externalParties.

Finally, we call the jQueryUI function autocomplete, using two selectors. In line 21 above, which is commented out, you can see an example of the markup for one of the input elements rendered in the InfoPath form. One of the hardest parts of all of this was to figure out what selector to use. We settled on looking for an input element where the id contained ‘FormControl0_V1_I1_T2’.  (We actually added the autocomplete behavior to two columns in the form, thus the second selector for ‘FormControl0_V1_I1_T3’.)

We added this script into the newifs.aspx and editifs.aspx pages using a trusty Content Editor Web Part (CEWP). Since this was SharePoint 2010, and because it makes for far better code management, we stored the script in a separate file and referenced it using the Content Link.

Bingo-bango, we had a nice, little additional piece of functionality which made the users very happy. This is an example where thinking about all of the tools at your disposal and how you might glue them together into the right solution to get the job done can be the right approach rather than a lot of custom coding.

<UPDATE dateTime=”2011-08-25T23:51″>

My partner in crime for this exercise was Marcel Meth (@marcelmeth), and he’s done a post on it, too, which you can read here. I was able to steal his image of the results, which I’ve added above. Note that the image is not showing the real data but our test data, which was simply a list of the 3000 or so major cities and towns in the US.

</UPDATE>

<UPDATE dateTime=”2011-08-26T09:25″>

I got a question in the comments below about how we added the script to the InfoPath page, and I wanted to add those details here.

  • First, we opened each form in the browser, which launched it in a dialog box.
  • We got the actual URL of the page in the dialog by right-clicking and looking at its properties. The NewForm was newifs.aspx and the EditForm was editifs.aspx (as I mention above).
  • We opened the form page directly in a new browser window and used the toolpaneview=2 Query String parameter trick to put the page into edit mode. This allows you to edit a list form page and add Web Parts to it.
  • We added the CEWP, and put the reference to our script file in the Content Link.
  • Apply and done.

</UPDATE>

Similar Posts

128 Comments

  1. Hi Marc,
    I got another problem, hope you can help me…pls,
    I finished your tut, and it’s working, but..whenever this form postback (by click a button, or textchanged of another textbox,..) the autocomple faill…!
    Can you give me an advice to solve it…?
    Thank you…

      1. I’ve encountered this too, and my conclusion is that this autocomplete doesn’t work very well with postbacks. What I think is happening is that you’re binding the autocomplete function after Infopath has bound all of its event handlers. And they execute in the order they were added. So the postback happens before the autocomplete has a chance to finish autocopmlete-ing. I haven’t found a workaround, other than to remove anything from the Infopath form (like Rules) that would cause a postback. Then you’re left with implemting all of your “rules” with your own custom javascript or jquery. There may be some way to change the order of event handlers after they’re created but I haven’t been smart enough to figure that out.

        1. I didn’t do anything more magical than exactly what I show in the post and it worked fine. There are so many things that you might be doing in your form that it’s very hard for me to even imagine what might be causing things to not be working for the two of you.

          Autocomplete itself is just a way to populate an input element. If a postback is firing in the midst of that before the user can select a value, then of course it won’t work, as the autocomplete won’t actually happen.

          M.

  2. Thanks Marc! I wasn’t necessarily asking you to solve this. I don’t think there are issues with SPServices or even jQueryUI here. I have gotten this solution to work several times as shown above. Just pointing out for Phong’s benefit that anytime you add things to your Infopath form (e.g. rules) that cause postbacks, event handlers that you add post-page-load are likely to run into problems because the DOM vanishes before they execute. My solution so far has been to avoid Infopath rules that cause postback and handle them with custom code instead. Some Infopath rules will cause a postback every time a field is defocused, regardless of whether the rule is attached to or acts on that field.

  3. I have an “Employee” List that contains 2 fields (Department & Job Title) in SharePoint 2010. These 2 fields are to be Cascading Dropdowns. One I first created the “Employee” List and added a CEWP to it with the appropriate jQuery & SPServices code for the cascade, it worked great.

    However, once I edited the Form in InfoPath (and thus creating the InfoPath Web part & newifs.aspx page), the SPServices code for the cascade no longer works. There are no errors, it just doesn’t do the cascading drop downs.

    Is this because of what you said in this post? “When InfoPath forms load in the browser, they don’t load with the rest of the page, but instead they are loaded slightly afterward in what amounts to an asynchronous load. Because of that, using $(document).ready(), our trusted jQuery friend, doesn’t work.”

    This is the code I have in the CEWP:

    $(document).ready(function() {

    $().SPServices.SPCascadeDropdowns({
    relationshipList: “JobTitles”,
    relationshipListParentColumn: “Department”,
    relationshipListChildColumn: “Title”,
    parentColumn: “Department”,
    childColumn: “Job Title”,
    debug: true
    });
    });

    Any help is greatly appreciated.

    1. Nick:

      SPCascadeDropdowns is built to work with the default list forms. If you’ve switched to InfoPath, it won’t work, as the markup is considerably different.

      I’m no InfoPath expert, but can’t you do cascading drop downs in InfoPath natively?

      M.

      1. ahh, ok gotchya – that makes sense.
        Yeah I was holding off implementing the Cascading DropDowns in InfoPath cause your SPServices way is sooo much easier :)
        But looks like I’ll be using InfoPath directly instead.

        Thanks for the clarification though – much appreciated!

  4. Hi Mark,
    Thanks for this great post..
    Actually I was also looking for validation of this auto-complete text field as in it doesn’t except any inputs other than that available in the data source and if the user attempts to save form with some custom value than it gives some kind of error message..
    I am not good with Jquery scripts so any kind of help would be appreciated.Thanks.

    1. Rishi:

      The jQueryUI autocomplete just assists you to get a value into the InfoPath field. If that field is set to have some validation that the value doesn’t pass, you’ll get an error.

      M.

  5. Hi Marc

    I’m having an issue attempting this on an Infopath form published to a form library. I’ve used the following code to no avail:

    window.onload = function(){
    window.setTimeout(readyCall, 1000);
    }

    function readyCall(){

    //Variable for Array where Title column values will be pushed to.
    var Requestors = [];

    //Variable for the Requestor Text Field.
    var RequestorField = “input[id$=’FormControl0_V1_I1_T1′]”

    //Call the SPServices library.
    $().SPServices({
    operation: “GetListItems”,
    listName: “Final List of PPMC”,
    CAMLViewFields: “”,
    async: false,
    completefunc: function (xData, Status){
    $(xData.responseXML).SPFilterNode(“z:row”).each(function(){
    Requestors.push($(this).attr(“ows_Title”));
    }); //Close .each(function())
    } //Close function (xData, Status)
    }); //Close SPServices

    //Input field from form.
    $(RequestorField).autocomplete({
    source: Requestors,
    minLength: 3,
    focus: function(event, ui){
    $(RequestorField).val(ui.item.value);
    return false;
    }
    }); //Close autocomplete

    window.setTimeout(readyCall, 15000);
    } //Close readyCall function

    It doesn’t seem to work at all!

    1. Vince:

      Note that I was pretty clear about the fact that this is a hack. It worked in our case and several others have had success with it, but it’ll undoubtedly require debugging on your end.

      M.

  6. Hello Marc…
    This is what i was looking for like a month…
    Thanks for sharing it…
    I need ur assistance in finding the ID of the field in Infopath form webpart like the one u have mentioned it here(input[id$=’FormControl0_V1_I1_T3′]”).
    I kind of tried as many ways as possible to get the id of my infopath form field.But hard luck.
    Is there any specific workaround for this.

    1. Aravindan:

      There’s no “workaround’. You need to use a DOM inspector like IE’s Developer Tools or Firebug to identify the id of the element.

      M.

  7. Hi Marc,

    Been reading your posts and really enjoying them but can’t figure this out. I’m a newbie to jquery, while I’m confident it’s something I’m doing , I’d like your input on if my issue is a restriction of SharePoint 2007, IE8 or the fact that this is not on a list’s new/edit page. This is on a publishing page, but I’d assume it’d work the same…I know, don’t assume.

    Apologize that this isn’t about InfoPath, but it was the only example I found of yours that used the jquery-ui autocomplete vs. spautocomplete. To give you some background I have a DVWP and a couple of textboxes on a page. The textboxes are used as filters based on user input. It’d be great to have the autocomplete work on those. The issue I have with SPAutoComplete is that it doesn’t disappear when I click anything else, it won’t let me cursor down through the selection and it won’t let me type in just a few letters (forces me to choose something). Jquery-UI allows me to do that.

    Unfortunately, I can’t get the SPServices to fire and attach to the input field. Any thoughts?

    $(document).ready(function() {

    var externalKeywords = [];

    $().SPServices({
    operation: “GetListItems”,
    listName: “Keywords”,
    CAMLViewFields: “”,
    async: false,
    completefunc: function (xData, Status) {
    $(xData.responseXML).SPFilterNode(“z:row”).each(function() {
    externalKeywords.push($(this).attr(“ows_Title”));
    });
    }
    });

    $(“input[id$=’autocomplete’]”).autocomplete({
    source: externalKeywords,
    minLength: 3
    });

    });

    REALLY appreciate your help!

    1. James:

      Your code looks OK to me. Have you looked at the Net traffic to see if you’re getting valid data back from the SPServices call? The easiest way to do that is to add an alert(xData.responseText) inside the completefunc.

      M.

      1. I just tried that and got a soap error. Then replaced my list name with its guid and got data. Still no autocomplete though. Thx for the quick response.

          1. Yes, here is a subset of code generated for the 105 items in the list.

            <?xml version="1.0" encoding="utf-8"?><soap:Envelope 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"><soap:Body><GetListItemsResponse xmlns="http://schemas.microsoft.com/sharepoint/soap/"><GetListItemsResult><listitems xmlns:s='uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882'
                 xmlns:dt='uuid:C2F41010-65B3-11d1-A29F-00AA00C14882'
                 xmlns:rs='urn:schemas-microsoft-com:rowset'
                 xmlns:z='#RowsetSchema'>
            <rs:data ItemCount="105">
               <z:row ows_Attachments='0' ows_LinkTitle='AUB' ows_MetaInfo='1;#' ows__ModerationStatus='0' ows__Level='1' ows_ID='1' ows_owshiddenversion='1' ows_UniqueId='1;#{160D8D64-3010-4C00-B129-548E83508879}' ows_FSObjType='1;#0' ows_Created_x0020_Date='1;#2013-05-09 14:05:21' ows_Created='2013-05-09 14:05:21' ows_FileLeafRef='1;#1_.000' ows_FileRef='1;#Epic/Handouts/Lists/Keywords/1_.000' />
               <z:row ows_Attachments='0' ows_LinkTitle='Allergy' ows_MetaInfo='2;#' ows__ModerationStatus='0' ows__Level='1' ows_ID='2' ows_owshiddenversion='1' ows_UniqueId='2;#{99FE71A9-80FE-40E9-96EE-2DDE3648BB31}' ows_FSObjType='2;#0' ows_Created_x0020_Date='2;#2013-05-09 14:05:21' ows_Created='2013-05-09 14:05:21' ows_FileLeafRef='2;#2_.000' ows_FileRef='2;#Epic/Handouts/Lists/Keywords/2_.000' />
               <z:row ows_Attachments='0' ows_LinkTitle='Anticoagulation' ows_MetaInfo='3;#' ows__ModerationStatus='0' ows__Level='1' ows_ID='3' ows_owshiddenversion='1' ows_UniqueId='3;#{DCC2BEAB-A7C0-497E-A2E1-B241367393A3}' ows_FSObjType='3;#0' ows_Created_x0020_Date='3;#2013-05-09 14:05:21' ows_Created='2013-05-09 14:05:21' ows_FileLeafRef='3;#3_.000' ows_FileRef='3;#Epic/Handouts/Lists/Keywords/3_.000' />
               <z:row ows_Attachments='0' ows_LinkTitle='Asthma' ows_MetaInfo='4;#' ows__ModerationStatus='0' ows__Level='1' ows_ID='4' ows_owshiddenversion='1' ows_UniqueId='4;#{D4046EC1-023C-4EE9-8C66-6B37F45B0FAB}' ows_FSObjType='4;#0' ows_Created_x0020_Date='4;#2013-05-09 14:05:21' ows_Created='2013-05-09 14:05:21' ows_FileLeafRef='4;#4_.000' ows_FileRef='4;#Epic/Handouts/Lists/Keywords/4_.000' />
               <z:row ows_Attachments='0' ows_LinkTitle='Audiology' ows_MetaInfo='5;#' ows__ModerationStatus='0' ows__Level='1' ows_ID='5' ows_owshiddenversion='1' ows_UniqueId='5;#{4B8E2DAE-0161-4196-B817-F16EB4ED2563}' ows_FSObjType='5;#0' ows_Created_x0020_Date='5;#2013-05-09 14:05:21' ows_Created='2013-05-09 14:05:21' ows_FileLeafRef='5;#5_.000' ows_FileRef='5;#Epic/Handouts/Lists/Keywords/5_.000' /> </rs:data> </listitems></GetListItemsResult></GetListItemsResponse></soap:Body></soap:Envelope>
            
            
            1. James:

              It looks like you’re getting the values back in ows_LinkTitle, so it’s probably something about the way you’re passing the values into the autocomplete or the selector.

              M.

              1. Here is how I am assigning:

                $(“input#autocomplete”).autocomplete({
                source: externalKeywords,
                minLength: 2
                });

                Do I need other attributes on the input field like “title” or “name”? I’ve tried these two methods for passing the values:

                1. Found on your InfoPath Blog entry page: externalKeywords.push($(this).attr(“ows_LinkTitle”));

                2. Found on this page which references yours: http://www.itidea.nl/index.php/jquery-spservices-and-autocomplete/
                externalKeywords[k] = $(this).attr(‘ows_LinkTitle’); k = k + 1;

                Either way, I thought Id be able to alert some data within the function, once for each list item. I realize it would be as many times as items, but the fact that I wasn’t able to made me wonder if it’s the autocomplete assignment to the input field or the fact that it’s not even iterating on the each function.

                1. You were right. I changed the selector to:
                  $(“#txtKeywords”).autocomplete({source:externalKeywords,minLength: 3});

                  and it took just fine! Appreciate your help and feedback!

  8. Hi Marc,

    Not sure if you are still monitoring this page…first off thank you for the solution description it was the place I finally found out where to construct the jQuery selector to address the Infopath generated fields. I saw your code: id$=’FormControl0_V1_I1_T3 and that was the key to getting me unstuck.

    But…my solution was working for several days and one day it just stopped working. The only thing that had changed is that I had messed with some fields in Sharepoint and also in Infopath and used the preview tool in Infopath. Actually all I did was make two of the fields mandatory (Cannot be blank). I first went into my Sharepoint list and made the fields mandatory…and for some reason that didn’t seem to take so I opened the list up in Infopath, let it refresh the fields…opened up one of the fields and it looked right (Cannot be blank checked) … so I just republished the site back out to SP just to be safe. After that the jQuery code stopped working.

    After testing that jQuery was still working I looked at the ID tags using Firefox inspector and realized that the ID tags had changed from:

    …..FormControl0_V1_I1_T1
    to
    …..FormControl0_V1_I1_S1_I1_T1

    I’m not sure why those ID tags would have added in that _S1_I1. I updated all my code to reflect the new ID tags and things are working again.

    Do you know why the field ID tags would have changed on me? I’m worried that I don’t know what made them change. Was it opening the form back up in Infopath and republishing?? I’m worried that if someone comes in behind me some day and does something that makes the ID tags change again my code will break.

    Any thoughts on this would be appreciated.

    Thank you again for sharing your work, time and expertise.

    ER

    1. ER:

      Editing the form in InfoPath is likely to change the ids, but I don’t know under exactly what circumstances.

      As I say in the post, this is a bit of a hack though it works. When you use it, you’ll just have to be careful when you make future changes.

      M.

  9. Hi Marc – apologies if this has been posted multiple times, Firefox wasn’t giving any feedback as to whether it had submitted my reply.

    I can see from a number of comments that others have had similar issues to my own when trying to use jQuery/SPServices with InfoPath forms. There are a couple of tricks that I’ve documented here http://stackoverflow.com/a/18199201/132599.

    First of all, rather then use setTimeout to wire eventhandlers, SharePoint has an array of initialisation functions that are called when the page is loaded and it’s possible to add your own function, e.g. CustomInfoPathInit, to that array via:

    _spBodyOnLoadFunctionNames.push(‘CustomInfoPathInit’);

    The other issue is that when the form displays “Sending data to server”, it promptly loses any eventhandlers that have been wired up. The workaround for this is to bind a handler to $document that isn’t lost when the form is reloaded (used to be handled by the jQuery “.live()” function which was deprecated in version 1.7 and removed in 1.9). In my case I want to do some validation prior to submit (input[value=”Save”]):

    function CustomInfoPathInit(){
    $(document).on(‘focusin’, ‘input[value=”Save”]’, function(event){
    $(this).attr(‘onclick’, null); // remove standard handler
    $(this).off(‘click’).on(‘click’, function(){
    alert(‘click’); // validation here
    });
    return (Button.OnClick(this, event)); // original click handler
    });
    }

    My example will continue to work correctly even when the form sends data to the server.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.