1

Some Background

I have a publishing site that presents documents with stylized icons using some custom display templates and views (with CSR). Document icons are rendered using fontAwesome classes which are assigned to the document using managed metadata, terms (Item Types) and custom properties.

Item Types in Term Store Management

Screenshot of rendered documents

Now rather than perform a call to the term store for every document, I thought it would be better to create a function that obtains the termset and store the results into browser session storage / global variable. The display templates/views can then lookup the values from the global variable and render the icons as needed.

The function(s) I've created are detailed below and are defined in a custom script file loaded by the master page.

<!-- =SharePoint - Supporting files -->
    <SharePoint:CacheManifestLink runat="server"/>
    <SharePoint:ScriptLink language="javascript" name="core.js" OnDemand="true" runat="server" Localizable="false" />
    <SharePoint:ScriptLink language="javascript" name="menu.js" OnDemand="true" runat="server" Localizable="false" />
    <SharePoint:ScriptLink language="javascript" name="callout.js" OnDemand="true" runat="server" Localizable="false" />
    <SharePoint:ScriptLink language="javascript" name="sharing.js" OnDemand="true" runat="server" Localizable="false" />
    <SharePoint:ScriptLink language="javascript" name="suitelinks.js" OnDemand="true" runat="server" Localizable="false" />
    <SharePoint:ScriptLink language="javascript" name="sp.js" OnDemand="false" runat="server" Localizable="false" LoadAfterUI="true" />
    <SharePoint:ScriptLink language="javascript" name="sp.ui.dialog.js" OnDemand="false" runat="server" Localizable="false" LoadAfterUI="true" />
    <SharePoint:ScriptLink ID="jqueryjs" language="javascript" name="~SiteCollection/orbit/js/jquery-3.2.1.js" runat="server" defer="False"/>
    <SharePoint:ScriptLink ID="orbitinit" language="javascript" name="~SiteCollection/orbit/js/orbit-init.js" runat="server" defer="False" />
    <SharePoint:ScriptLink ID="orbitrender" language="javascript" name="~SiteCollection/orbit/js/orbit-rendering-overrides.js" runat="server" defer="False" />
    <SharePoint:CustomJSUrl runat="server" />
    <SharePoint:SoapDiscoveryLink runat="server" />

Note: Because the call is asynchronous I have used a helper function to track the promise before it continues.

Orbit.ItemTypes.getItemTypes = function () {

   if (sessionStorage.getItem("itemTypes") === null) {
        console.log("itemTypes not loaded - getting terms from term store.");
        Orbit.ItemTypes.setItemTypes();
    } else {
        // load into global array
        console.log("itemTypes already loaded.");
        globalItemTypesArray = JSON.parse(sessionStorage.getItem("itemTypes"));
    }

};

Orbit.ItemTypes.setItemTypes = function () {

    var itemTypesArray = [];  // Global variable;

    // Obtain custom properties of Item Types from Term Store
    var scriptBase = window.location.protocol + "//" + window.location.host + "/_layouts/15/";
    $.getScript(scriptBase + "SP.Runtime.js", function () {
        $.getScript(scriptBase + "SP.js", function () {
            $.getScript(scriptBase + "SP.Taxonomy.js", function () {
                var context = SP.ClientContext.get_current();
                var taxonomySession = SP.Taxonomy.TaxonomySession.getTaxonomySession(context);
                var termStore = taxonomySession.getDefaultSiteCollectionTermStore();
                var termSet = termStore.getTermSetsByName("Item Types",1033).getByName("Item Types");
                var terms = termSet.getAllTerms();
                context.load(termSet);
                context.load(terms);

                Orbit.Utility.promises = [];
                Orbit.Utility.promises.push(function() {
                    return $.Deferred(function (dfd) {

                        context.executeQueryAsync(
                            function onSuccess() {
                                var termEnumerator = terms.getEnumerator();

                                while (termEnumerator.moveNext()) {
                                    var currentTerm = termEnumerator.get_current();
                                    var term_name = currentTerm.get_name();
                                    var term_id = currentTerm.get_id().toString();
                                    var custom_properties = currentTerm.get_objectData().get_properties()["CustomProperties"]["iconclass"] !== undefined ? currentTerm.get_objectData().get_properties()["CustomProperties"] ["iconclass"] : "";

                                    itemTypesArray.push({"termid": term_id, "name" : term_name, "iconclass" : custom_properties});
                                }

                                // store Item Type terms in localstorage
                                dfd.resolve(itemTypesArray);
                            },
                            function onFailure(args) {
                                console.log("ERROR: loading Item Type terms from Term Store");
                            }

                        );

                    }).promise(); 

                });

                $.when(Orbit.Utility.MultiPromiseTracker(Orbit.Utility.promises)).then(function(termStoreData) {
                    sessionStorage.setItem("itemTypes", JSON.stringify(termStoreData[0]));
                    globalItemTypesArray = JSON.parse(sessionStorage.getItem("itemTypes"));
                });

            });
        });
    });
};


Orbit.Utility.MultiPromiseTracker = function (arrayOfPromises) {

    // function that keeps track of multiple promises

    var deferred = $.Deferred();
    var fulfilled = 0, length = arrayOfPromises.length;
    var results = [];

    if (length === 0) {
        deferred.resolve(results);
    } else {
        arrayOfPromises.forEach(function(promise, i){
            $.when(promise()).then(function(value) {
                results[i] = value;
                fulfilled++;
                if(fulfilled === length){
                    deferred.resolve(results);
                }
            });
        });
    }

    return deferred.promise();

}

The problem

I thought by managing the asynchronous call using $.when().then() the page would hold off rendering until the results were returned, but on troubleshooting it appears that as soon as SP.js is loaded (required by the JSOM call) the page is rendered causing the display template / view to error. When the page is refreshed the documents are rendered correctly, likewise when display other pages after globalItemTypesArray has been initialized.

I am new to JSOM, and relatively new to javaScript and appreciate any help and guidance you can provide.

Many thanks in advance!

Paul

Solution

Orbit.ItemTypes.getItemTypes = function () {

    globalItemTypesArray = [];

    if (sessionStorage.getItem("itemTypes") === null) {
        console.log("itemTypes not loaded - getting terms from term store.");
        Orbit.ItemTypes.setItemTypes();
    } else {
        // load into global array
        console.log("itemTypes already loaded.");
        globalItemTypesArray = JSON.parse(sessionStorage.getItem("itemTypes"));
    }

};

No change for Orbit.ItemTypes.getItemTypes

Orbit.ItemTypes.setItemTypes = function () {

    var itemTypesArray = [];  // Global variable;

    // Obtain custom properties of Item Types from Term Store
    var scriptBase = window.location.protocol + "//" + window.location.host + "/_layouts/15/";
    $.getScript(scriptBase + "SP.Runtime.js", function () {
        $.getScript(scriptBase + "SP.js", function () {
            $.getScript(scriptBase + "SP.Taxonomy.js", function () {
                var context = SP.ClientContext.get_current();
                var taxonomySession = SP.Taxonomy.TaxonomySession.getTaxonomySession(context);
                var termStore = taxonomySession.getDefaultSiteCollectionTermStore();
                var termSet = termStore.getTermSetsByName("Item Types",1033).getByName("Item Types");
                var terms = termSet.getAllTerms();
                context.load(termSet);
                context.load(terms);

                Orbit.Utility.promises = [];
                Orbit.Utility.promises.push(function() {
                    return $.Deferred(function (dfd) {

                        context.executeQueryAsync(
                            function onSuccess() {
                                var termEnumerator = terms.getEnumerator();

                                while (termEnumerator.moveNext()) {
                                    var currentTerm = termEnumerator.get_current();
                                    var term_name = currentTerm.get_name();
                                    var term_id = currentTerm.get_id().toString();
                                    var custom_properties = currentTerm.get_objectData().get_properties()["CustomProperties"]["iconclass"] !== undefined ? currentTerm.get_objectData().get_properties()["CustomProperties"] ["iconclass"] : "";

                                    itemTypesArray.push({"termid": term_id, "name" : term_name, "iconclass" : custom_properties});
                                }

                                // store Item Type terms in localstorage
                                dfd.resolve(itemTypesArray);
                            },
                            function onFailure(args) {
                                console.log("ERROR: loading Item Type terms from Term Store");
                            }

                        );

                    }).promise(); 

                });

                $.when(Orbit.Utility.MultiPromiseTracker(Orbit.Utility.promises)).then(function(termStoreData) {
                    sessionStorage.setItem("itemTypes", JSON.stringify(termStoreData[0]));
                    globalItemTypesArray = JSON.parse(sessionStorage.getItem("itemTypes"));
                    Orbit.ItemTypes.postRenderItemTypes();
                });

            });
        });
    });
};

Added call to Orbit.ItemTypes.postRenderItemTypes(); in $.when().then block.

Orbit.ItemTypes.postRenderItemTypes = function () {

    var requiredElements = ["orbit-cswp-document","orbit-document", "orbit-page"];
    var selectionString = "";
    var selectedElements, itemType, iconClass;

    for (var i = 0; i < requiredElements.length; i++) {

        selectionString += ".";
        selectionString += requiredElements[i];

        if (requiredElements.length > 0 && i !== requiredElements.length - 1) {
            selectionString += ", ";

        }
    }

    selectedElements = $(selectionString);

    selectedElements.each(function (index) {
        itemType = $(this).find(".orbit-item-type").text().trim();
        iconClass = Orbit.RenderOverrides.GetDocumentIcon("name", itemType, "", globalItemTypesArray);
        // refreshes icons on CSWP
        $(this).find(".orbit-icon span").removeClass().addClass(iconClass);
        // refreshes icons on Orbit View
        $(this).find("span.orbit-icon").removeClass().addClass("orbit-icon").addClass(iconClass);
    });

}

New function to update icons post rendering. Note: Re-uses function Orbit.RenderOverrides.GetDocumentIcon originally created to render display template/views.

1

Ah yes, the old problem of the CSR / async conflict. As I've written before, my usual strategy when wanting to include data that has to be retrieved asynchronously in something that is being rendered using a CSR override, is to use the CSR system to render placeholders (maybe divs with specific IDs that I can reference later), and once my async call(s) are complete, then fill in the placeholder with the data.

You could probably do something similar in this situation, so when you get all your terms, populate your global array but also look over the page for the placeholders where you want to immediately use that information.

  • Thanks Dylan. I did what you suggested and it works a treat. I've updated the code in my questions with the final solution. – Paul Dowsett Aug 8 '18 at 20:55

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.