Adding “Page X of Y” pagination type in Word with C#

Adding “Page X of Y” pagination type into a Word document from C# into a Word add-in project can be very difficult. I have spent hours to figure out how to do this. It seems to be a special need for this, because sometimes people are requested to scan documents and miss some pages. Now looking back, code seems to be easy, but believe me, wasn’t easy at all to put pieces side by side to obtain this effect because there is lack of documentation about Word add-ins projects.

foreach(Section section in Globals.ThisAddIn.Application.ActiveDocument.Sections)
{
                Range footer = section.Footers[WdHeaderFooterIndex.wdHeaderFooterPrimary].Range;
 
                footer.Collapse(WdCollapseDirection.wdCollapseStart);
                footer.Fields.Add(footer, WdFieldType.wdFieldNumPages);
                footer.Collapse(WdCollapseDirection.wdCollapseStart);
                footer.InsertAfter(" of ");
                footer.Collapse(WdCollapseDirection.wdCollapseStart);
                footer.Fields.Add(footer, WdFieldType.wdFieldPage);
                footer.Collapse(WdCollapseDirection.wdCollapseStart);
                footer.InsertAfter("Page ");
}

Feel to extend it and use this code. Simple as this you can accomplish what is requested.

Advertisements

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.