SPFindPeoplePicker setting value not working in SP Online?

Jan 29, 2015 at 7:12 PM
I'm using SPServices 2014.02 and jQuery 1.11.2. I've done this a million times on prem but this is the first time I'm trying it in SharePoint Online. Is it working for anyone else? Any markup changes required to "find" the field?

Here's my code:
$().ready(function() {
    ExecuteOrDelayUntilScriptLoaded(setAssginment, "sp.js");
});

function setAssginment(){

    $().SPServices.SPFindPeoplePicker({
        peoplePickerDisplayName: "Assigned To",
        valueToSet: "User One; User Two"
    });
}
If I set a breakpoint and then just punch $().SPServices.SPFindPeoplePicker({ peoplePickerDisplayName: "Assigned To" }); into the console the expected object comes back, so I think it might just be an issue with the valueToSet operation.

Any ideas?
Coordinator
Jan 30, 2015 at 5:06 PM
Sorry I haven't replied sooner.

I'm guessing there's a difference in the markup in SharePoint Online that I'm not handling. I don't think anyone else has reported this.

Let me take a look.

M.
Jan 30, 2015 at 5:08 PM
I don't think there's an SLA for a free product. No worries. :)

If you'd like me to set up a test rig on a site I can give you access to I can do that. Let me know what Microsoft ID to invite.

Thank you!
Coordinator
Jan 30, 2015 at 5:16 PM
I like to be more responsive than the paid guys. It's not actually that hard to do!

M.
Feb 4, 2015 at 3:08 AM
Edited Feb 4, 2015 at 4:50 PM
I recently ran into this too. SP Online (and I believe 2013 as well) uses a client-side people picker instead of the people picker user control from 2010 and before. The SPServices code only deals with the user control. I needed it to work on 2010, 2013 and SP Online, so I ended up doing something like this:
var columnInternalName = "SalesRep";

// look for a div whose id starts with the internal name and ends with ClientPeoplePicker
var pplpkrDiv = $("[id^='" + columnNameInternal + "'][id$='ClientPeoplePicker']");

// you should be able to pass in anything the people picker can uniquely resolve
var currentUser = "i:0#.f|membership|joe@thesharepointconcierge.onmicrosoft.com";

if (pplpkrDiv.length > 0) {

    // if we find a client people picker div, use the SClientPeoplePicker 
    ExecuteOrDelayUntilScriptLoaded(function () {

        // initialize the client people picker 
        var clientPplPicker = SPClientPeoplePicker.SPClientPeoplePickerDict[pplpkrDiv[0].id];

        // pass in the text to resolve
        clientPplPicker.AddUserKeys(currentUser);

    }, "clientpeoplepicker.js");
} else {
    // otherwise, use SPServices
    var displayName = "Sales Rep";

    ExecuteOrDelayUntilScriptLoaded(function () {

        $().SPServices.SPFindPeoplePicker({
            peoplePickerDisplayName: displayName,
            valueToSet: currentUser,
            checkNames: false
        });

    }, "sp.js");
}
Basically, you just look for a div with an ID that starts with the field internal name and ends with 'ClientPeoplePicker' (you may be able to find it by looking for the display name in the title or something like that, I just happened to have the internal name handy and the ID ending with 'ClientPeoplePicker' seemed pretty conclusive). If you find it, SPServices isn't going to work but you use the same div ID to get an initialized SPClientPeoplePicker instance, and it's pretty easy to use.

If you want to get the current values, that's just:
clientPplPicker.GetAllUserInfo()
which if I remember correctly returns an array of objects with a slew of information similar to the EntityData stuff you get from the people picker user control.

I thought about modifying the SPServices method so it did something like this and worked on SharePoint Online and 2013, and contributing it back, but I just haven't quite gotten around to it ;)
Coordinator
Feb 4, 2015 at 1:23 PM
Thanks, mcsheaj.

M.
Feb 5, 2015 at 4:10 PM
It seems to be more complicated than a selector issue. Turns out that at the point at which the script runs (and I am using ExecuteOrDelay), the controls aren't even on the page yet.

If I let the page fully load then the selector from mcsheaj works fine (I haven't yet tested SPServices again).

I'm running this from a Script Editor web part. I'm going experiment to see if the order of the web parts makes a difference somehow.
Feb 5, 2015 at 4:26 PM
New info. Figured out to use _spBodyOnLoadFunctionNames.push("setAssignment"); to delay the execution of the code until after the form was done loading. However the selectors still aren't working (I tried SPServices and mcsheaj's).

New code:
<script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/jquery.SPServices/2014.02/jquery.SPServices-2014.02.min.js"></script>
<script type="text/javascript">
    ExecuteOrDelayUntilScriptLoaded(setAssignmentPre(), "sp.js");
    
    function setAssignmentPre(){
        _spBodyOnLoadFunctionNames.push("setAssignment");
    }
    
    function setAssginment(){

        $().SPServices.SPFindPeoplePicker({
            peoplePickerDisplayName: "Assigned To",
            valueToSet: "User One; User Two"
        });
    }
</script>
After the page loads I manually set a value in the people picker, then ran $().SPServices.SPFindPeoplePicker({ peoplePickerDisplayName: "Assigned To" });. The currentValue property of the returned object is an empty string, which makes me think that there's still a selector issue going on.
Feb 5, 2015 at 6:06 PM
Ok, not a selector issue necessarily. If I use mcsheaj's selector after the page finishes loading then it works. I've been trying to figure out how to properly wait for everything in the script but that doesn't seem to be working. ExecuteOrDelay doesn't work, nor does _SPBodyOnLoad...

I'll keep trying and report back.
Feb 5, 2015 at 6:39 PM
Kind of ridiculous, but what I ended up doing was using setTimeout(func, delay) to delay action of the script. This works. I hate it. :-/
<script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
<script type="text/javascript">
    _spBodyOnLoadFunctionNames.push("setAssignmentPre");
    var timer;
    
    function setAssignmentPre(){
        ExecuteOrDelayUntilScriptLoaded(setTimer(), "clientpeoplepicker.js");
    }
    
    function setTimer(){
        timer = setTimeout(setAssignment, 3000);
    }
    
    function setAssignment(){
        // look for a div whose id starts with the internal name and ends with ClientPeoplePicker
        var columnNameInternal = "AssignedTo";
        var pplpkrDiv = $("div[id^='" + columnNameInternal + "'][id$='ClientPeoplePicker']");

        // you should be able to pass in anything the people picker can uniquely resolve
        var currentUser = "user1@domain; user2@domain";

        if (pplpkrDiv.length > 0) {
            // if we find a client people picker div, use the SPClientPeoplePicker 
            var clientPplPicker = SPClientPeoplePicker.SPClientPeoplePickerDict[pplpkrDiv[0].id];
            if(clientPplPicker !== undefined)
                clientPplPicker.AddUserKeys(currentUser, false);
        }
    }
</script>
Coordinator
Feb 5, 2015 at 7:12 PM
My guess is that MDS is enabled, which makes it tricky to understand when the page is fully loaded. What you have will work, but I agree it feels icky.

Check out Hugh Wood's series here: http://www.spcaf.com/blog/sharepoint-javascript-context-dev-part-1/

M.
Feb 5, 2015 at 7:16 PM
Yes it's definitely MDS related. I was thinking of moving the script out of the form and using RegisterModuleInit or something, but this is a quick and dirty fix for a very limited-use form.
Feb 18, 2015 at 6:31 PM
Edited Feb 18, 2015 at 6:35 PM
Sorry I didn't get back to you sooner, but I setup alerts on this item and they must have ended up in my spam bucket. If MDS is your problem, you want to do something like this:
<script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
<script type="text/javascript">

    // if asyncDeltaManager is defined, MDS is enabled
    if (typeof(asyncDeltaManager) !== "undefined") {
        // call setAssignment once each time start.js is loaded (i.e. each 
        // partial page load)
        ExecuteOrDelayUntilScriptLoaded(function () {
            // add_endRequest is the MDS/asyncDeltaManager equivalent to 
            // _spBodyOnLoadFunctionNames, but note that you pass a 
            // function reference, not the funciton name as a string
            asyncDeltaManager.add_endRequest(setAssignment);
        }, "start.js");
    }
    else {
        // otherwise, just add setAssignment to the list of function names 
        // to call on load
        _spBodyOnLoadFunctionNames.push("setAssignment"); 
    }
    
    function setAssignment(){
        // look for a div whose id starts with the internal name and ends 
        // with ClientPeoplePicker
        var columnNameInternal = "AssignedTo";
        var pplpkrDiv = $("div[id^='" + columnNameInternal + "']"+
            "[id$='ClientPeoplePicker']");

        // you should be able to pass in anything the people picker can uniquely resolve
        var currentUser = "user1@domain; user2@domain";

        if (pplpkrDiv.length > 0) {
            // if we find a client people picker div, use the SPClientPeoplePicker 
            var clientPplPicker = 
                SPClientPeoplePicker.SPClientPeoplePickerDict[pplpkrDiv[0].id];
            if(clientPplPicker !== undefined)
                clientPplPicker.AddUserKeys(currentUser, false);
        }
    }

</script>
The funny thing is I had to put in a setTimeout in the non-MDS code for SharePoint 2010 using $().SPServices.SPFindPeoplePicker, because without it and getting called on _spBodyOnLoadFunctionNames I was finding the people picker control wasn't always fully formed and SPFindPeoplePicker was crashing. I intend to go back and figure out what's the right point in the page life cycle to get called so I don't have that problem, but short-term a 1 second pause got me past it.

Anyway, using the above code, I don't need a setTimeout at all on 2013 or SharePoint online, with or without MDS enabled. And remember, every time we use setTimeout to get around a timing bug, a programmer loses his wings ;)

Joe
Feb 18, 2015 at 6:33 PM
Much appreciated! This is a small utility form with a limited shelf life, but I'll see if I can make some time to test your approach on my form. :)

Chris