SharePoint 2013 Containment Hierarchy via REST

PowerShell

SharePoint support teams commonly write PowerShell to report on, say site membership, site last used time etc.

PowerShell is an excellent solution, it is very rich in on premise farms, not so rich for SharePoint online yet still powerful.

However, its major drawback is that it either needs to be scheduled meaning that the report is not in real time.

Web Services

So why not use SharePoint web services? Script can be added to a page, or executed in the browser console, to get data such as webs, lists and fields for a site collection. However, to get this data all in one go, along with the relationships between the entities requires a little learning curve.

As the RESTful web services are asynchronous, we will need to wait until the results of one web service call are received, to form the basis of the next web service call and so on.

Consider a report, where we want to see all SharePoint webs, their lists and then some field information. A requirement fulfilled with calls to three different web services.

There are two key concepts here. The first being the web sequencing of service calls; we need get all the SharePoint webs, then for each web, we need to get the child lists. Finally for the child list, we need to get the fields we require, such as url. This diagram shows this.

I Promise

The second concept is around the promise design pattern. We can use JQuery promises to simplify the requests to the various webservices. Of course you can use Ajax calls with the delegate methods, but the JavaScript starts to become difficult to read and indent!

Another concern is of around "building" your output, then appending cheunks of data when recived back from a webserivce call. Unlike the console window, where we can write the result directly to the window. For asynchronous circumstances, we need to hold results until we have say all the lists for a given web, then when finished append it to a parent item and when finished write the lot to a layer in the DOM.

To explain these concepts, we will create a report of all the webs for a site collection, their constituent lists, with link to the list, its name and guid.

ACME.SupportSite.Utilities.WebListsReport = function (divToPlaceReport) {  
    var webs = [];
    var lists = [];
    var TPRWebListDataContainer = 'TPRWebListData';
    divToPlaceReport = '#' + divToPlaceReport;
    jQuery(divToPlaceReport).append('<div id="' + TPRWebListDataContainer + '"></div>').attr('class', 'TPRWebListDataContainer');

    jQuery.support.cors = true;
    jQuery.ajax({
        crossDomain: true,
        headers: { "Accept": "application/json; odata=verbose" },
        xhrFields: { withCredentials: true },
        url: "//sharepointsupport/_api/Web/Webs",
        type: "GET",
        success: function (response) {
            jQuery(response.d.results).each(function () {
                webs.push(this);
            });
        },
        dataType: "json",
        error: function (xhr, status) {
            console.log('error');
        }
    }).then(function () {
        jQuery.each(webs, function () {
            var webUrl = this.Url;
            var webGuid = this.Id
            jQuery('#' + TPRWebListDataContainer).append('<table id="TPRWeb' + webGuid + '"><strong>' + this.Title + '</strong></table>').attr('class', 'TPRWeb');

            jQuery.ajax({
                crossDomain: true,
                headers: { "Accept": "application/json; odata=verbose" },
                xhrFields: { withCredentials: true },
                url: this.Url + "/_api/Web/Lists",
                type: "GET",
                success: function (response) {
                    //after calling fields method, back to listitems stuff
                    jQuery(response.d.results).each(function () {
                        lists.push(this);
                        jQuery('#TPRWeb' + webGuid).append('<tr><td id="TPRListUrl' + this.Id + '" class="TPRListUrl"><a href="' + webUrl + '/Lists/' + this.Title + '">' + this.Title + '</a></td><td id="TPRListId' + this.Id + '" class="TPRListId">' + this.Id + '</td></tr>');
                    });
                },
                dataType: "json",
                error: function (xhr, status) {
                    console.log('error');
                },
                complete: function (xhr, textStatus) {
                    lists = [];
                }

            }).then(function () {
                jQuery.each(lists, function () {
                    var listGuid = this.Id;
                    var listTitle = this.Title;
                    //now get the field
                    jQuery.ajax({
                        crossDomain: true,
                        headers: { "Accept": "application/json; odata=verbose" },
                        xhrFields: { withCredentials: true },
                        url: webUrl + "/_api/Web/Lists(guid'" + listGuid + "')/Items?$select=GUID,FileDirRef,Title",
                        type: "GET",
                        success: function (response) {
                            //need to select the element of the list anchor and update it's url
                            jQuery('#TPRListUrl' + listGuid + '>a').prop("href", response.d.results[0].FileDirRef);
                        },
                        dataType: "json",
                        error: function (xhr, status) {
                            console.log('error');
                        }
                    }).done(function () {
                        console.log('complete');
                    });
                })
            }).done(function () {
                console.log('done');
            });
        });
    });

Can be downloaded from a GitHub Gist at