Calling SharePoint 2013 REST api using an AngularJS Service

Calling the SharePoint 2013’s REST api can be a repetitive task. During the last Office365 summit in Amsterdam, the developer track always pointed to the Office365 Patterns and Practices for samples on how to develop in office 365. To my greatest joy they also have an SPA sample!

AngularJS services provide a way to share code within the app. This is great for not always having to write the same code over and over again. In this case calling SharePoint REST urls using $http.

The following is a snippet from https://github.com/OfficeDev/PnP/blob/master/Samples/Core.SharePointProxyForSpaApps/SharePointProxyForSpaAppsWeb/App/common/services/sharepointproxy.service.js:

(function (angular) {
    "use strict";

    angular
        .module('sharepointproxy.service', [])
        .factory('SharepointProxyService', SharepointProxyService);

    function SharepointProxyService($http) {

        var proxyEndpointOptions = {
            url: 'api/sharepoint',
            method: 'POST',
            data: {}
        };

        var defaultProxyOptions = {

        };

        var service = {
            transformRequest: transformRequest,
            sendRequest: sendRequest,
            getWebTitle: getWebTitle
        };

        function getData(o) {
            return o.data;
        }

        function transformRequest(request) {
            var transformedRequest = proxyEndpointOptions;
            transformedRequest.data = request;

            return transformedRequest;
        }

        function sendRequest(request) {
            return $http(request)
                .then(getData)
                .then(function (data) {
                    return data;
                })
            ;
        }

        return service;
    }

    SharepointProxyService.$inject = ['$http'];

})(angular);

It’s a great example on how to start using an AngularJS service calling SharePoint. It can be easily extended with more defined methods, like getting the title of the hostWeb:

        function getWebTitle(hostWebUrl) {
            var requestUrl = hostWebUrl + '/_api/web/title';
            var request = {
                url: requestUrl,
                method: 'GET',
                headers: {
                    Accept: 'application/json;odata=verbose'
                }
            }
            var spRequest = transformRequest(request);
            return sendRequest(spRequest);
        }

All you need to add in your controller is a simple call to the service passing the hostWebUrl:

var queryParameterString = (window.location.search[0] === '?') ? window.location.search.slice(1) : window.location.search;
$scope.queryParameters = deparam(queryParameterString);

        $scope.requestServiceData = function () {
            console.log("Request service data!");
            var result = SharepointProxyService.getWebTitle($scope.queryParameters.SPHostUrl)
            .then(function (data) {
                $scope.serviceResponse = data;
            });
        };

Finish off by adding a button to your HTML to call the service:

<button ng-click="requestServiceData()">Request</button>

Updating content types using SharePoint Web Services (SPServices)

When you deploy an updated content type which is created from code and have new fields, your changes won’t automatically be pushed to children that inherit from your content type. This is only the case when you have created your content type using code. In the sandbox solution, I’m using the SharePoint Web Services to add new fields to content types that inherit from the updated content type.

Why the web services? With the push from Microsoft to go more and more client side, I found it a challenge to not do something with simply a farm solution or a server-side script. During the development of the web part, I’ve found 2 annoyances by using the SPServices library and jQuery/SharePoint:

  1. SPServices says every method name is unique. This is not true. There’s a method “UpdateContentType” for both the Lists.asmx and the Webs.asmx. Due to this overlooked bug, the call will always be directed to the Webs.asmx web service. I’ve modified the SPServices js file to comment out the WSops.UpdateContentType method for Webs.
  2. jQuery will always put your generated attributes to lower case. The web services are case-sensitive as you can see from the following screenshot (taken from STSSOAP.DLL, decompiled with ILSpy)
    caseSensitive

The web part itself is pretty straight forward. You’ll get a list of content types with update links.

content type updater webpart

When using firebug or another web dev tool, you can track all the calls that are made to the Webs.asmx & Lists.asmx

content type updater console window

I’ve deployed a custom content type to my site, created a list with that content type and then made an update to the site content type. As you can see there’s a column missing from the list content type:

site content type

list content type

After I click on “Update” next to “CustomContentType”, the script will detect a difference between these two and update accordingly:

soap call new fields

And the updated list content type:

updated list content type

All in all I’m pretty happy with the result. You can do a lot with just the web services from SharePoint, but they take some time to get used to. Also the documentation isn’t always great as for instance they mention to use <FieldRefs> while in the source code of the web service itself there’s only a parse of the <Fields> tag.

For the full source, or if you want to download and test the web part itself, you can go to CodePlex.

Passing the result from a dialog to the parent page

Sometimes you want to communicate with the parent page of your dialog in order to pass on the result of your work. Thankfully the SP.UI.ModalDialog framework contains the commonModalDialogClose method. The javascript CSOM has all the tools in place to construct the following application:

  • Add a button to the ribbon. This button will also open the dialog.
  • Create an application page that will be used as a dialog. This application page has a text box and a button.
  • Add javascript to capture the result of the application page, in this case just text, and put it where our cursor resides on the parent page.

First things first, we’ll add a button to our ribbon.

ribbonbutton

Since there are many tutorials available, I won’t go into detail here. Simply add an empty element to your project and replace the contents with this one:

<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <CustomAction
    Id="bramnuyts.InsertButton"
    Location="CommandUI.Ribbon"
    Title="InsertRibbon Button">
    <CommandUIExtension>
      <CommandUIDefinitions>
        <CommandUIDefinition
          Location="Ribbon.EditingTools.CPInsert.Groups._children">
          <Group
            Id="Ribbon.EditingTools.CPInsert.InsertGroup"
            Sequence="1"
            Description="Insert"
            Title="Insert"
            Template="Ribbon.Templates.Flexible2"
            >
            <Controls Id="Ribbon.EditingTools.CPInsert.InsertGroup.Controls">
              <Button Id="Ribbon.EditingTools.CPInsert.InsertGroup.InsertButton"
                Command="InsertCommand"
                Image16by16="/_layouts/images/removeStylesHS.png"
                Image32by32="/_layouts/images/removeStylesHH.png"
                LabelText="Insert"
                TemplateAlias="o2" />
            </Controls>
          </Group>
        </CommandUIDefinition>
        <CommandUIDefinition
          Location="Ribbon.EditingTools.CPInsert.Scaling._children">
          <MaxSize
            Id="Ribbon.EditingTools.CPInsert.InsertGroup.MaxSize"
            Sequence="15"
            GroupId="Ribbon.EditingTools.CPInsert.InsertGroup"
            Size="LargeLarge" />
        </CommandUIDefinition>
      </CommandUIDefinitions>
      <CommandUIHandlers>
        <CommandUIHandler
          Command="InsertCommand"
          CommandAction="javascript:insert();" />
      </CommandUIHandlers>
    </CommandUIExtension>
  </CustomAction>
  <CustomAction
    Id="InsertCommand.Script"
    Location="ScriptLink"
    ScriptSrc ="/_layouts/DialogResult/insert.js" />
</Elements>

I added the javascript in a separate javascript file “insert.js”. I like the ScriptLink approach as it doesn’t dirty the elements.xml file. However there are some reported issues when using ScriptLinks so go for the approach you feel is the most safe.

insert.js

Inside my insert.js file I have 2 methods: addElement(txt) and insert(). The “insert()” method will be responsible for handling the click on the button and creating the dialog. The “addElement(txt)” method will be responsible for adding content to the rich content page. Once again SharePoint is there to help us, this time it’s the “RTE” class, which points to the Rich Text Editor. By using the “RTE.Cursor.get_range()” method, we’re able to determine where a user has put his cursor.

function addElement(txt) {
    var range = RTE.Cursor.get_range();
    range.deleteContent();
    var selection = range.parentElement();
    if (!selection) {
        return;
    }
    var span = selection.ownerDocument.createElement('span');
    span.innerText = txt;
    range.insertNode(span);
    RTE.Cursor.get_range().moveToNode(span);
    RTE.Cursor.update();
}

function insert() {
    var options = {
        url: SP.Utilities.Utility.getLayoutsPageUrl('/DialogResult/insert.aspx'),
        title: 'Insert',
        allowMaximize: false,
        showClose: true,
        width: 300,
        height: 150,
        dialogReturnValueCallback: Function.createDelegate(null, function (result, returnValue) {
            if (result == SP.UI.DialogResult.OK) {
                    addElement(returnValue);
            }
        })
    };

    SP.UI.ModalDialog.showModalDialog(options);
}

Now that we have our button and the javascript to handle the dialog, we have to create our dialog page.

insert.aspx

I added a text box and a button to the main contentplaceholder:

insert.aspx.main

Add the “Button1_Click” OnClick method to the button and add the following code to the code behind:

protected void Button1_Click(object sender, EventArgs e)
{
    int result = 1; // Result = OK
    string returnValue = TextBox1.Text;
    Page.Response.Clear();
    Page.Response.Write(String.Format(
        CultureInfo.InvariantCulture, 
        "<script type=\"text/javascript\">window.frameElement.commonModalDialogClose({0}, {1});</script>", 
        new object[] { 
            result, 
            String.IsNullOrEmpty(returnValue) ? "null" : String.Format("\"{0}\"", returnValue) 
        }));
    Page.Response.End();
}

The code will close the dialog window, pass “OK” as result and text from the text box as return value.

When you deploy the code (make sure that your package contains the button) you’ll see the following button appear when you edit your rich content page:

result ribbon

When you click the button the dialog page will appear:

result dialog

When you insert some “test text” and click insert, your text will show up where you left your cursor:

result test text