Attaching a file to a list item in SharePoint add-in model

Long time since I wrote about the SharePoint. Is still my job, I am still working on this, but I simply did not find the time to discover new things. Now, as SharePoint add-in model is more and more adopted in SharePoint development, due to the migration of large companies to Office 365, I am starting to face new challenges. One of this is how to attach a file to an existing list item.

<input type="file" id="attachment1" />

As HTML5 is the new standard for web, file control is able to read the content of a file as a binary string, so no more needed to server side languages. For rest, jQuery and SharePoint JavaScript libraries will do the work for you.


       Type.registerNamespace('Shp');

       Shp.Attachments = function () {
            /// <summary>Shp Attachments static class</summary>
            throw 'Cannot initiate Shp.Attachments static class';
        };

        Shp.Attachments.get_file = function (fileInput) {
            /// <summary>This method is used to get the content of the file as binary string</summmary>

            var deffered = jQuery.Deferred();
            var reader = new FileReader();
            reader.onload = function (e) { 
                deffered.resolve(e.target.result);
            };  

            reader.onerror = function (e) {
                deffered.reject(e.target.error);
            };

            reader.readAsBinaryString(fileInput.files[0]);
            return deffered.promise();
        };


        Shp.Attachments.add = function (listName, itemId, fileInput, webUrl, success, fail) {
            /// <summary>Add attachments</summary>
            /// <param>List name</param>
            /// <param>Item Id</param>
            /// <param>File input controls</param>
            /// <param>Web url</param>
            var e = Function.validateParameters(arguments, [{ name: 'listName', type: String, optional: false, mayBeNull: false },
                                                            { name: 'itemId', type: String, optional: false, mayBeNull: false },
                                                            { name: 'filesInput', type: HTMLElement, optional: false, mayBeNull: false },
                                                            { name: 'webUrl', type: String, optional: false, mayBeNull: true },
                                                            { name: 'success', type: Function, optional: false, mayBeNull: false },
                                                            { name: 'fail', type: Function, optional: true, mayBeNull: false }], true);
            if (e) throw e;


            var webUrl = webUrl || _spPageContextInfo.webAbsoluteUrl;
            var fail = fail || function (err) { alert(err); };

            Shp.Attachments.get_file(fileInput).then(function (fileContent) {
                var parts = fileInput.value.split("\\");
                var fileName = parts[parts.length - 1];
                // Attachments add internal method
                Shp.Attachments._add(listName, itemId, fileContent, fileName, webUrl, success, fail);
            });
        };

        Shp.Attachments._add = function (listName, itemId, fileContent, fileName, webUrl, success, fail) {

            var scriptBase = webUrl + "/_layouts/15/";
            jQuery.getScript(scriptBase + "SP.RequestExecutor.js", function () {

                var executor = new SP.RequestExecutor(webUrl);
                executor.executeAsync({
                    url: webUrl + "/_api/web/lists/GetByTitle('" + listName + "')/items(" + itemId + ")/AttachmentFiles/add(FileName='" + fileName + "')",
                    method: "POST",
                    binaryStringRequestBody: true,
                    body: fileContent,
                    state: "Update",
                    success: function () {
                        success(itemId);
                    },
                    fail: function (data) {
                        fail(data.responseText);
                    }

                });

            });
        };

        Shp.Attachments.registerClass('Shp.Attachments');

Once code is organized, attaching a file become an easy task.

 Shp.Attachments.add('Tickets', '2', document.getElementById('attachment1'), null, function (itemId) {
        alert(itemId);
    }, function (err) {
        alert('Error: ' + err);
    });

Shp.Attachments.add accepts following parameters:

  • List name as string
  • Item id as string
  • HTML file control as HTML element
  • Web URL. It can be nul and in this case is used _spPageContextInfo.webAbsoluteUrl
  • Success method to be executed if attachment is added
  • Fail method to be executed if attachment failed

Copy, adapt the code according to your needs and use it. Happy coding! If it helps, you can just say hi to me and I will be happy as well. 🙂

Advertisements

Send email using SharePoint API

I see all over examples about how to send emails using SharePoint API. I am not going to reinvent the code, but I am just organizing it into a little bit more object orientated way. And again, Microsoft Ajax library is here to help us. As always, first step is to create the namespace where you want to place the objects.

Type.registerNamespace('Shp.Utility');

Once I done it, I need to create an object containing email properties.

Shp.Utility.EmailProperties = function (to, from, subject, body) {
    /// <summary>Create an instance of Shp.Utility.EmailProperties
    /// <param name="to" type="String" mayBeNull="false" optional="false">To</param>
    /// <param name="from" type="String" mayBeNull="false" optional="false">From</param>
    /// <param name="subject" type="String" mayBeNull="false" optional="false">Email Subject</param>
    /// <param name="body" type="String" mayBeNull="false" optional="false">Email Body</param>
    var e = Function.validateParameters(arguments, [{ name: 'to', type: Array, elementType: String, elementMayBeNull: false, optional: false, mayBeNull: false },
                                                    { name: 'from', type: String, optional: false, mayBeNull: false },
                                                    { name: 'subject', type: String, optional: false, mayBeNull: false },
                                                    { name: 'body', type: String, optional: false, mayBeNull: false }], true);
    if (e) throw e;

    this.to = to;
    this.from = from;
    this.subject = subject;
    this.body = body;
    this.cc = [];
    this.bcc = [];
}

Shp.Utility.EmailProperties.prototype = {


    add_cc: function (cc) {
        /// <signature>
        ///     <summary>Add CC to email properties</summary>
        ///     <param name="cc" type="String" mayBeNull="false" optional="false">CC</param>
        ///     <returns type="Shp.Utility.EmailProperties"></returns>
        /// </signature>
        /// <signature>
        ///     <summary>Add CC to email properties</summary>
        ///      <param name="cc" type="Array" elementType="String" elementMayBeNull="false" mayBeNull="false" optional="false">CC</param>
        ///      <returns type="Shp.Utility.EmailProperties"></</returns>
        /// </signature>
        var e1 = Function.validateParameters(arguments, [{ name: 'cc', type: String, optional: false, mayBeNull: false }], true);
        var e2 = Function.validateParameters(arguments, [{ name: 'cc', type: Array, elementType: String, elementMayBeNull: false, optional: false, mayBeNull: false }], true);
        if (e1 != null && e2 != null) throw e1 || e2;

        var instance = this;
        if (e1 == null)
            instance.cc.push(cc);
        if (e2 == null)
            instance.cc = Array.clone(cc);

        return instance;
    },

    add_bcc: function (bcc) {
        /// <signature>
        ///     <summary>Add CC to email properties</summary>
        ///     <param name="bcc" type="String" mayBeNull="false" optional="false">CC</param>
        ///     <returnts type="Shp.Utility.EmailProperties" />
        /// </signature>
        /// <signature>
        ///     <summary>Add CC to email properties</summary>
        ///     <param name="bcc" type="Array" elementType="String" elementMayBeNull="false" mayBeNull="false" optional="false">CC</param>
        ///     <returnts type="Shp.Utility.EmailProperties" />
        /// </signature>
        var e1 = Function.validateParameters(arguments, [{ name: 'bcc', type: String, optional: false, mayBeNull: false }], true);
        var e2 = Function.validateParameters(arguments, [{ name: 'bcc', type: Array, elementType: String, elementMayBeNull: false, optional: false, mayBeNull: false }], true);
        if (e1 != null && e2 != null) throw e1 || e2;

        var instance = this;
        if (e1 == null)
            instance.bcc.push(bcc);
        if (e2 == null)
            instance.bcc = Array.clone(bcc);

        return instance;

    },

    GenerateRequestBody: function () {
        /// <summary>Generate Ajax request body</summary>
        var instance = this;
        var request = {
            'properties': {
                '__metadata': { 'type': 'SP.Utilities.EmailProperties' },
                'From': instance.from,
                'To': { 'results': instance.to },
                'CC': { 'results': instance.cc },
                'BCC': { 'results': instance.bcc },
                'Body': instance.body,
                'Subject': instance.subject
            }
        }
        return JSON.stringify(request);
    }

}

Shp.Utility.EmailProperties.registerClass('Shp.Utility.EmailProperties');

Here is actually the point where the code is simplified. Creating email properties object makes you write less code in the Ajax call. Generate request body method is using all necessary properties of the object to create string representation of JSON object to be used as the request body.

var from = 'someone@domain.com';
var body = 'Email body'; // Pay attention, you might need to encode it
var subject = 'Email subject';
var to = ['person1@domain.com', 'person2@domain.com'];

// Create email properties object
var emailProperties = new Shp.Utility.EmailProperties(to, from, subject, body);

// Add CC and BCC
emailProperties.add_cc(['person3@domain.com', 'person4@domain.com']);
emailProperties.add_bcc(['person3@domain.com', 'person4@domain.com']);

// Forgot to add someone, no worries 
// Passing a string instead of array of strings makes the method not to act like a setter. Instead is adding email to CC or BCC field.
emailProperties.add_cc('forgotten@domain.com');
emailProperties.add_bcc('forgotten@domain.com');

Now, as we have the object containing the email properties, is time to handle the Ajax call.


Shp.Utility.Email = function () {
    /// <summary>Shp.Utility.Email static class</summary>
    throw 'You cannot initialize an instance of Shp.Utility.Email static class';
}


Shp.Utility.Email.SendEmail = function (emailProperties, success, fail) {
    /// <summary>Send an email using SharePoint API</summary>
    /// <param name="emailProperties" type="Shp.Utility.EmailProperties" optional="false" mayBeNull="false">Email Properties</param>
    /// <param name="success" type="Function" optional="false" mayBeNull="false">Success</param>
    /// <param name="fail" type="Function" optional="true" mayBeNull="false">Fail</param>
    var e = Function.validateParameters(arguments, [{ name: 'emailProperties', type: Shp.Utility.EmailProperties, optional: false, mayBeNull: false },
                                                    { name: 'success', type: Function, optional: false, mayBeNull: false },
                                                    { name: 'fail', type: Function, optional: true, mayBeNull: false }], true);
    if (e) throw e;


    Shp.Utility.Email._SendEmail(emailProperties, success, fail || function (errCode, errText) {
        alert('Send email Ajax call failed with error code ' + errCode + ':' + errText);
    });
}

Shp.Utility.Email._SendEmail = function (emailProperties, success, fail) {
    var ajax = new Sys.Net.WebRequest();
    ajax.set_url(_spPageContextInfo.webServerRelativeUrl + '/_api/SP.Utilities.Utility.SendEmail');
    ajax.set_httpVerb('POST');
    ajax.set_body(emailProperties.GenerateRequestBody());
    ajax.get_headers()['Accept'] = 'application/json;odata=verbose';
    ajax.get_headers()['Content-Type'] = 'application/json;odata=verbose';
    ajax.get_headers()['X-RequestDigest'] = $get('__REQUESTDIGEST').value;

    ajax.add_completed(function (executor, eventArgs) {
        if (executor.get_statusCode() >= 200 && executor.get_statusCode() < 300 && executor.get_responseAvailable() === true) {
            success(executor.get_responseData());
        }
        else {
            alert('Ajax failed with error code ' + executor.get_statusCode() + ':' + executor.get_responseData());
        }
    });

    ajax.invoke();
}

Shp.Utility.Email.registerClass('Shp.Utility.Email');

This class is very simple. It contains a method called “SendEmail” which accepts 3 arguments:

  • Email properties, which is the object created before
  • A function to be executed if Ajax is successfully, which accept obtained data from server as parameter.
  • A function to be executed if Ajax fails. This function accepts error code and error text as parameters, but is not mandatory. If not specified, an alert containing this information is shown.

As an example, check the code below.

Shp.Utility.Email.SendEmail(emailProperties, function (data) {
    // Do something with data
});

There one important thing I need to mention. It seems you can send emails only to the users registered to the SharePoint. If not registered, the user won’t receive emails, but the email doesn’t fail as the rest of registered users will receive it.

There is always a way to improve the code and if you have suggestions, please let me know. 🙂

Accessing some SharePoint properties from JavaScript

We know now Microsoft is pushing the SharePoint development to client side as much as possible. This means developers should use JavaScript more instead of server side code where is possible and Microsoft made this easier by exposing some SharePoint properties to client side code. There is a JavaScript object called _spPageContextInfo which contains some properties you might use in your code:

  • webServerRelativeUrl
  • webAbsoluteUrl
  • siteAbsoluteUrl
  • serverRequestPath
  • layoutsUrl
  • webTitle
  • webTemplate
  • tenantAppVersion
  • isAppWeb
  • webLogoUrl
  • webLanguage
  • currentLanguage
  • currentUICultureName
  • currentCultureName
  • clientServerTimeDelta
  • siteClientTag
  • crossDomainPhotosEnabled
  • webUIVersion
  • webPermMasks
  • pageListId
  • pageItemId
  • pagePersonalizationScope
  • userId
  • systemUserKey
  • alertsEnabled
  • siteServerRelativeUrl
  • allowSilverlightPrompt

List is long and you can figure out yourself how to use them. Probably the most noticeable property is userId. I remember when people moved to SharePoint 2013, a lot of developers were upset they could not use any more _spUserId. That’s probably they did not know it was replaced with _spPageContextInfo.userId. I would say, Microsoft did not communicate enough new functionalities, but this doesn’t mean they did not offer something to replace old functionality.

“Data cannot be inserted because there is no matching record” error

Microsoft Access and linked tables to SharePoint lists are commonly used to manipulate data from SharePoint sites. Through the Microsoft queries, users can bulk update or insert data into a SharePoint list. I also did the same, but a few days ago I started to receive an error running an update query against a SharePoint list: “Data cannot be inserted because there is no matching record”.

In my case, removing some users from the site was the cause of this. People/group field type is actually a type of lookup field which is pointing to User Information List. But when you remove a user from site, which means removing it from the users list also, people/group field value is not reset to blank and it keeps continue storing the ID of removed user. So, when you try to run an update query you actually force the field to keep the value, but it fails because users is not anymore accessible in users list. If you recently removed users, go to your list and update records replacing removed users with other users, which are not removed from your site.

Autocomplete textbox with JavaScript CSOM

I have searched on the internet about how to create an auto-complete functionality in SharePoint. Of course, jQuery UI was the solution with an Ajax request to REST service. For some reason, I cannot understand it, the examples I have seen are based on synchronous Ajax request. So I simply said no way. I needed something asynchronous to avoid page freeze.

Normally to create an auto-complete is a simple thing.


    jQuery('#txtBox').autocomplete({
        minLength: 3,
        source: function(request, response) {
         // At the and of the async operation call response with obtained results
         }
     });

Asynchronous operation will be placed inside source function, but instead using classic Ajax examples, I will use JavaScript CSOM. It is not better, but I like it more. So is more a personal choice.

For getting data from SharePoint, you can use classical example from MSDN website, but I prefer to reorganize the code a little bit. After all I still have Microsoft Ajax library available so I can put classes in namespaces or I can validate parameters type.

/// <Reference Name="MicrosoftAjax.js" />


Type.registerNamespace('Shp');


Shp.Lists = function () {
    throw 'Cannot instantiate Shp.Lists static class';
}


Shp.Lists.GetItems = function (listName, query, web, success, fail) {
    /// <summary>Get list items based on provided CAML query</summary>
    /// <param name="listName" type="String" optional="false" mayBeNull="false">List name</param>
    /// <param name="query" type="String" optional="false" mayBeNull="false">Query</param>
    /// <param name="web" type="SP.Web" optional="false" mayBeNull="true">Web</param>
    /// <param name="success" type="Function" optional="false" mayBeNull="false">Success callback</param>
    /// <param name="fail" type="Function" optional="true" mayBeNull="false">Fail callback</param>

    var e = Function.validateParameters(arguments, [{ name: 'listName', type: String, mayBeNull: false, optional: false },
                                                   { name: 'query', type: String, mayBeNull: false, optional: false },
                                                   { name: 'web', type: SP.Web, mayBeNull: true, optional: false },
                                                   { name: 'success', type: Function, mayBeNull: false, optional: false },
                                                   { name: 'fail', type: Function, mayBeNull: false, optional: true }], true);
    if (e) throw e;

    var fail = fail || function (error) { alert(error); };
    var ctx = (web === null) ? SP.ClientContext.get_current() : web.get_context();
    var web = (web === null) ? ctx.get_web() : web;


    Shp.Lists._GetItems(listName, query, ctx, web, success, fail);
}

Shp.Lists._GetItems = function (listName, query, ctx, web, success, fail) {

    var oList = web.get_lists().getByTitle(listName);
    var camlQuery = new SP.CamlQuery();
    camlQuery.set_viewXml(query);
    var oListItems = oList.getItems(camlQuery);
    ctx.load(oListItems);
    ctx.executeQueryAsync(function () {
        success(oListItems);
    }, function (sender, args) {
        fail(args.get_message());
    });
}

Shp.Lists.registerClass('Shp.Lists');

“Shp.Lists.GetItems” should be called with the following parameters:

  • List name, as string, not optional and cannot be null.
  • CAML query as string, not optional and cannot be null.
  • Web as SP.Web, not optional but can be null. In this case web associated with the current context is used.
  • Success as function, not optional and cannot be null. It is executed if operation is a success.
  • Fail function, optional and cannot be null. If not specified and operation fails, code will alert the error message.

Now as I created a reusable function for reading list items, everything should be much easier. I just need to call “Shp.Lists.GetItems” with correct parameters inside auto-complete source and, if operation is successfully, to add suggestions based on list items.

jQuery('#txtBox').autocomplete({
    minLength: 3,
    source: function (request, response) {
       
        var term = request.term;
        var query = '<View><Query><Where><Contains><FieldRef Name="Title" /><Value Type="Text">' + term + '</Value></Contains></Where></Query></View>';
        Shp.Lists.GetItems("list name", query, null, function (items) {
            var suggestions = [];
            var listItemEnumerator = items.getEnumerator();
            while (listItemEnumerator.moveNext()) {
                suggestions.push(listItemEnumerator.get_current().get_item('Title'));
            }
            // Add suggestions
            response(suggestions);

        });

    }
});

This was my approach of creating the auto-complete functionality. As I said, is a personal option to use JavaScript CSOM because looks for me more organized and structured. Of course code can be extended and you can even create an Ajax client side control to incorporate this functionality.

Thank you for reading my post!

How did I add Angular in SharePoint apps

SharePoint 2013 development model is based on what is called SharePoint Apps. Typically, these are applications which rely on client side, including also Angular, more and more popular among developers. For me it was a little bit of a problem to get used with new development model, because I was used with client side development on classic web forms technology. So I have tried to combine what I knew with new way of doing things.

Because I did not want to interfere with existing master pages, I have created mine and simplified it to fit the functionality I want to achieve. So, I have added a module called “MasterPages” and change the name and extension of the sample text file, included by default in the module”, to “Layout.master”. Inside the master page I have placed the following content.

<!DOCTYPE html >
<html runat="server" dir="ltr" ng-app="holidayapp">
    <head runat="server">
         <meta http-equiv="X-UA-Compatible" content="IE=10" />
        <meta name="GENERATOR" content="Microsoft SharePoint" />
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
        <meta http-equiv="Expires" content="0" />
        <title><asp:ContentPlaceHolder runat="server" ID="PlaceHolderPageTitleInTitleArea" /></title>  
        <SharePoint:ScriptLink language="javascript" name="core.js" OnDemand="true" runat="server" Localizable="false"  />
        <SharePoint:ScriptLink language="javascript" name="sp.js" LoadAfterUI ="true" OnDemand="false" runat="server" Localizable="false" />
        <asp:ContentPlaceHolder runat="server" ID="laceHolderAdditionalPageHead" />      
    </head>
    <body>
        <form runat="server" id="aspnetForm">
         <asp:ScriptManager runat="server" AjaxFrameworkMode="Enabled" EnablePartialRendering="true" LoadScriptsBeforeUI="false" ScriptMode="Debug">
             <Scripts>
            </Scripts>
         </asp:ScriptManager>
          <asp:ContentPlaceHolder runat="server" ID="PlaceHolderMain">
             

        </asp:ContentPlaceHolder>
        </form>
    </body>
</html>

In the elements file, I have change the URL where master page should be deployed:

<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <Module Name="MasterPages">
    <File Path="MasterPages\Layout.master" Url="_catalogs/masterpage/Layout.master" ReplaceContent="TRUE"  Type="GhostableInLibrary"/>
  </Module>
</Elements>

Beside classic master page code, you can notice I have added mark-up for Angular application. It remained to load required JavaScript files, for which I used script manager control. I see no reason why I should not use it. Loading script after user interface, it offers the best way of loading scripts in a web page as it does not affect performance.

<asp:ScriptManager runat="server" AjaxFrameworkMode="Enabled" EnablePartialRendering="true" LoadScriptsBeforeUI="false" ScriptMode="Debug">
 <Scripts>
<asp:ScriptReference Path="../../Scripts/angular.min.js" />
 </Scripts>
</asp:ScriptManager>

We do not have yet controller, but my intention is to add it to the content page. So, let’s move forward and change content page accordingly.

<%@ Page Inherits="Microsoft.SharePoint.WebPartPages.WebPartPage, Microsoft.SharePoint, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" MasterPageFile="~site/_catalogs/masterpage/Layout.master" Language="C#" %>
<asp:Content runat="server" ContentPlaceHolderID="PlaceHolderMain">
    <asp:ScriptManagerProxy runat="server"> 
        <Scripts>
            <asp:ScriptReference Path="../Scripts/Default.js"/>
        </Scripts>
    </asp:ScriptManagerProxy>
    
    <div ng-controller="maincontent">
        {{mymessage}}
    </div>
  
</asp:Content>

Please note the followings:

  • Content page is pointing now to my master page I have created before (see page directives).
  • Controller logic will be place in “Default.js” file, loaded through script manager proxy control. Again we use standard web forms technique of loading files.
  • Controller mark-up is placed in a content area.

As is not the scope of this article to discuss about Angular in details, logic of the controller is very simple.

var holidayApp = angular.module("holidayapp", []);

var content = holidayApp.controller("maincontent", function ($scope) {
    $scope.mymessage = "Message";
});

The idea behind this article is to show you can still use web forms and add modern technology to your project. Personally, I do not agree loading JavaScript files in the header. Script manager is still very powerful. And even more, you can combine this way Angular with Microsoft Ajax client side library.

Working with client people picker

I was digging into SharePoint 2013 client people picker control and I have decided to write some findings about it. First of all, my approach is more classical. I was trying to do as much as I can declarative, instead of creating complicated JavaScript code for it. Here is how I have done.

First thing I did was to be sure I have all required JavaScript files loaded. So adding these lines to the header should be more than fine:

<SharePoint:ScriptLink name="clienttemplates.js" runat="server" LoadAfterUI="true" Localizable="false" OnDemand="False" />
<SharePoint:ScriptLink name="clientforms.js" runat="server" LoadAfterUI="true" Localizable="false"  OnDemand="False" />
<SharePoint:ScriptLink name="clientpeoplepicker.js" runat="server" LoadAfterUI="true" Localizable="false"  OnDemand="False" />
<SharePoint:ScriptLink name="autofill.js" runat="server" LoadAfterUI="true" Localizable="false"  OnDemand="False" />					

In the content area I have added client picker editor tag, defining also a handler for user resolved event.

<SharePoint:ClientPeoplePicker ErrorMessage="Not a valid user" ValidationEnabled="True" runat="server" AllowMultipleEntities="False" ClientIDMode="Static" AutoFillEnabled="True" Required="True" OnUserResolvedClientScript="userResolved"  ID="AssignedTo" VisibleSuggestions="10"/>


<script type="text/javascript">
var userResolved = function (elementId, users) {


}
 </script>

You can notice I have added the script inside page content. For some reasons, probably loading order, if I move the JavaScript function handler to an external file and load it through ScriptManager (set to load scripts after UI) it won’t work and will find the function as being undefined. For me it looks like a loading order and probably client picker control is processed before my file is loaded by the script manager. Not nice what I have done, I know, but is working. 🙂

Coming back to the handler, it should be a function called with two parameters. First one represents the id of the element. More exactly is a id of the top container which includes multiple DOM elements required by the people picker control. The second parameter is an array of objects. For each user an object is pushed to an array and passed to our function.

var userResolved = function (elementId, users) {
    ///<summary>Handler for user resolved event</summary>
    /// <param Type="String">Element id</param>
    /// <param Type="Array" ElementType="Object">Users</param>

    for (var i = 0; i < users.length; i++) {
        // Object containing properties for the users, like Key, Description, EntityType, IsResolved
        var user = users[i];
        alert(user['Key']);
    }
}

However, beside user resolved event, we can also have a handler for value changed or for picker validate. You can bind the event to the handler in the same way.

<SharePoint:ClientPeoplePicker ErrorMessage="Not a valid user" ValidationEnabled="True" runat="server" AllowMultipleEntities="False" ClientIDMode="Static" AutoFillEnabled="True" Required="True" OnControlValidateClientScript="pickerValidate" OnValueChangedClientScript="valueChanged"  ID="AssignedTo" VisibleSuggestions="10"/>

<script type="text/javascript">
var valueChanged = function (elementId, users) {
    ///<summary>Handler for value changed event</summary>
    /// <param type="String">Element id</param>
    /// <param Type="Array" ElementType="Object">Users</param>
}

var pickerValidate = function (elementId, users) {
    ///<summary>Handler for picker validate event</summary>
    /// <param type="String">Element id</param>
    /// <param Type="Array" ElementType="Object">Users</param>
}
 </script>

So far so good, but I think there is still a question to be answered. How do we get the value of the control?

To get the value of people picker control, you can do the following.

// Get the div
var div = document.querySelector('div[id*="AssignedTo"][id*="TopSpan"]');
// Get instance of the people picker
var ctl = SPClientPeoplePicker.SPClientPeoplePickerDict[div.id];
// Obtaining the users as an array of objects
var users = ctl.GetAllUserInfo();

What I do not like here is querySelector, but you can bypass this if you are setting “ClientIDMode” property to “Static”. This means that you can calculate client id of container div by concatenating ID of the control you have set declarative with “_TopSpan”.

// Get the div
var div = $get("AssignedTo" + "_TopSpan");
// Get instance of the people picker
var ctl = SPClientPeoplePicker.SPClientPeoplePickerDict[div.id];
// Obtaining the users as an array of objects
var users = ctl.GetAllUserInfo();

An interesting fact is that value of client picker control is persistent after complete post back. This means you can use new control, even if is client side one, using the development techniques you used for previous version of SharePoint.