Skip navigation
All Places > Canvas Developers > Blog
1 2 3 4 Previous Next

Canvas Developers

48 posts

Developer Tools for the Canvas User > Tutorial #3

 

For this tutorial we are going to combine what we learned in tutorial's 1 and 2. The goal will be to make an AJAX request with JavaScript to the Canvas API, and then when the request is successful update the DOM with an updated value. The Document Object Model or DOM, is the webpage as your browser sees it. I'll get you through this tutorial without any more details, but if you're curious here are some resources:

W3 Schools - JavaScript DOM Methods

MDN - Document Object Model (DOM) - Web APIs

 

As part of this rapidly escalating tutorial I am no longer going to use pictures. Anything you need should be referenced from the previous tutorials.

 

  1. Grab the snippet from the last tutorial and add the following code (lines 10-14) to the end.
    var payload = {
         "nickname":"My W○RKR∞M ⸚ Test"     // what we are changing the nickname to
    }

    $.ajax({
         url : '/api/v1/users/self/course_nicknames/1214595',
         type: 'PUT',
         data: payload
    })
    // when the request is resolved
    .done(function(response, status) {
         console.log(response)
    })
    • The jQuery done() method allows us to do something when the AJAX request is complete. For now, copy or type this code into your Console tab, and press Enter
    • The console.log() method allows us to display messages to the console. This is generally used for debugging or displaying other messages while developing. The user will never see these messages unless they have the Console tab open.
    • The response should look just like the one you saw in the Preview pane of the Network tab from Tutorial #2
  2. Now, you will update the name of the Course Card...
    Note: We could have done this with the payload value, but this is a better example of making an AJAX request and doing something when the request is finished and successful.
  3. First we have to find a Selector we can use to identify the element we want to change. 
    Right click on the Course Card where you see the course nickname (the text with color), and Inspect Element
    Hopefully, you will land on a element that looks something like this
    <span style="color:#FDC010;" data-reactid=".1.$1266609.0.2.0.0.0">W○RKR∞M⸚CARROLL</span>
    Note the style="" attribute with value color:#FDC010;
    We will use this as our selector for this tutorial.[1][2]

  4. Let's update our code and use jQuery to update the element after the AJAX request completes, see line 12
    We will use the response.nickname value
    var payload = {
         "nickname":"My W○RKR∞M ⸚ Test"     // what we are changing the nickname to
    }

    $.ajax({
         url : '/api/v1/users/self/course_nicknames/1214595',
         type: 'PUT',
         data: payload
    }).done(function(response, status) {
         $('span[style*="color:#FDC010;"]').html(response.nickname)
    })
    Press Enter, after updating the code in the Console tab
  5. You should see the nickname of your course card update
    If not, try refreshing the page and reset or adjust some of the values you are passing with the code.

 

Congrats, AGAIN! 

This is the basic workflow of a Canvas end-user modification. These are some of the fundamental practices used in making interactive web pages all across the web. You can expand these skills to make your own custom Canvas modifications. Remember to review the Canvas API Documentation and thoroughly plan out the objective and goals your script is trying to achieve.

 

[1] There a plenty of resources on the web for CSS and HTML selectors for interacting with DOM elements via JavaScript, check with Google.

[2] This is because Canvas uses React - A JavaScript library for building user interfaces, which allows elements to load or update after the Primary DOM loads. This is noticeable via the attribute data-reactid="". Usually unique elements will have an id="" attribute if the element is unique and a class="" attribute if there are similar elements. This creates problems when jQuery tries to interact with these elements, because they are not part of the page when jQuery loads. We would prefer to use predictable Selectors whenever possible. This is a more advanced topic and may be covered in a later tutorial.

 

 Rapid Escalation

Repeat Tutorial 3 and update the color of the Course Card, after sending a new color to the Canvas API.

Developer Tools for the Canvas User > Tutorial #2

 

Before getting started with APIs, read through paragraphs 1-5 of the linked thread by Stuart Ryan

Canvas APIs: Getting started, the practical ins and outs, gotchas, tips, and tricks

Stuart confirmed there is a working beta environment on the Canvas Free For Teachers accounts at https://canvas.beta.instructure.com


To save on lengthy screenshots, I've combined some panels, please let me know if this causes confusion.

 

  1. Now that you have the console open, click on the Network tab, and the ⃠ Clear button on the left
    This will clear all the requests that happened during page load
    Drag out some space with the border between the ViewPort (on the left) and the Developer Tools
  2. Now click on the edit button at the top right of any Course Card, change the nickname and the color, click Apply
    You should see two new requests with the course number
  3. Click on the request prefixed with course_ and then the Network Headers tab
    Take a quick peak at the following:
    Request URL - this is the URL (or endpoint) of the request we want to send to the API to change the color, it contains your user and course id's
    MethodPUT - HTTP, and all the Request Methods - HTTP | MDN 
    Status Code200 OK, and all the Response Status Codes - HTTP | MDN 
    Content Typehttps://www.google.com/search?q=JSON
  4. Now click on the Network Preview tab
    This is JSON, I hope you clicked that last link.
    I'll walk you through with Colors first, it's a simple place to start.
    // this is a simple JSON object, with the hex value of the color you chose
    // you'll usually see JSON like you see in the console, with all the white space removed
    {hexcode: "#F0592B"}

    // but it's often useful to nest it with tabs while you're working and learning
    {
         hexcode: "#F0592B" // this is the data that was sent to Canvas to save the color
    }

    // it has one (1) value, simple right?

    Canvas Developer Tools | Change Color

  5. That's 5 pieces of information we need to change the color with the API
    URL - API endpoint/api/v1/users/:id/colors/:asset_string
    MethodPUT - replaces all current representations of the target resource with the request payload.
    ID - your user idself - use self when you can't remember your user_id [1]
    Asset Stringcourse_# - get the course number from the course URL
    Parameters{hexcode: "#F0592B"} // the payload you will PUT to the URL
  6. Let's keep it simple and use jQuery to make this API request
    Type or copy the 10 lines below into the Console tab, and edit the course number in the URL
    After you get this snippet into the console, hit Enter
    var payload = { hexcode: '#FDC010' } // yellow

    // http://api.jquery.com/jquery.ajax/
    $.ajax({
        url  : '/api/v1/users/self/colors/course_1214595',
         type: 'PUT',
         data: payload
    })

    // #8BC34A - a green hex code

    {readyState: 1, setRequestHeader: ƒ, getAllResponseHeaders: ƒ, getResponseHeader: ƒ, overrideMimeType: ƒ, …}

  7. You should immediately see a response that looks something like the one above, we'll cover it in tutorial #3, click on the Network tab
    Review the Network Headers and the most recent request for course_#
    Hopefully you'll see Status Code of 200 OK
    If not, compare your code to the snippet above // comments can be ignored, or take a peak at tutorial 3
  8. Click on the Network Preview tab
    You should see a JSON object with the hexcode you sent with the request in the Console tab
  9. Refresh the page
    Unlike tutorial 1, this time you sent the data to the Canvas API where it was saved. When you refreshed the page, you got another copy of the Dashboard with the new color.
  10. Try It Again!
    Go back to the Console tab. To re-execute the last command/code you ran in the console, press the up arrow on your keyboard
    Copy the green hex code from the snippet, and replace the payload value, press Enter
    Pick a different Hex Code with W3 - HTML Color Picker

 

Congratulations!

You've just used javascriptjqueryajax, and the Canvas api to Update custom color - Users - Canvas LMS REST API Documentation 

Review the documentation anytime you want to interact with Canvas programmatically. The documentation is a great starting point to any project, then planning how you will use it in your script or program.

 

 Escalating

 

  1. Let's continue to the Course Nickname.
    Click on the Network request without the course_ prefix, in the screenshot below it's 1214595
    Review the Headers and the Preview tabs as you did above
    Canvas Developer Tools | Set Course Nickname
  2. Some more JSON, this time with a few more parameters, let's take a look
    // you see a string like this
    {"course_id":1214595,"name":"W○RKR∞M⸚CARROLL","nickname":"My W○RKR∞M ⸚"}

    // we can indent and format, making it easier to read
    {
         "course_id":1214595,          // the id of the course we want to rename
         "name":"W○RKR∞M⸚CARROLL",     // the current nickname of the course
         "nickname":"My W○RKR∞M ⸚"     // what we are changing the nickname to
    }
  3. Three values were returned in response to setting the Course Nickname using the Dashboard Card. However, we only need to send 1 value to change the nickname.
    Take a look at Set course nickname - Users - Canvas LMS REST API Documentation
    Note the Endpoint and the Method, it looks like this

     PUT /api/v1/users/self/course_nicknames/:course_id

     

  4. Let's update the snippet of code we used in the first section of Tutorial #2

    We need to update the payload with the nickname, and change the URL

    // update the payload with the values we need to set
    // indent them to be easier to read
    var payload = {
         "nickname":"My W○RKR∞M ⸚ Test"     // what we are changing the nickname to
    }

    // http://api.jquery.com/jquery.ajax/
    $.ajax({
         url : '/api/v1/users/self/course_nicknames/1214595',
         type: 'PUT',
         data: payload
    })
  5. Copy and paste your code into the Console tab (unless you edited it there) and press Enter
  6. You should immediately see a response that looks something like the one above, we'll cover it in tutorial #3, click on the Network tab
    Review the Network Headers and the most recent request for course_#, hopefully you'll see Status Code of 200 OK
    If not, compare your code to the snippet above // comments can be ignored, or take a peak at tutorial 3
  7. Click on the Network Preview tab
    You should see a JSON object with the hexcode you sent with the request in the Console tab
  8. Refresh the page
    You should see your course change names
  9. Try It Again!
    Edit the snippet, change the nickname and press Enter

 

[1] Profile - Users - Canvas LMS REST API Documentation

      or in the browser console, try ENV.current_user_id

 

 Rapid Escalation

Check out Developer Tools #3 - Update the DOM with an AJAX Response  and we'll update the Dashboard without Refreshing the page

Developer Tools for the Canvas User > Tutorial #1

 

  1. On your Canvas Dashboard, Right Click on a Course Card and choose Inspect
    If you miss and the highlighted element is not exactly as shown, hover over the elements until you have the whole Course Card
    Look for the <div> elements with a class of ic-DashboardCard
  2. Choose Delete Element
    Canvas Browser Tools - Delete Element
    It's Gone!
    Canvas Browser Tools - Element Deleted
    Not Really.
    And you didn't break anything either. You deleted the element from the browser's local copy of the page, nothing on the server has changed.
    To get a new copy from Canvas, continue to step 3
  3. Refresh the page

 

All Done! After the refreshed page downloads, your Canvas Dashboard is back to normal.

 

There's no real purpose for doing this other than to walk you through finding elements to edit on the page, or to make this tutorial. I'm enrolled in a few more than the three courses seen here. For the average Canvas user this may be helpful for writing tutorials and other documentation, or maybe removing and modifying personally identifiable information to take and transfer screenshots safely.

 

 Escalating

 

  1. To modify an element on the page, right click on it or find it in the Elements tab, and choose Copy Copy Selector
    Canvas Developer Tools | Change HTML with JavaScript
  2. Next, click on the Console tab and type in // and paste the selector, press Enter (that is 2 forward slash's)
    This is a JavaScript comment (code that isn't executed) with an identifier we can use to change the content of the <h1> that displays the Dashboard page title
    You should also see undefined on the line below, this is because nothing was executed and nothing was returned
  3. Using either of the 1 line jQuery* snippets below, you can change the title of the Dashboard page or remove the last Dashboard Card from the page
    // change the page title/header
    $('#dashboard_header_container > div > h1').html('My Dashboard')
     // remove the last dashboard card
    $('.ic-DashboardCard:last').remove()

    Each line should edit the page immediately and the console will respond with an updated DOM object

    Canvas Developer Tools | Change and Delete
  4. Refresh the page to clear your changes

 

* The usage of jQuery here is for demonstration purposes. Canvas currently provides jQuery, however it may be deprecated in the future. The following snippet shows how to change the page Title with vanilla JavaScript.

// change the page title/header
document.querySelector("#dashboard_header_container > div > h1").textContent = "My Dashboard";

 

 

 

 Rapid Escalation

If you want to make changes to your Canvas Dashboard more permanently, check out this thread and Github repository.

Sorting Dashboard Course Cards 

canvancement/dashboard at master · jamesjonesmath/canvancement · GitHub 

 

For a deeper look at modifying Canvas pages with Javascript, look at Hiding Content from Certain Roles

While collaborating recently, I’ve found myself referring to the Javascript Console, or is it Developer Tools, maybe Web Inspector; what about adding Browser before, do I provide a link? Sometimes, I don’t know whether to provide a link to an article that discusses the particulars of opening, inspecting or getting to the tab I want to reference; or if I’m insulting someone. So, I've decided to create a series of short tutorials that will hopefully walk new developers through some basic tooling around on Canvas with a Browser and JavaScript. I will also try to use these as references in future posts and discussions. I hope they are helpful, feedback is welcome. Enjoy!

 

Before we get started, let me point out that I won’t be getting lengthy on how to open the tools in each browser since everyone can do that with Google, and the easiest way is to right click the web page and select Inspect Element or Inspect, if you're using Chrome.

 

The following is a brief, but detailed, and rapidly escalating tutorial of things any Canvas User can do with Developer Tools. Along the way, I will share some community resources that will help guide you with additional information.

 

tutoriallevelfocus (tab in developer tools)
#1 - Delete and modify elements, and Refreshfreshmanelements
#2 - Update a course nickname with the APIsophomorenetwork, console
#3 - Updating the DOM and Handling Responsesjuniorconsole, elements
soon - Status Codes and API Troubleshootingseniorconsole, network
tbd - maybe all of the above without jQuerymasters

 

// indexing a few related resources

guidelevelfocus
API Testing: Postman by Garth Egbertadvancedapi requests, json

 

 While searching around the community for this series, I stumbled upon this comment by James Jones.

/hiding-content-from-certain-roles#comment-8682 

The problem with JavaScript resources is that, for the basic stuff, you're not trying to learn JavaScript, you're trying to accomplish a specific task. There's no book or website that I want to sit through the whole thing to learn how to program in JavaScript. I don't want to start with "Hello world." I don't have time to start with "Hello world." I want to solve the problem that needs solved and get back to life.

 

My primary resources are the encyclopedia (Mozilla Developer Network documentation) and the commentary on it (Stack Overflow), using Google to get me to the content I need. I often start with a solution from Stack Overflow and then go look up the right way to do it using MDN.

I'll also add that W3 and W3Schools are great resources for what we're doing.

 

If you have questions or need assistance, please ask in a comment, it will help others.

I wrote this mostly at home, tested, revised, etc. If you work through these tutorials and find any issues please let me know and I will get it fixed right away.

                   

Please let me know if you would like to contribute, I would be happy to add new tutorials and information. Contribute new tutorials with API endpoints that work for user/student roles, no advanced permissions.

                   
Mike Cowen

Collapse & Expand Modules

Posted by Mike Cowen Employee Jul 5, 2018

For various reasons I decided to write a little CSS & JS to do the following:

  1. Load the modules page with all modules collapsed by default
  2. Create a button on the modules page to Expand/Collapse all modules
  3. When a user clicks on a link that takes them to a module in the modules page, make the modules page load with all modules collapsed EXCEPT the module that was clicked on the previous page.

 

This is what the page looks like when first opened. Notice the button at the top "Expand All Modules."

Screenshot of collapsed modules with button allowing to expand all modules

 

 

This is what the page looks like with the modules expanded. Notice the button at the top "Collapse All Modules."

Screenshot of expanded modules with button allowing to collapse all modules

 

I am putting the code here for others to use and improve.  If you make improvements, please share them with the rest of the community.  I am not a coding professional and consider myself to be an intermediate level, NOT an expert (although I got some help from some experts when I got stuck during this project  .)

 

If you have suggestions, please feel free to let me know. I'm always looking to improve my coding

 

 

**DISCLAIMER**

This code is 100% unsupported by me and/or by Instructure. Use it at your own risk. Always try new code in your test or beta environments first. This will probably break. If you don't have someone on staff who can maintain it or fix it when it breaks, you will want to seriously reconsider using it in the first place.

 

 

Add this to the CSS file:

/*  Collapse all modules. 
     use this in combination with JS to auto-open the module link that was
     clicked to navigate to the page */


#content #context_modules .content,
#content #context_modules .collapse_module_link
{
  display: none;
}

#content #context_modules .expand_module_link {
  display: inline-block;
}

 

Add this to the JavaScript file:

//
// Use in combination with CSS that loads all modules in a collapsed state.
// When navigation to the modules page is to a specific module,
// automagically click the module so it opens in an expanded state.
//
var anchor = window.location.hash;
var underscore = anchor.indexOf("_") + 1;
var characters = anchor.length;
var     module = anchor.substring(underscore,characters);
var selector = "span[aria-controls='context_module_content_"+module+"']";
console.log(selector);

window.onload = function() {expand_module()};
function expand_module() {
    document.querySelector(selector).click();
     console.log("click attempted");
}

//
// Add button and script to expand and collapse all modules
//

// Create the button on Modules pages
if(window.location.href.indexOf("modules") > -1) {
     document.querySelector('.header-bar-right__buttons').insertAdjacentHTML( 'afterbegin', '<button class="btn" id="expand-collapse-modules" status="collapsed" onclick="expand_collapse_modules()"><span class="screenreader-only">Collapse or expand all modules</span>Expand All Modules</button>');
    }

// when the button is clicked, expand or collapse all modules that are not currently expanded or contracted.
function expand_collapse_modules()
{

    if (document.getElementById("expand-collapse-modules").status == "collapsed"){
         document.getElementById("expand-collapse-modules").innerText = "Expand All Modules";
         document.getElementById("expand-collapse-modules").status = "expanded";
          var items = document.querySelectorAll("span[style='display: inline-block;'][aria-expanded='true']");

         }
    else {        
         document.getElementById("expand-collapse-modules").innerText = "Collapse All Modules";
         document.getElementById("expand-collapse-modules").status = "collapsed";
          var items = document.querySelectorAll(".expand_module_link:not([style='display: none;'])");
         }
        
    for (var i = items.length-1; i >= 0 ; i--) {  
        console.log(i);
        items[i].click();
    }
        
}

 

The code is also attached to this blog for your convenience.

I recently had the opportunity to teach in Canvas again for the first time in a little while, and I noticed that my behavior when grading was to always navigate to my instructor to-do-list to launch Speedgrader. However, I had navigate back to the home page to view the to-do-list every time I finished grading all submissions for an assignment. This caused me a lot of frustration as I knew that the to do list was accessible via the API. So I wrote a little bit of code that I have found to be indispensable now to me as an instructor.

 

The following code attaches a little button in Speedgrader to navigate to the next item on the todo list for teachers in the course. If you do not have a to do list on the home page (ie: Admins) you will not see this new button.

And when a teacher has no other assignments in their to do list they get a nice thumbs up indicating this is the last assignment that needs grading. However, the teacher will still need to complete any submissions on that assignment to actually be done grading.

Hope your teacher's find it useful!

var currentURL = window.location.href;
if (currentURL.indexOf('/gradebook/speed_grader?') > -1) {
    var courseID = currentURL.match(/\/courses\/(\d+)/g);
    var myRegexp = /(?:^|assignment_id)=(\d*?)(?:\D|$)/g;
    var doRegexp = myRegexp.exec(currentURL);
    var assignment_id = doRegexp[1];
    // console.log(assignment_id)
    $.get('/api/v1'+ courseID +'/todo?per_page=100', function (response) {
        if (response.length > 0){
            selectNextUrl = parseData(response, assignment_id)
            if(selectNextUrl != false){
                addToUI = insertHTML(selectNextUrl['html_url'])
            } else {
                html = '<div><a id="speed_grader_gradebook_link" class="Button Button--icon-action gradebookActions__Button gradebook-icon-link" title="All done! No more assignments to grade."><i class="icon-like" aria-hidden="true"></i><span class="screenreader-only" aria-hidden="true">All done! No more assignments to grade</span></a></div>'
                $('.assignmentDetails').after(html);
            }
        }
    });
}

var parseData= function(response,id) {
    // console.log(response)
    for (var i = 0; i < response.length; i++) {
        assignment_id = response[i]['assignment']['id'];
        if (String(assignment_id) !== id){
            return response[i]
        }
    }
    return false
}
var insertHTML = function(html_url){
    html = '<div><a href="'+ html_url +'" id="speed_grader_gradebook_link" class="Button Button--icon-action gradebookActions__Button gradebook-icon-link" title="Next Assignment that Needs Grading"><i class="icon-arrow-open-right" aria-hidden="true"></i><span class="screenreader-only" aria-hidden="true">Navigate to Next ToDo Assignment</span></a></div>'
    $('.assignmentDetails').after(html);
}

If you need help knowing where to add the code in Canvas, see this guide: How do I upload custom JavaScript and CSS files to an account?

Jeremy Bell

Canvas Scrolling Side Menu

Posted by Jeremy Bell Jun 27, 2018

Issue:

In longer lessons, students can become disorientated on how the current information in their viewport relates to the entire lesson.

 

Custom Fix:

Create a sidebar menu that scrolls with the students viewport.  It dynamically captures that main sections of the lesson and highlights the section that the student is currently viewing.

 

 

Code:

Code is a little long, so I have it has a gist on GitHub.

Canvas Scrolling Side Menu · GitHub 

Hi all! I’m an Instructional Technologist / Developer in Emerson College’s Instructional Technology Group.

 

Emerson has recently switched to using Panopto for video hosting and we’ve been working through some growing pains. We’ve heard from our reps for both Panopto and Canvas that we’re not the only school having the below problem, so I wanted to share the workaround we’re using.

 

The problem, as reported by our users, occurs on iOS versions of the Canvas Student and Canvas Teacher apps on pages that contain embedded Panopto videos requiring login to access.

 

On Android versions of the Canvas apps, users were offered the chance to login and watch the embedded videos.

Android user experience with Panopto embeds in mobile apps

 

On iOS, however, users encountered a frame with the text “Server Error” and “404 - File or directory not found.”

iOS user experience with Panopto embeds in mobile apps

 

Since our online courses are taught solely through Canvas and all have embedded Panopto videos on the front page, this was not the kind of first impression we wanted online learners to have of their courses.

 

We’re still working with Canvas and Panopto to find a solution, but in the meantime I wrote some custom Javascript and CSS for the Canvas mobile apps to improve the user experience. To keep the learning experience consistent across devices, the custom JS works for both iOS and Android versions of the Canvas apps.

 

First, the Javascript checks to see if there’s an iframe on the page with a source URL matching our Panopto URL (ie, https://emerson.hosted.panopto.com/).

 

Then, if it finds one or more matching iframes, it creates and adds a paragraph to the top of the page that encourages users to view the course in a browser instead:

“Warning: Some video content on this page is not supported in Canvas mobile apps. For the best experience, please access this course via a browser.”

""Android app view of the new warning message.

 

""
iOS app view of the new warning message.

 

Finally, the script replaces each matching iframe with just a plain link to the video that opens as an External Tool.

""
Android and iOS users both see the same "Watch video" link; user experience is kept consistent.

 

Panopto video shown with "External Tool" wrapping.
Clicking "Watch video" opens the video for playback while keeping some Canvas navigation options to return to course content.

 

The result is a much cleaner interface and user experience, which we’re considering keeping even when the original error gets resolved.

 

In theory this code could be adapted to replace any other iframes that don’t display properly in mobile by changing the source it’s checking for. So far we’ve only gotten reports of issues with Panopto, but that might change as more teachers and students start to use the apps.

 

I've shared a version of the code we’re using on GitHub. It's been edited to be a bit more generalized and is pure Javascript, so no need to load in jQuery. 

 

Canvas mobile - Custom embed user experience · GitHub 

 

If you aren’t already using custom mobile JS and CSS files, you can upload these two as-is* using these instructions: https://community.canvaslms.com/docs/DOC-10862-4214724282

 

If you are already using a custom CSS file for mobile, just update that file with the code from this CSS file.

 

If you’re already using a custom js file for mobile, you can copy to your file the two functions:

function replaceEmbed(frame,linkText) { … } and function customEmbedUX() { … } *

Then, just call customEmbedUX() from within whatever document ready type function you’re using.

 

If you use it, please keep some form of attribution with it. If you have suggestions for improvement, I’d love to hear them!

 

* Edited to add: One minor edit is needed in the code to make it work for you. In the customEmbedUX() function, change the Emerson URL to your Panopto URL. 

CHANGELOG

Jun 6, 2018 1:01 PM

May 31, 2018 3:11 PM

 

README

If you're a system administrator of a Canvas LMS instance with a deep organization of sub accounts you have inevitably found yourself in one of Dante's 9 Circles of Hell, repetitively clicking Sub Accounts at each and every level as you drill down to where you need to go. Here at CCSD we have over 8,000 sub accounts, and yet this only affects 5 people. So we will share this to help anyone else who is trapped.

 

The JavaScript and CSS files here add a directory style recursive HTML menu to the Canvas global navigation admin tray...

 

In Pictures

CollapsedExpandedSearch
Admin Tray CollapsedAdmin Tray ExpandedAdmin Tray Search

Configuration & Setup

Zero to little configuration is needed for admintray-subaccmenu.js, so I won't even bother explaining subacctray.cfg

Option 1

  1. Host the file admintray-subaccmenu.js on a secure server (https) or somewhere like Amazon S3
  2. Copy/append the contents of admintray-subaccmenu.inc.js to your Canvas Theme JavaScript file, point the URLto the hosted file.

Option 2 (Quick Start)

  1. Just copy the example snippet below that uses this repo's version and RawGit   to your Canvas Theme JavaScript file. However, please note that the RawGit URL points to the repo source and any changes to it may affect your usage later. I recommend hosting your own for stability, but this can get you started.

 

Once loaded the script will initialize an API call (to /api/v1/accounts/self/sub_accounts) and loop through x/100, collecting every sub account in your instance, compile a recursive HTML unordered list and store it your browser's localStorage.

Depending on the number of your sub accounts and internet speed, this will take a moment, be patient for the first load, open the tray and wait for it to show up. This takes us about 45-60 seconds on production. You will see a loading indicator in the Admin Tray while it compiles.

Features & Use

 

Instance Independent

There are some users (like Instructure Canvas Remote Admins) who may login to multiple Canvas Instances/institutions, so each menu will be stored in localStorage based on the uniqueness of x.instructure.com, including x.beta.instructure.com, x.test.instructure.com, and xyz.instructure.com.

For most users and institutions this will go mostly unnoticed unless you have different sub accounts between your Production, Test and Beta environments. I made this update to help those users it will affect. I also personally hate copying, pasting or replicating files just to change 1 line. This update allows 1 file to be hosted on a CDN like Amazon S3 and simply change the var show_results_parentvalue in admintray-subaccmenu.inc.jsor leave it blank.

Therefore an already minified file has been provided in the repo.

 

Alphabetical Sorting

Yup! As far as I know, the Canvas API does not allow sorting API calls alphabetically during pagination. The entire stack of sub accounts is sorted prior to building the menu.

Localized Search

Using JavaScript/jQuery to search within the stored HTML, preventing further API calls. You can search the entire sub account menu by name.

Search Result - Prefix Parent Account (Skip-to-Depth Display)

I don't know what else to call it, so here is an explanation.

Suppose your directory structure looks something like the following, where (#) is the depth of the account.

  • High Schools (1)
    • George Washington HS (2)
      • Course Shells (3)
      • SIS Courses (3)
        • English (4)
        • Math (4)
        • Science (4)
  • Middle Schools (1)
    • Betsy Ross MS (2)
      • Course Shells (3)
      • SIS Courses (3)
        • English (4)
        • Math (4)
        • Science (4)

 

When we search for Science and get multiple sub accounts of 'Science', we can't identify which one we want to choose.

  1. Science
  2. Science

So if we map the results depth of 4 to it's parent depth at 2 (instead of 3, SIS Courses) we can get a result like:

  1. George Washington HS > Science
  2. Betsy Ross MS > Science

 

And both are links to their respective account.

 

Examples

// one skip
show_results_parent = { 4:2 }
// expected result:
// George Washington HS > Science

// or multiple skips, must be unique
show_results_parent = { 4:2, 3:2 }
// expected results:
// (4:2) George Washington HS > Science
// (3:2) George Washington HS > SIS Courses

Note: If you don't define the skip, it will not display a parent

Reload

The ↻ button at the bottom right of the menu will clear the menu from localStorage and recompile it for the current Canvas instance. Use this when you or other admins have added or removed sub accounts.

 

User Impact

As mentioned above, CCSD has 5 system admins, with 40,000 Employees and over 300,000 Students. Be kind to your users, use the snippet below to reduce the impact of your admin-onlytools. This is included in the repo as admintray-subaccmenu.inc.js

if (ccsd.util.hasAnyRole('admin','root_admin')) {
  // used for search results, result:parent, see documentation for more details
  var show_results_parent = {}
  // async load the sub account menu script
  $.ajax({
    url: 'https://cdn.rawgit.com/robert-carroll/ccsd-canvas/master/admintray-subaccmenu/admintray-subaccmenu.min.js',
    dataType: 'script',
    cache: true,
    data: {skipd: JSON.stringify(show_results_parent)}
  })
}

 

Code here...

ccsd-canvas/admintray-subaccmenu at master · robert-carroll/ccsd-canvas · GitHub 

TL:DR; There is a free self-paced course on Canvas Network that can get a non-programmer to write scripts with Ruby & the Canvas API...

 

Calling all Canvas admins who have ever been frustrated by an Instructure employee telling you, "It can be done through the API." The barrier to entry for using the Canvas API and programming in general can seem very high. Luckily, it's easier to learn than you think. 

 

As an Implementation Consultant, I understand how frustrating it is to be limited in what you can do through the UI alone. I've spent the last 18 months as an onsite resource for a Canvas client, and have needed to leverage the Canvas API more times than I can remember. Whether it is to pull information, or push a default grading scheme to courses, there are some things that are just easier through the API.

 

I've spent a very long time these past few months building curriculum and recording videos as part of the instructional unit. This course is being offered by me, and is not to be considered an official Instructure course. If the engineers saw my code they might have an aneurism because it is pretty far from beautiful. However, the intentional design of the course is to help Canvas Administrators, who like me once, thought that writing a script and running it on your own computer was a task for only the smartest of people.

 

 

The course is divided into 5 instructional units, with over 2 hours of recorded video and exercises to accompany them. The course is fully self-paced and does not have a close date. Hope you enjoy!

 

Sign up for the Ruby & the Canvas API 

Overview

One of the most common questions I'm asked when consulting our Canvas partners is how to synchronize the roster from Canvas into an external application. There are a variety of ways this can be accomplished, each with their advantages and disadvantages depending on the needs of your product and customers. This post is meant to be a resource for external tool providers hoping to maintain a synchronized roster between their systems and Canvas.

LTI-only Workflow

Requirements

This approach requires an LTI integration to be configured and visible somewhere within a Canvas course. Ideally, this LTI connection will already have an LTI SSO mechanism. If username, login ID, email, and/or SIS ID is required, make sure the privacy level is set to Public in the tool configuration. Otherwise, Canvas will only send an opaque LTI user id (as the user_id parameter) and a Canvas ID (as the custom_canvas_user_id).

Advantages

  • API access not required
  • Interoperable
  • Can provision users on-the-fly as they launch the tool

Limitations/Challenges

  • LTI tool is only aware of users who've launched their tool at least once
  • Unidirectional: cannot push new enrollments to Canvas
  • Cannot determine if users drop courses or are deleted from Canvas

Instructor/Admin/Student Workflow

  1. Configure an LTI tool in Canvas
  2. Launch the tool
  3. Tool consumes user information (name, email, ID's, roles, contextual information etc...) and attempts to match on an ID. Best practice is to match on the user_id from the launch and then fall back to some other ID if a match is not found
  4. If a match is confirmed (and the signature matches), let the user access their information in your application
  5. If no match is found, either or send them through a user-creation flow within the iframe, or auto-create a user for them based on the information in Canvas (you may want to let them set a password at this point, or email them a registration URL)

LTI + API Workflow

The main limitation of the LTI-only workflow is that it requires users to launch the tool through the LMS. This often does not make sense. For example, some tools are used only by instructors, so there is no opportunity for the students to launch an LTI tool. The LTI + API workflow works best for tools targeting teachers or students, but is limited when synchronizing rosters for large accounts.

Requirements

This approach requires an LTI integration to be configured. It also must use OAuth2 to obtain instructor and student API tokens.

Advantages

  • Can provision entire courses or sections with a teacher token, or entire accounts with an admin token
  • Can remove/hide users that have dropped courses or have been deleted
  • Bi-directional: if authorizing user has permissions, can create/update/delete enrollments within Canvas

Limitations/Challenges

  • Requires implementation of OAuth2
  • Requires heavy usage of Canvas-specific API’s making it not interoperable
  • If attempting to sync entire accounts, can be slow for large accounts due to API throttling

Admin Workflow

  1. Admin launches the tool from account navigation or elsewhere
  2. Tool kicks off OAuth2 if it needs to obtain or refresh a token
  3. Tool retrieves a list of courses via the accounts API, then uses the Enrollments API for each course
  4. Tool can then create/update/delete users from their database based on the results


NOTE: This approach is not recommended for large accounts due to API throttling. Instead, either use the LTI + SIS + API approach in the next section or see the Instructor Workflow below.

Instructor Workflow

  1. Instructor launches the tool from course navigation or elsewhere
  2. Tool kicks off OAuth2 if it needs to obtain or refresh a token
  3. Tool consumes the custom_canvas_api_domain and custom_canvas_course_id from the LTI launch and checks the Enrollments API for the course. Alternatively, the tool can check the enrollments for the instructor to sync all courses they are enrolled in; this requires consuming the custom_canvas_user_id
  4. Tool can then create/update/delete users from their database based on the results

Student Workflow

  1. Student launches the tool from the course
  2. Tool kicks off OAuth2 if it needs to obtain or refresh a token
  3. Tool consumes the custom_canvas_course_id from the LTI launch and checks the Enrollments API for the course


NOTE: Some students may not have permission to view the list of users in their course.

LTI + Reports API Workflow

The main limitation of the LTI + API workflow is that it doesn’t work well for large accounts. By using the Reports API, tools can generate reports that list all users in an account. This is best used for institutions who’ve implemented SIS, but it’s not a requirement.

Advantages

  • Can provision entire accounts without worry of being throttled
  • Can remove/hide users who have dropped courses or have been deleted
  • Bi-directional: if authorizing user has permissions, can create/update/delete enrollments within Canvas via SIS Imports API

Limitations/Challenges

  • Requires an admin-level token
  • Best if institution has implemented SIS; SIS exports only show resources with SIS ID's. If SIS ID's are absent, you'll need to understand how to interpret the provisioning report
  • Requires implementation of OAuth2
  • Requires heavy usage of Canvas-specific API’s making it not interoperable
  • Requires parsing CSV's and downloading files
  • Requires understanding the structure of SIS CSV files or the provisioning report

Admin Workflow

  1. Admin launches the tool from account navigation
  2. Tool kicks off OAuth2 if it needs to obtain or refresh a token
  3. Tool starts a report via API


NOTE: There are two reports of interest here. The easiest to interpret is the SIS Export report; however, the SIS Export report only shows users with SIS ID's. In this scenario, you’ll need to rely on the provisioning report instead.

  1. Tool polls the report to check progress
  2. When the report is complete, tool downloads the report and parses the data to synchronize users
  3. Tool can then create/update/delete users from their database based on the results

Instructor/Student Workflow

Instructors and students don’t have access to the reports API. However, the same functionality of the LTI + API integration described in previous sections can still be used to compliment the LTI + Reports API workflow. For example, you may want to do a nightly sync at the account level, but still use API + LTI to synchronize data when a user launches the tool.

NOTE: Admins can also manually send reports to a tool provider for one-time provisioning of new customers; however, in order to maintain an accurate list of users and enrollments, the tool would still need to sync data via LTI and/or API.

NOTE: The workflows which leverage API can be further automated or simplified by obtaining an admin token with the “become other users” permission. This token can then be used to make API requests on behalf of teachers and students by appending as_user_id=<student/teacher user id>. However, this can be problematic for high numbers of assignments or students as you’ll likely experience throttling issues.

SIS-only Integrations

This won’t be discussed here. For more information about integrating with Canvas as a SIS provider, please contact: Chase Pendleton <cpendleton@instructure.com> (SIS Partnerships Manager)

Roadmap: LTI Names and Role Provisioning Services (Membership Service)

We’re currently working toward implementation of the IMS LTI Names and Role Provisioning Service. We hope to have most, if not all, of the service implemented by the end of 2018. This service will make the LTI-only workflow more useful for instructors in particular as it doesn’t require all users in a course to launch the tool, and it'll allow tools to be aware of changes in the course roster.

Requirements

The requirements are the same as the LTI-only workflow outlined above, but you must also conform to the IMS LTI Names and Role provisioning Service as a tool provider.

Advantages

  • Does not require API access
  • Can provision an entire course on-the-fly when an instructor or admin launches a tool from the course
  • Interoperable

Limitations/Challenges

  • Will not be able to synchronize an entire account
  • Launch must occur in the context of a course
  • Unidirectional: cannot push new enrollments to Canvas

Workflow

  • User launches tool from course
  • Canvas sends a service URL that a tool can use to see a full list of users for the course
  • Tool can then create/update/delete users from their database based on the results

Concluding Remarks

This isn’t a comprehensive guide to solving the challenge of synchronizing users and enrollments between systems. I encourage creativity and product discovery with your customers to see how they envision your product working within Canvas. My hope is that this guide has at least allowed you to gain a better understanding of what is and isn’t possible so your organization can make decisions that help our mutual users have the greatest experience possible.

 

For those who’ve already solved this problem, how has your integration combined API and LTI to provision users from Canvas? Are there any interesting approaches you've taken that I haven’t considered here? Please share in the comments!


Jesse Poulos
Partner Integrations Specialist
Instructure

It’s no secret that externally handling pages was difficult with Canvas. The problem was the API didn’t expose an ID for the page, so you needed to look it up by title.

https://<ORG>.instructure.com/courses/<COURSE ID>/pages/<MY-PAGE-TITLE>

 

Simple enough if you know the title and the users never change it, but that’s just wishful thinking.

 

I’m not sure when this was released, but Mark mentioned it and I couldn’t be happier.  So now the page details include a page_id field you can store if your external system needs to link back to a specific page.

{     
   "title": "My Page Title",    
   "created_at": "2018-01-05T15:54:00Z",    
   "url": "my-page-title",    
   "editing_roles": "teachers",    
   "page_id": 1234,
   
   "last_edited_by": {        
      "id": 4321,        
      "display_name": "Jason",        
      "avatar_image_url": "...",        
      "html_url": "..."    
   }
}

The API call is just as you would expect.

https://<ORG>.instructure.com/api/v1/courses/<COURSE ID>/pages/<PAGE ID>

Or, if you still need to use the page title.

https://<ORG>.instructure.com/api/v1/courses/<COURSE ID>/pages?search_term=<My Page Title>

Thanks Canvas for continuing to make things better!

 

 

 

Originally posted by me at codesequences.com, Canvas LMS Page Handling Improvements – Code Sequences 

As Canvas admins we have a need to track external tool usage, across all course shells. We often are very interested to know adoption of a tool at our institution. Or if we determine that a particular tool has a technical problem, we need to be able to find which teachers to contact. Unfortunately neither the LTI Tool Report nor External Tool API are suited for this task, in themselves. Both are only effective at finding tool installations either at tool or account contexts. What is needed is a way to find when an external tool is enabled in a course navigation, used in an assignment or module. Fortunately, we can gather this information from the Assignments API, Modules API, and Tabs API and weave the external tool info with course and teacher info gathered from the Courses API.  

 

In this post, (and in perhaps subsequent posts) I will describe my approach, which is developed in Python. I am eager to receive input from others to determine how my code can be improved. My Python code uses a perhaps idiosyncratic style, choosing to wrap api calls in Python generators with closures, rather than using objects and classes. It seems that this more lightweight approach is well suited for getting data out of Canvas APIs and weaving it together into reports.  Again, I am interested in having a discussion on this - there may be a better way than what I am doing. Folks familiar with Clojure and the videos lectures of Richard Hickey will guess my motivations -  ultimately I  am trying to make something complex simple. 

 

The heart of the report building process looks like this:


# ----Iterate over all courses ---------------------------
for c in all_courses():
    # Build report in a Python default dictionary, where the default type is list,
    # allows appending without checking key first
    xtool_data_for_course = defaultdict(list)
    # ---- And for each course, retrieve all installed x-tools, tabs, modules, and assignments
    # ---------XTOOL INSTALLS-----------
    for x in course_xtools(c['id']):
        xtool_data_for_course['XTOOL_INSTALLS'].append(x)

    # ---------TABS--------------------
    for t in course_tabs(c['id']):
        xtool_data_for_course['TABS'].append(t)

    # ---------MODULES---------------------
    for m in course_modules(c['id']):
        for i in module_items(direct_url=m['items_url']):
            xtool_data_for_course['MODULES'].append(i)

    # ---------Assignments---------------------
    for a in course_assignments(c['id']):
        xtool_data_for_course['ASSIGNMENTS'].append(a)

Each of the for loops is iterating over a Python generator. The generator is "produced" by the api_wrapper_function, which "encloses" all the parameters specific to a particular endpoint (URL, authentication, and options) and typically receive an ID that specifies which course to query. The generator also may have a filter predicate specified, so only  active, external tools are returned. Another module specifies the defining parameters for the generators, so our client code here does not need to sweat the details.

 

The data-structure produced is a list of Python dictionaries for each course that is found to have at least one external tool in-place. 


[{COURSE_INFO:{course_code, name, id},
     INSTRUCTOR_INFO: [{instructor INFO}, {}..],
     XTOOL_INSTALLS:[ {xtool tool info}, {},...],
     TABS: [ {tab info} ... ],
     MODULES: [ {module item info}, ...],
     ASSIGNMENTS: [ {assignment xtool info}]

Each record for a course will have COURSE_INFO and INSTRUCTOR INFO and may have XTOOLS, TABS, MODULES, ASSIGNMENTS.  This data-structure is processed into a CSV report by another script. Right now the process works, with a few problems I am still working on. As you may guess, a process that inspects each and every course with multiple (four)  endpoint calls is going to be slow. May look at running multiple threaded agents that split up the list and then aggregate results at the end. Another challenge is surfacing the right information about external tools in course sites, and excluding less critical info. For example, my current report shows up with bunch of redirects. In most cases that is not what we are interested in. Ok, that is it for now. If folks are interested will the underlying functions/generators that make this work. Bit of code-clean up to do first! Would like to thank Peter Love for the advice he offered and help tracking down all the endpoints needed to generate this report. Finally do let me know if there are better ways of achieving this same ends. Let me down easy... but do let me know, thanks!

 

References:

Assignments API (Assignments - Canvas LMS REST API Documentation 

Modules API (Modules - Canvas LMS REST API Documentation ) 

Tabs API (Tabs - Canvas LMS REST API Documentation )

Python Generators - Generators - Python Wiki 

Kansas State University is proud to announce our first open source LTI application. It is an attendance taking tool. The source code is available on GitHub.

 

Previously we released a Java library to interface with the Canvas API. (blog post | code) This API library is now being used by a couple other universities and we have received several pull requests to add more functionality. Thank you to the Canvas community for embracing open source software and contributing back! We hope that this will continue.

 

Now, as part of the attendance taking application, there are a few other bits we would like to highlight. All of our LTI applications are based on an LTI launch framework to handle common launch tasks and provide common resources. We hope that others may find it useful in implementing their own LTI applications. In order to assist in learning how to use it, we have created a minimal self-contained LTI application that shows how to use the launch framework. Here are some details on these parts:

 

Attendance LTI application

This application was developed before Instructure released the Roll Call application as open source so it may not be quite as valuable now but it may still be worth a look if there is functionality you are missing in the existing Canvas experience.

 

Initially, we developed this application for the K-State Polytechnic Campus in Salina, Kanas. Their aviation maintenance program has strict guidelines from the FAA, which requires them to track contact time between students and instructors down to the minute. Later, we extended the application to be more broadly useful to the campus community.

 

Some of the features of the application include:

  • Minute versus daily attendance tracking, configurable by the instructor on a per-course basis
  • Instructors can choose to allow or prohibit students from seeing details of their attendance records
  • Attendance can be pushed to the Canvas grade book as an assignment

 

There is still some room for improvement to make this application more useful to other institutions. Depending on your existing environment, here are some specific hurdles you may face when trying to deploy this application:

  • The application was written against an Oracle database. It should not take too much effort to make the application database agnostic since we use Hibernate. We may get around to this at some point to allow for better testing.
  • We tried to avoid using K-State specific terms and branding but some did slip in. For example our SIS ID is commonly called the "Wildcat ID" and shows up as "WID" in some parts of the application.
  • The deployment process is not exactly simple. It would be great to have a docker image that packages the application and container together for easy deployment. Currently it runs on Wildfly. There is an INSTALL.md file in the repository that details every step needed to install the application in a UNIX-like environment.

 

LTI Launch Framework

This framework is based on Spring and handles LTI launch requests. (source code: GitHub) Spring Security is used to handle verifying the OAuth signature that comes with the LTI launch request. It also assists your application in getting an API access token from the user via the OAuth2 flow.

 

Because this framework is based on Spring, you must implement some interfaces and make the implementations available as Spring beans. These beans handle persisting user OAuth tokens for subsequent application launches, generally setting up the LTI application, and ensuring that the application is communicating with the correct Canvas instance. The last part is vital for hosted Canvas customers because it prevents a test version of the application from accidentally interacting with a production instance of Canvas. This mixup can readily occur when the test environment gets automatically overwritten with production settings periodically.

 

We have run into challenges as it relates to error handling in Spring. At the moment it is left mostly up to your application to handle specific errors correctly. This leads to some code duplication for handling common LTI related error conditions. As time permits, we are working to improve this problem.

 

Minimal LTI application

We created a minimal LTI application to demonstrate how to use the LTI Launch Framework. (source code: GitHub) The application outputs a simple page of text. It only authenticates the LTI launch request and then echoes back information including the username and LTI parameters being used in the application.

 

Unlike the Attendance application, this one is trivial to deploy! It uses maven to build an executable JAR which contains an embedded Tomcat server. Running it as as simple as executing the command: java -jar lti-launch-example.jar

 

Comments (and pull requests) are welcome!

This is a follow up to my previous blog post, "A Simple Python GET Script". Personally, I find myself using POST requests quite frequently. So while the GET script was great for showing at a basic level how Python can interact with your Canvas instance, this blog post can hopefully show why this is such powerful tool. 

Creating a New User

I'd like to start off by saying that Canvas makes it extremely easy to create new users. In fact, the ease of use of the Canvas system is the reason that Financial Mentors of America (FMA) chose to develop it's online content in it, as opposed to other LMS that are available. But there are a few advantages of taking the complicated route and creating the user through the API. 

 

The main reason I do it this way, in some cases, is because creating a user through the API gives me full control over the process. I can completely register a user on the back-end, pick him a password, and skip sending the auto-generated confirmation email to send my own welcome letter instead. You can view all the other neat things by viewing the official API documentation

 

This, in a way, demonstrates the real power of the Canvas LMS. You don't need to know anything about the back-end to be able to use Canvas to a great extent. However, for the users who take the time to learn it, it opens up a whole new world of functionality. 

The Script

POST Script in Python

The requests library really makes this process simple. You'll notice from the first blog post that there are many of the same elements in it. You still have a URL, although the URL changes depending which API call you are making. You still have headers and you still need your access token. In fact, I have only changed two parts of the last script. 

  1. payload = {...,...,...}
  2. r = requests.post(..., ..., params=payload)

 

Payload

This is where we tell our script which of the special parameters we want to include in our post. In this case, I've picked the users name, assigned to the name the email address, picked his password, chose not to send his confirmation email, and auto-registered him. Once again, for any request you make, there are almost always custom parameters you can include in your request. These are all documented in the Canvas API documentation

 

 

r = requests.post(..., ..., params = payload)

This is where we tell Canvas what kind of API request we are making. In my first blog, we told Canvas to GET information, in this blog we are telling it to POST information into our Canvas instance. In addition to the URL and the headers, we include in the POST the "Payload" which contained the specific information we wanted our POST request to do. 

 

The Result

Script Result
So I have python here set-up to return to me some mumbo-jumbo, However, the status code 200 means that my request went through. I was able to log-in with my user and the selected password, and my parameters went through successfully. I usually use stuff like this in conjunction with enrollment API, so after I have the created user, I can enroll him/her into a course with whatever level of access I like, with about the same level of control. 

 

Hopefully, this has been useful in not only giving you an example of some simple, working Python code that can interact with Canvas but also demonstrating some of the inherent usefulness in using the API to gain more control over some of the things you may have to do in Canvas on a regular basis.