Based on the above answer by Alexander, this is the best I could come up with for a general SharePoint Designer solution. In the form of ASP tags in XSLT, you create the people picker pairs (SP2010 and SP2013) like so:
<SharePoint:FormField runat="server" ID="_START_Attendees_END_" ControlMode="Edit" FieldName="Attendees"/>
<SharePoint:ClientPeoplePicker runat="server" ClientIDMode="Static" AutoFillEnabled="True" ID="Attendees" ValidationEnabled="True" PrincipalAccountType="User" Rows="1" AllowMultipleEntities="True" CssClass="ms-long ms-spellcheck-true"/>
The FormField control is the one that is bound to the list column. It has the FieldName property on its API. For reasons of inheritance, the ClientPeoplePicker control does not. That's why it can't be bound directly to a list column. Kinda poorly designed really. Anyway, on the ClientPeoplePicker we use ClientIDMode="Static" to make it easier to select the rendered elements. ClientIDMode doesn't have the same effect in the FormField control, so there's no point using it. We need to be clever with the ID.
The technique I came up with is to use predictable tokens that will match a regular expression but not collide with anything. I chose a prefix of "START" and a suffix of "END". In between those tokens, we use the same string as the ClientPeoplePicker ID, which in this example is also the same as the FormField FieldName attribute, though it doesn't need to be. With fields setup in this way we then hide the old style pickers on the form using CSS and use jquery to copy values between the old and new. The javascript function PreSaveAction() will execute before the form is saved. In this function we copy values from the new style pickers to the old style ones. We can use the jQuery $(document).ready method to do the reverse when the form loads. Code is shown below and can be introduced through a Script Editor web part on the form page, written into the XSLT, or included through external files. It will work for any custom forms designed in this way.
<script type="text/javascript" src="/_layouts/15/clientforms.js"> </script>
<script type="text/javascript" src="/_layouts/15/clientpeoplepicker.js"></script>
<script type="text/javascript" src="/_layouts/15/autofill.js"></script>
<script type="text/javascript">
function PreSaveAction() {
for (var key in SPClientPeoplePicker.SPClientPeoplePickerDict) {
var pickerObj = SPClientPeoplePicker.SPClientPeoplePickerDict[key];
// drop the _TopSpan
var customID = key.substr(0, key.length - 8);
$('div[id*="_START_' + customID + '_END_"]').html(pickerObj.GetAllUserKeys());
}
return true;
}
$(document).ready(function() {
var oldStylePickers = $('div[id*="UserField_upLevelDiv"]');
var regexp = /_START_(.*?)_END_/;
oldStylePickers.each(function() {
var $div = $(this);
var userKeys = [];
$div.find('span.ms-entity-resolved').each(function() {
userKeys.push($(this).attr('title'));
});
//alert(userKeys);
var matches = regexp.exec(this.id);
if (matches && matches.length > 1) {
var newPickerID = matches[1];
var pickerObj = SPClientPeoplePicker.SPClientPeoplePickerDict[newPickerID + '_TopSpan'];
for (var i=0; i < userKeys.length; i++) {
var login = userKeys[i];
pickerObj.AddUserKeys(login);
}
}
});
});
</script>
<style type="text/css">
span.ms-usereditor {
display: none;
}
</style>
I hope this helps someone. I couldn't find a complete solution anywhere and spent a fair bit of time on this.