Forcing CheckName.click() on blur or textarea/input for People Picker

Jul 19, 2010 at 6:13 PM
Edited Jul 19, 2010 at 7:35 PM

Hey Marc,

I'm beating my head against a wall on this...

As you know, I extended your library locally to include web service calls to an internal source. The internal source is a set of web services that get/set Active Directory content. (I only care about the gets, though.)

What I want to do is take the information in an Employee People Picker and use that content as the seed for the AD web service call to get data to place in a hidden Manager People Picker field.

The problem is: I'm doing all of this on NewForm, so I'm forcing the CheckName image to be clicked so that it will resolve for the Employee before I try to get the Manager data. But, CheckName is asynchronously called from the click(), so even though I wait for the click to finish (by chaining the commands), it still happens that the data is not there yet when I need it to get the Manager data for the hidden field.

I'm doing all of this is PreSaveAction(), so what I'd like to do instead is add a 'blur' function to the People Picker's textarea/input field that will .click() on CheckName. This should give plenty of time for the Employee data to resolve before looking up the Manager data on PreSaveAction().

So, here's what I'm trying, but with no success:

    txtEmpData.blur(function() {
        $("a#" + checkEmpId).click(function() {return false});
    });

...with txtEmpData resolved to the textarea object and checkEmpId unstrung and concatenated from the form to resolve to the CheckName button link, itself.

But, it doesn't do what I expect: namely, click Check Names as soon as the field loses focus.

Coordinator
Jul 20, 2010 at 3:20 AM

Working with the People Picker is icky. You can quote me on that.

I think what you want to so is to wait for the SPAN and the DIV below to be created. The presence of these two elements indicates that the name has been resolved against the User Information list. They also contain the user's account, so you can use that.

<span tabIndex="-1" title="SERVER\marc.anderson" class="ms-entity-resolved" id="spanSERVER\marc.anderson" contentEditable="false">
<DIV style="DISPLAY: none" id=divEntityData description="SERVER\marc.anderson" isresolved="True" displaytext="Marc Anderson" key="SERVER\marc.anderson">

M.

 

Jul 20, 2010 at 12:25 PM
Edited Jul 20, 2010 at 12:31 PM

So, what would "waiting" on those to be created look like? Is there not an event on one of the People Picker controls that I can jump into to force CheckNames to run?

Blessings,
Jim Bob

Coordinator
Jul 21, 2010 at 2:04 AM
Edited Jul 21, 2010 at 3:29 AM

You can just call CheckNames directly yourself.  The trick is to wait for the name to be resolved. Those two elements above won't be there until the resolution is complete, so you can set up a loop which checks for them and if they are not there, waits x milliseconds before trying again.  There's a delay function in jQuery that should help, or if you're strictly JavaScript on this one, setTimeout.

M.

Jul 22, 2010 at 1:52 PM

I am so close on this one...

In fact, most of the time, it works.

For some users, however, it appears that CheckNames doesn't finish fast enough. The only time it works is when there's an alert in the midst of the process. If I comment that out, it will fail for those users every time. For other users (and I can't discern any quantifiable difference between "work"-ers and non-"work"-ers), it works every time with or without the alert.

I've tried putting in delays, but they don't seem to help, even really long ones.

Thoughts?

Blessings,
Jim Bob

Coordinator
Jul 22, 2010 at 1:58 PM

It's probably something about the network topology or the available resourse on their machine because CheckNames goes back to the User Information List for the data, I believe.

Are you waiting for the SPAN or DIV above to appear? That's the only failsafe route, I think.

M.

Jul 22, 2010 at 2:10 PM

Yes, but they never "appear" while the code is running.

How would you call the jQuery .delay()? Maybe I'm delaying the thing I'm waiting for, rather than delaying everything else while I wait for it to run.

Blessings,
Jim Bob

Coordinator
Jul 23, 2010 at 2:54 AM

What you might want to do is something more like this:

while($("span.ms-entity-resolved") == null) { var x;}

The var x; line just gives the script something to do in the while loop. As long as the span isn't there, the loop will continue.  I'm not positve that it's a null test, but you should get the idea.  You'll also want to qualify the selector more tightly if there is more than one People Picker on the page.

You would probably want it to be smarter, but this might get you up and running.

M.

Jul 30, 2010 at 1:41 PM
Jim Bob, Did you ever get a workable solution to this? I am trying to do the exact same thing and while I can get ahold of the DIV and TEXTAREA associated with the people picker I do not seem to be able to trigger anything from either. Thank you in advance. Janette
Jul 30, 2010 at 2:08 PM
Edited Jul 30, 2010 at 2:14 PM

No, Janette.

I still can't get it to trigger the way I want it to... I found a workaround for my project, though.

Here's the code I ended up with to make it work:

<script src="/js/jquery-1.4.2.min.js" type="text/javascript"></script>
<script src="/js/jquery.SPServices-0.4.8.js" type="text/javascript"></script>
<script language="javascript" type="text/javascript">

$(document).ready(function() {

    // Hides the manager field so we can fill it in programmatically
    $("nobr:contains('Manager')").closest("TR").hide(); 

});

function PreSaveAction() 
{ 

    //These are declared globally to keep them in scope
    var userId;
    var mgrName = new String;
    var spanEmpData;

    //Employee data from the form
    var tdEmpData = $("nobr:contains('Employee Name')").closest("TD").next(); //Captures the TD that the Employee Name field is in
    var idEmpLong = $(tdEmpData).find("input[id$=HiddenUserFieldValue]").attr("id"); //Finds the ID of the Employee Name field

    // Control IDs
    var idEmpLeft = idEmpLong.substring(0,idEmpLong.search("HiddenUserFieldValue")); //Gets the leftmost portion of the ID, repeated in all IDs
    var textEmpId = idEmpLeft + "UserField_hiddenSpanData"; //The ID of the hidden field that contains the actual data for Employee Name
    var checkEmpId = idEmpLeft + "UserField_checkNames"; //The ID of the link around the Check Names image

//    alert("The ID we'll use to find the hidden XML is: " + textEmpId);
//    alert("The hidden XML is: " + $("input#" + textEmpId).val());

    // Click the CheckName button
    clickEmployee(checkEmpId); // Which of these actually does the work? Does it get clicked twice?
    $(tdEmpData).find("a#" + checkEmpId).click( function() {
        while($("span.ms-entity-resolved") == null) { var x;}
    });
//    while($("span.ms-entity-resolved") == null) { var x;}

    //Let's look this up again
    tdEmpData = $("nobr:contains('Employee Name')").closest("TD").next(); //** Is this really necessary?

    var i=0;
    while (i <= 200) {
        if (TheRealDeal==null) {
            var tdEmpData = $("nobr:contains('Employee Name')").closest("TD").next(); //Captures the TD that the Employee Name field is in
            TheRealDeal = $("div#" + idEmpLeft + "UserField_upLevelDiv div#divEntityData").attr("key");
        } else {
            break;
        }
        i += 1;
    };

    var TheRealDeal = $("div#" + idEmpLeft + "UserField_upLevelDiv div#divEntityData").attr("key");

    resEmpData = $("input#" + textEmpId).val(); // Resoved HTML
    spanEmpData = $(resEmpData).find("div[id^=divEntityData]").attr("key"); //Finds the UserID of the Employee Name field

    //Manager data from the form
    var tdMgrData = $("nobr:contains('Manager')").parent().parent().next(); //Captures the TD that the Manager field is in
    var idMgrLong = $(tdMgrData).find("input[id$=HiddenUserFieldValue]").attr("id"); //Finds the ID of the Manager field

    resEmpData = $("input#" + textEmpId).val(); // Resoved HTML
    spanEmpData = $(resEmpData).find("div[id^=divEntityData]").attr("key"); //Finds the UserID of the Employee Name field

    // Control IDs
    var idMgrLeft = idMgrLong.substring(0,idMgrLong.search("HiddenUserFieldValue")); //Gets the leftmost portion of the ID, which is repeated in each ID for this set of controls
    var textMgrId = idMgrLeft + "UserField_hiddenSpanData"; //The ID of the hidden field that contains the actual data for Manager
    var checkMgrId = idMgrLeft + "UserField_checkNames"; //The ID of the link around the Check Names image


    //Check to see what sort of data do we have in the Employee Name field. Is it something we can use to isolate the User List record so that we can pull the Manager from it?
    var EmpAcct = $("input[id=" + textEmpId + "]").val(); //The value of the hidden data field for Employee Name
//  id*=_ctl00_ctl04_ctl00_ctl00_ctl00 -- in our case, the EmpAcct is ctl #0 (ctl00); this string finds that one control

    //If the EmpAcct wasn't set before look in the key attribute of the UserField_OuterTable's divEntityData
    if (EmpAcct == "") EmpAcct = $("table[id*=_ctl00_ctl04_ctl00_ctl00_ctl00][id$=UserField_OuterTable]").find("div#divEntityData").attr("key");

    var strQuery = "<Query><Where><Eq>"; //Start building a query string where two fields are equal
    var strValue = ""; //This will be the filling in the query

    // Make an education guess about whether the user entered their name or account
    if ((TheRealDeal == null) || (TheRealDeal == undefined)) { //No entity information, meaning it hasn't already been saved or had the Employee Name 'verified'
        if (EmpAcct.search(" ") == -1) { //Doesn't have space, treat as Login
            strQuery += "<FieldRef Name='Name' />"; //Name in the User List is where the account name (RADNET\lastnamef) is stored
            if (EmpAcct.search("RADNET") == -1) strValue = "RADNET\\"; //If it doesn't have RADNET in it, we'll add it
        } else { //Has spaces; treat as a Name
            strQuery += "<FieldRef Name='Title' />"; //Title in the User List is where the Display Name (firstname lastname) is stored
        }
        strValue += EmpAcct; //Add the actual data we have for EmpAcct

        //Two elements make up the Query call: FieldRef and Value. If we're searching for a record WHERE Name=howardj, Name would be in the FieldRef element, and howardj would be in the Value element with a Type='text'. For ex:
	// <Query><Where><Eq>
	//     <FieldRef Name='Name' /> //Empty tag because nothing else is needed beyond attributes
	//     <Value Type='Text'>howardj</Value>
	// </Eq></Where></Query>
	// Creates equivalent WHERE clause: WHERE Name="howardj"
        strQuery += "<Value Type='Text'>" + strValue + "</Value>";    
        strQuery += "</Eq></Where></Query>";

//        alert("#7: " + strQuery); //Display what we've come up with so far - #7

        //Use the strQuery to look up the userID for the given Employee
        $().SPServices({
             operation: "GetListItems",
             async: false,
             webURL: "http://{site}/", //Replace with your site, of course
             listName: "User Information List",
             CAMLViewFields: "<ViewFields>" +
                                 "<FieldRef Name='Name' />" +
                             "</ViewFields>",
             CAMLQuery: strQuery,    //Here's the query we built above
             completefunc: function (xData, Status) {
	                 $(xData.responseXML).find("[nodeName=z:row]").each(function() {
                     userId = $(this).attr("ows_Name");
                 });
             }
         });
    } else { //Has entity information; get login from XML
//        userId = spanEmpData //The entity's data contains the account info for the Employee in the key attribute
        userId = TheRealDeal;
    };

//    alert("#8i - TheRealDeal: " + TheRealDeal);
//    alert("#8: " + userId);
    $().SPServices({
         operation: "GetUserByUsername",
         // Here is where I call my internal web service to set mgrName
         // I basically slipped it into Marc's SPServices wrapper to avoid completely reinventing the wheel
     });

     if ((mgrName.length == 7) || (mgrName.length == 0)) {
         $("nobr:contains('Manager')").closest("TD").show();
         return false;
     } else {
         $(tdMgrData).find("div[Title='People Picker']").html(mgrName);
         $(tdMgrData).find("a#" + checkMgrId).click();
//         alert($(tdMgrData).find("div[Title='People Picker']").html());
//         return false; //for testing
         return true; 
     }
//    return false; // Cancel the item save process 
//if (mgrName=="") {
//       alert("I'm having trouble locating your manager's name. Please try submitting again.");
//       return false
//} else return true; // Process save.  
};

function clickEmployee (ctlID) 
{
    $("a#" + ctlID).click();
};
</script>

I fully expect Marc to point out ways I could have done this more cleanly. ;)

Hope that helps!

Blessings,
Jim Bob

 

Coordinator
Aug 3, 2010 at 1:37 AM

Wow, that's a lot of code, Jim Bob!

I see that you are still using

<script src="/js/jquery.SPServices-0.4.8.js" type="text/javascript"></script>

I'd really suggest you upgrade to v0.5.6. I've done a lot since v0.4.8, including a lot of bug fixes.

M.

Aug 3, 2010 at 11:48 AM

OK, but you've gotta show me the best way to separate out my code so that I can upgrade SPServices without breaking the wrapper to my web services. When I tried to unhook the two, it no workie.

Blessings,
Jim Bob

PS: How ya been? USPJA keeping you busy?

Feb 27, 2013 at 11:19 AM
I'd like to chime in on this conversation.
I've tried performing the "click" event on the check names icon. Only if the user clicks the icon in the page will the divEntityData div be created from what I have seen. Clicking it in your client script does not create the tag. I've tried putting in a sleep section and waited for 10 seconds after clicking the check names icon and it's still not created. I believe an actual page postback must be performed for this to be created. The postback is performed when the user physically clicks the icon. Not when it's clicked in a script. I've tried clicking it 3 times and still no "divEntityData" div is created.
I was hoping to use it for retrieving the NAME for the account in the people picker but have wasted 3 days trying to get it to work.
Feb 27, 2013 at 11:20 AM
You can wait for years. divEntityData does not get created unless the user physically clicks the check names icon. Doing it a code behind does not create it.
Feb 27, 2013 at 11:49 AM
sympmarc wrote:
You can just call CheckNames directly yourself.  The trick is to wait for the name to be resolved. Those two elements above won't be there until the resolution is complete, so you can set up a loop which checks for them and if they are not there, waits x milliseconds before trying again.  There's a delay function in jQuery that should help, or if you're strictly JavaScript on this one, setTimeout. M.
Tried this however after 20 seconds, it never appeared.
Coordinator
Feb 27, 2013 at 12:25 PM
jdhavo:

Have you looked at the SPFindPeoplePicker function here?

M.
Mar 4, 2013 at 12:34 PM
Edited Mar 4, 2013 at 12:38 PM
You wouldn't know where to find an example of how to get the value by any chance? Have seen various examples of how to set the value, nothing on how to get it.
Usually don't need to set the value since the user will be populating this. I need to render the control and get the value programmatically.
Also, I get script errors when I load SPServices.

Thanks for the assistance.
Coordinator
Mar 4, 2013 at 10:31 PM
What are the errors you get when trying to load SPServices? Are you following the instructions at the bottom of this page?
http://spservices.codeplex.com/documentation

M.