simulating/replicating Blackboard "template variables" in Canvas

VVMarshburn
Community Explorer
3
2114

Our institution is just completing a migration from Blackboard to Canvas. While Canvas appears to offer some very useful capabilities for our purposes, there may be some features of Blackboard that we miss somewhat. Among these are the "template variables" that could be utilized to embed user or course information into the content – e.g., by adding a code such as "@X@user.full_name@X@@" to content items including announcements, assignments, or module pages, the user's name would appear when rendered by the Web browser. This type of functionality can be helpful in customizing and personalizing content in an LMS, or automating repeated information such as a course name. 

To demonstrate, one would be able to construct the following content in Blackboard:

[image] template variables in Blackboard (code)

and have it rendered thus:

[image] template variables in Blackboard (rendered)

By default, Canvas does not currently include this kind of functionality, but thanks to some insight from resourceful members of the Instructure forums (namely user "dtod," whose code I borrowed) and the rest of the Internet (and maybe a very little help from ChatGPT), I have been able to replicate something similar in our particular instance of the LMS. This requires access to the Themes feature of your Canvas instance, where you will need to add/modify custom Javascript. (This does require admin rights, so if you do not possess this ability, you may need to try begging/pleading/cajoling/bribing your own powers-that-be as necessary.)

The premise of this code is based on the existence of what Canvas refers to as "environment variables" or ENV variables, which in this context could be made to provide similar functionality as Blackboard's template variables. (These are apparently commonly used with LTI, but we can also exploit them otherwise.)

If one were to investigate ENV variables in a Web browser's console while viewing a course page in Canvas, one would find many potentially interesting and useful bits of data that could be leveraged. However, it seems there is not necessarily an overall consistency in how some of these variables are implemented between different course pages or contexts.

For instance, "ENV.current_user" is a default system ENV variable that seems to be available in just about every context, and its properties, such as ".id" (user's Canvas ID) or ".display_name" (user's full name), can be regularly accessed. One may also observe that there appears to be a number of standalone variables derived from or related to "ENV.current_user," such as "ENV.current_user_id," which could also prove to be useful.

On the other hand, variables related to course information may not be as consistently available or accessible across content. For example, on a course home page, the "ENV.COURSE" variable would provide some very practical properties such as ".id" (course Canvas ID) and ".long_name" (full name of course), and there are some other seemingly related "shortcut" variables such as "ENV.COURSE_ID" and "ENV.COURSE_TITLE." However, as it turns out, all of these are not necessarily generated for every context.

The purpose of this code is to identify specific ENV variables that could be useful as template placeholders and to automatically substitute their actual values while a page is rendered in a Web browser. The inherent flexibility of this technique is that ultimately we can identify or define as many variables as we think we need.

The actual Javascript presented here is the result of extrapolation from various sources. Currently, it provides a reasonably working functionality that could no doubt stand any amount of improvement. Javascript is honestly not my current forte (I have been immersed in python for the past six years or so).

So, if you are willing to conduct some experimentation of your own, you would need to add the following to a Javascript file that would be uploaded to one of the Themes in your Canvas instance, which would then be selected as the current theme. (See link above regarding custom Javascript in Canvas.)

First we need to specify the variables we want to utilize (some may be system default ENV variables, some will be defined by us).

 

var envVars = ['ENV.current_user.display_name', 'ENV.courseID', 'ENV.current_user_login', 'ENV.course_name'];

 

In this case, we will ultimately utilize "ENV.current_user.display_name" as is, since it appears to be accessible throughout Canvas content; whereas for the others, while ENV variables exist that would include such data, they are not consistently defined by Canvas itself in every context, so we are defining them independently; this will help ensure we will have them available throughout our sessions. We will refer to these as "non-default" ENV variables.

We will need a function for waiting until certain conditions are met (e.g., values assigned to variables); this is sometimes referred to as a "promise" function in Javascript terminology.

 

async function waitUntil(condition, time = 100) {
    while (!condition()) {
        await new Promise((resolve) => setTimeout(resolve, time));
    }
}

 

Next, we set up the function to define and derive the non-default ENV variables (theoretically, we can create as many of these non-default items as needed; we would need to include them in the var envVars statement above as well). This will be the function referenced in the event listener that will initiate this entire process.

 

async function getVariables() {
    //non-default/derived ENV variables
    ENV.courseID = window.location.pathname.split('/')[2]; //this can be easily extracted from the current URL/path when viewing a course; this iteration of this data should persist across content
    ENV.course_name = ''; //not quite the same as ENV.COURSE.long_name, which is only available in limited contexts
    ENV.current_user_login = ''; //we sometimes find it useful to have access to the user's login name

    //using API, get current user's profile and assign property 'login_id' value to ENV.current_user_login
    response = await fetch('/api/v1/users/self/profile');
    profile = await response.json();
    ENV.current_user_login = profile.login_id.toLowerCase();
    await waitUntil(() => ENV.current_user_login != ''); //call waitUntil to ensure variable has a value

    //using API, get course info and assign property 'name' value to ENV.course_name
    response = await fetch('/api/v1/courses/' + ENV.courseID);
    course = await response.json();
    ENV.course_name = course.name;
    await waitUntil(() => ENV.course_name != '');

    //call the function(s) to replace each instance of an ENV variable in a page's content with its value
    replaceInPage();
}

 

The next function is the one called at the end of getVariables(); it identifies page element(s) to be included in the substitution operation (mainly those using the class "ic-Layout-contentWrapper"; conceivably, you could include other page elements as well). Many thanks to Instructure Community Contributor "dtod" for this code.

 

function replaceInPage(){
    var user_content = document.getElementsByClassName('ic-Layout-contentWrapper');
    Array.prototype.forEach.call(user_content, function(el) {
        for (var i=0; i<envVars.length; i++){
            var searchFor = envVars[i];
            var re = new RegExp(searchFor, "g");
            
            //call function to perform actual replacement
            replaceInText(el, re, eval(envVars[i]));
        }
    });
}

 

The function called at the end of replaceInPage() performs the actual search and replace of ENV variable strings in course page content; again, many thanks to Instructure Community Contributor "dtod" for this code.

 

function replaceInText(element, pattern, replacement) {
    for (let node of element.childNodes) {
        switch (node.nodeType) {
            case Node.ELEMENT_NODE:
            attr='href';
            if (node.hasAttribute(attr)){
                node.setAttribute(attr, node.getAttribute(attr).replace(pattern, replacement));
            }
            replaceInText(node, pattern, replacement);
            break;
            case Node.TEXT_NODE:
                node.textContent = node.textContent.replace(pattern, replacement);
            break;
            case Node.DOCUMENT_NODE:
                replaceInText(node, pattern, replacement);
        }
    }
}

 

Finally, we need to add an event listener for the page "load" event to initiate the ENV variables substitution process by calling getVariables().

 

window.addEventListener('load',() => {
    //only call this for pages within an actual course
    if (/^\/courses\/[0-9]+/.test(window.location.pathname)){
        getVariables();
    }
});

 

Once this has been added to the active Theme in Canvas, it should now be possible to simulate something similar to Blackboard's template variables. The practical result is that if you include, for example, the variable "ENV.current_user.display_name" in some page content during editing and then save it, it will be replaced by the actual variable value during page loading. Thus, we should be able to construct Canvas content such as:

[image] environment variables in Canvas (code)

and observe it rendered in the Web browser as:

[image] environment variables in Canvas (rendered)

An even better practical example is something like a "certificate of completion" page that we have incorporated into some courses such as our Online Student Orientation. This is how the  implementation of ENV variables appears when editing the page:

[image] environment variables in Certificate page (code)

and this is how it is rendered by a Web browser:

[image] environment variables in Certificate page (rendered)

The possibilities could readily lend themselves to some creative content design.

It should be a relatively straightforward exercise to expand the range of ENV variables that can be substituted with actual values. For instance, if you wanted to reference a course's start and end dates within standard content, you could define "ENV.course_start" and "ENV.course_end" and extract "course.start_at" and "course.end_at" through the API (as I did with "ENV.course_name" from "course.name").

Another note to consider: as defined by the code here and based on the normal operation of a Web browser, you may find that the ENV variables will briefly appear in the content in their literal encoded format (e.g., as "ENV.current_user.display_name") before being replaced by their actual values. I have not been able to devise any method of avoiding or suppressing this, other than to incorporate additional code that hides (using CSS) and then un-hides such content once substitution is complete, so that at least the user only ever observes the actual values. This has been a rather minor obsessive compulsion on my part, but others might not find it as bothersome.

I cannot begin to suspect if anyone would find any of this useful, but on the off chance that someone else might be seeking to replicate Blackboard's template variables, this might offer some solution, or at least a clue towards a more effective solution. Please feel free to adapt or cannibalize this code as you see fit.

3 Comments