Reduce Code Need for SharePoint REST API Calls with SPRestRepository

Published on Author Rob WindsorLeave a comment

I’ve been wanting to do this for a while now and I finally found the time.

Take a look at the two REST API calls below. The only thing that is different is the resource path in the URL. If this is case, why are we writing the remainder of the code over and over again? It doesn’t make sense.

var call1 = jQuery.ajax({
    url: _spPageContextInfo.webAbsoluteUrl + "/_api/Web",
    type: "GET",
    dataType: "json",
    headers: {
        Accept: "application/json;odata=verbose"
    }
});

var call2 = jQuery.ajax({
    url: _spPageContextInfo.webAbsoluteUrl + "/_api/Web/Lists",
    type: "GET",
    dataType: "json",
    headers: {
        Accept: "application/json;odata=verbose"
    }
});

What we should do is create a function that has all the common code and just pass in the resource path as a parameter.

function makeCall(resourcePath) {
    var call1 = jQuery.ajax({
        url: _spPageContextInfo.webAbsoluteUrl + resourcePath,
        type: "GET",
        dataType: "json",
        headers: {
            Accept: "application/json;odata=verbose"
        }
    });
}

var call1 = makeCall("/_api/Web");
var call2 = makeCall("/_api/Web/Lists");

Of course we don’t want to write makeCall every time we use the REST API so it should really be put into a library. When doing so there will be other things we need to consider:

  • We won’t always be calling from the same service root URL so that will need to parameterized
  • We may be making cross-site calls (e.g. AppWeb to HostWeb) so we need to handle that
  • We might want to use JSON Light so the odata type needs to be parameterized
  • We won’t just be making GET requests so we also need to implement POST, MERGE, and DELETE
  • For the POST, MERGE, and DELETE operations we are going to need the form digest
  • The code to handle failed calls is almost always the same so lets provide a default handler

With all these things in mind, I wrote the SPRestRepository.

"use strict";

window.Demo = window.Demo || {};

Demo.SPRestRepository = function (appUrl, hostUrl) {
    var _formDigest = "";
    var _odataType = "verbose";

    function getOdataHeader() {
        return "application/json;odata=" + _odataType;
    }

    function checkFormDigest(formDigest) {
        if (!formDigest) {
            formDigest = _formDigest;
        }
        
        return formDigest;
    }

    function buildUrl(url) {
        url = appUrl + url;
        if (hostUrl) {
            var api = "_api/";
            var index = url.indexOf(api);
            url = url.slice(0, index + api.length) +
                "SP.AppContextSite(@target)" +
                url.slice(index + api.length - 1);

            var connector = "?";
            if (url.indexOf("?") > -1 && url.indexOf("$") > -1) {
                connector = "&";
            }

            url = url + connector + "@target='" + hostUrl + "'";
        }

        return url;
    }

    function getFormDigest() {
        return _formDigest;
    }

    function setFormDigest(formDigest) {
        _formDigest = formDigest;
    }

    function getOdataType() {
        return _odataType;
    }

    function setOdataType(odataType) {
        _odataType = odataType;
    }

    function get(url) {
        url = buildUrl(url);

        var call = jQuery.ajax({
            url: url,
            type: "GET",
            dataType: "json",
            headers: {
                "Accept": getOdataHeader()
            }
        });

        return call;
    }

    function add(url, body, formDigest) {
        formDigest = checkFormDigest(formDigest);

        url = buildUrl(url);

        var call = jQuery.ajax({
            url: url,
            type: "POST",
            data: body,
            headers: {
                "Accept": getOdataHeader(),
                "Content-Type": getOdataHeader(),
                "X-RequestDigest": formDigest
            }
        });

        return call;
    }

    function update(url, body, etag, formDigest) {
        if (!etag) etag = "*";
        formDigest = checkFormDigest(formDigest);

        url = buildUrl(url);

        var call = jQuery.ajax({
            url: url,
            type: "POST",
            data: body,
            headers: {
                "Accept": getOdataHeader(),
                "Content-Type": getOdataHeader(),
                "X-RequestDigest": formDigest,
                "IF-MATCH": etag,
                "X-Http-Method": "PATCH"
            }
        });


        return call;
    }

    function doDelete(url, formDigest) {
        formDigest = checkFormDigest(formDigest);

        url = buildUrl(url);

        var call = jQuery.ajax({
            url: url,
            type: "DELETE",
            data: body,
            headers: {
                "Accept": getOdataHeader(),
                "X-RequestDigest": formDigest,
                "IF-MATCH": "*"
            }
        });
    }

    function failHandler(jqXHR, textStatus, errorThrown) {
        var response = "";
        try {
            var parsed = JSON.parse(jqXHR.responseText);
            response = parsed.error.message.value;
        } catch (e) {
            response = jqXHR.responseText;
        }
        alert("Call failed. Error: " + response);
    }

    return {
        get_formDigest: getFormDigest,
        set_formDigest: setFormDigest,
        get_odataType: getOdataType,
        set_odataType: setOdataType,
        get: get,
        add: add,
        update: update,
        doDelete: doDelete,
        failHandler: failHandler
    }

}

Using this library greatly reduces the amount of code I need to write when using the REST API. Here’s an example.

(function () {
    "use strict";

    var appUrl = GetUrlKeyValue("SPAppWebUrl");
    var hostUrl = GetUrlKeyValue("SPHostUrl");
    var repo = null;

    jQuery(function () {
        jQuery("#getButton").click(doGet);
        jQuery("#createListButton").click(createList);
        jQuery("#createItemButton").click(createItem);
        jQuery("#updateItemButton").click(updateItem);

        repo = new Demo.SPRestRepository(appUrl);
        repo.set_formDigest(jQuery("#__REQUESTDIGEST").val());
    });

    function doGet() {
        var call = repo.get("/_api/Web/");
        call.done(function (data, textStatus, jqXHR) {
            var message = jQuery("#message");
            message.text(data.d.Title);

        });
        call.fail(repo.failHandler);
    }

    function createList() {
        var list = JSON.stringify({
            "__metadata": { type: "SP.List" },
            BaseTemplate: SP.ListTemplateType.tasks,
            Title: "Tasks"
        });

        var call = repo.add("/_api/Web/Lists", list);
        call.done(function (data, textStatus, jqXHR) {
            var message = jQuery("#message");
            message.text("List added");
        });
        call.fail(repo.failHandler);
    }

    function createItem() {
        var due = new Date();
        due.setDate(due.getDate() + 7);

        var item = JSON.stringify({
            "__metadata": { type: "SP.Data.TasksListItem" },
            Title: "Sample Task",
            AssignedToId: _spPageContextInfo.userId,
            DueDate: due
        });

        var call = repo.add("/_api/Web/Lists/getByTitle('Tasks')/Items", item);
        call.done(function (data, textStatus, jqXHR) {
            var div = jQuery("#message");
            div.text("Item added");
        });
        call.fail(repo.failHandler);
    }

    function updateItem() {
        var call = repo.get("/_api/Web/Lists/getByTitle('Tasks')/Items/?$top=1");
        call.done(function (data, textStatus, jqXHR) {
            var items = data.d.results;
            if (items.length > 0) {
                var item = items[0];
                updateItem(item);
            }
        });
        call.fail(repo.failHandler);

        function updateItem(item) {
            var url = "/_api/Web/Lists/getByTitle('Tasks')/Items(" + item.Id + ")";

            var update = JSON.stringify({
                "__metadata": { type: "SP.Data.TasksListItem" },
                Status: "In Progress",
                PercentComplete: 0.10
            });
        
            var call = repo.update(url, update, item.__metadata.etag);
            call.done(function (data, textStatus, jqXHR) {
                var div = jQuery("#message");
                div.text("Item updated");
            });
            call.fail(repo.failHandler);
        }
    }


})();

At some point soon I’ll record a video going over this in a little more detail and post it to my YouTube channel.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.