Universal Student View Button for Teachers

msgarcia
Community Contributor
23
7329

The Challenge

While the Student View button is easily found under the Settings panel of the course navigation menu, many of the instructors at my institution still would prefer for it to be accessible on their home screen. Other Learning Management Systems offer a Student View button that is a bit more global, thereby allowing an instructor to quickly jump into the Student View from just about any location in LMS.  @ian_heung  had previously suggested the idea for this feature in his idea, https://community.canvaslms.com/ideas/2057-quick-toggle-student-view‌, and its status has been updated to be on the Product Radar, but I am uncertain as to what the timeline for deployment may be.

Solution

I wanted to provide users with a solution for the interim period of time, while we wait for the release of this new feature. So I set out to create a javascript based option that would do the following:

  1. Determine if the user has an associated role as a Teacher for one or more courses.
  2. Render a "global" Student View button in the upper right corner the courses associated with this user.
  3. Hide the button when the user triggers the Student View.
  4. Prevent the button from rendering if the user is in a course where he or she is technically a Student.

For this solution, I simply created a custom javascript file with script that is deployed whenever a course successfully loads. The script assesses and validates information associated with the user, and uses Instructure's stylesheets, along with some javascript and jQuery, to append and render the default student view button in a new location. This button is placed along the top header of a course in the upper right corner, and can be clicked at any time by the teacher to trigger the student view. 

Demo

This video is currently being processed. Please try again in a few minutes.
(view in My Videos)

Repository

If you are interested in applying this to your environment, please feel free to fork or download from my Bitbucket repository, which can be found here.

23 Comments
chriscas
Community Coach
Community Coach

What does your global Student View button actually do?  I'm curious because as far as I know, the Student View button within each courses really uses a somewhat hidden "test student" that's unique to each course (once you use student view, the test student becomes visible on the course grades page too).  In a more global context (on the Dashboard for example), there wouldn't be an equivalent student role to use (In fact, using student view from a course, going to the dashboard doesn't work).

msgarcia
Community Contributor
Author

Great question. This button is more to remove the need for faculty to have to click on "Settings" in the navigation bar. Clicking this button is exactly the same as clicking the "Student View" button in settings. It creates a Test Student instance in the grade book, and returns the instructor to the home screen of that particular course. Just as with the regular student view, you are unable to navigate to the course dashboard. 

Ultimately, my decision to curate this came from an instructor request related to workflow. For many instructors, depending upon the length of their course navigation bar/screen size and orientation, scrolling down to tap on "Settings" in the navigation bar for a course, and then having to reorient themselves with the location of the "Student View" button amongst the other buttons can be a somewhat slow and cumbersome process. Additionally, for new instructors who have not used Canvas before, going from an LMS where the option to switch to "Student View" is feature prominently on most if not all pages of a course shell, to Canvas where the feature is tucked away in settings can make for a more obtuse UI.

chriscas
Community Coach
Community Coach

Ah got it, Thanks!  I think I got thrown off from the opening paragraph where you were talking about having the more global student view.  I was wondering if you somehow got an even deeper hack or something working.  Having the student view button on the course homepage does seem like a useful addition.

ben_hudson
Community Contributor

We added this to our environment at the end of last week, been a week and we've heard nothing but good comments from our teachers. Good job on the button it works great!

thomast
Community Participant

Hi Mark, I have very limited experience with Java.  However, I have uploaded Java (created by others like you) to our Canvas instance.  I uploaded your desktop file to our test instance.  However, I am seeing the button when I access a test course where I am the student.  I am also an admin on our account.  Can you provide any guidance?  I'd love to get this setup for the faculty here at our college--they will really appreciate it!

Thank you,
Treva

msgarcia
Community Contributor
Author

Treva,

Thanks for reaching out. So, I loaded the code that I have in the repo on my account, and went into courses where I am listed as a student, and I did not see the button. I think that we may need to look further into your course instance to test this and find out why you are still seeing the button. My initial hypothesis is that we will find an instance where the boolean test to truly determine that you are a student in the course is returning "false" at the moment. Let's try and set up a conference call via Zoom, and I can walk you through it. Direct Message me and we can work out the details.

tdelillo
Community Champion

I stumbled across this yesterday, and was excited to try it. We successfully added the script to our beta environment and started playing, but the fact that you always get kicked back to the Home page means you're not saving a lot of time or clicks with the script versus without. I love the idea, but I'd love it more if it toggled to the student view of the page you're on (which is what our faculty have been asking for), rather than navigating away. Any idea how daunting a scripting challenge that would be?

msgarcia
Community Contributor
Author

Tracey, that was my original plan was to have it act in a way that whatever page you clicked it on would be the page that would render in the student view. However, Instructure has coded Canvas such that, when Student View is triggered, there is an Ajax call to the server, and once the callback is returned successfully, the user is redirected to a specific page. As such, and because the heavy lifting is happening server side, there is nothing that I can do in my script. Javscript redirects are ignored, as they would take place before the ajax call, or would be ignored, in the hierarchy of events. Adding meta tags via javascript wouldn't work either as the pages that are loaded based on the switch wipe any existing code injections on the client side. 

I am hopeful that Instructure will update/modify their UI/UX in the future, so that there is a persistent Student View button that you can simply click at any time to turn it on/off on the page that you are currently on.

James
Community Champion

This was my assessment of the situation as well and why I never made it even as far as Mark did in writing something that would do this.

One possibility, not flushed out and probably not worth pursuing, would be to write current location and timestamp somewhere (browser local storage, cookie, or custom user data) and then query that on every page load to see if that information is available and recent. The browser's local storage or a cookie would be faster since it wouldn't require a network call for every page. If the information exists and the timestamp is within a few seconds of when it was set, then you can redirect on the page load. You can destroy the information as soon as you've read it and determined that a redirect is necessary.

With a user script, you can make it run before the page loads so the redirect happens quickly, but if it's part of custom JavasScript, then you would have to wait for the page to load and then send the redirect and it could be confusing to people, although I imagine they could get used to it for the benefit it would provide.

James
Community Champion

While still not universally available within a course, Canvas is making progress on the Student View button. It will soon be available on the course homepage.

It's currently available in beta testing and is scheduled for release into production on February 17, 2018. https://community.canvaslms.com/docs/DOC-14088-canvas-beta-release-notes-2018-02-06 

I didn't see anything in the release notes that suggested they added the inner workings needed to direct you anywhere other than the course home page and when I look at the source code for beta, it still hardcodes the URL for the course homepage.

Still, it reduces the need to go to the Settings page first.

mjennings
Community Contributor

msgarcia‌, I just came back across this and was wondering if something like this could be modified to place a button in the location you have that would be based on role to link to role specific tutorials. We have developed a website for students and one for faculty with different info and was looking for a good way to link these into the courses but it is a bit beyond the scope of my skill set. I actually posted about this a while back (Custom links to training in all courses) and some recommend me over here a year and a half ago. I had to put this on the back burner then, but now I am getting requests for something like this and though I would check back into it. Any help you could give would be appreciated. Thanks.

cesbrandt
Community Champion

msgarcia, excellent work! I've been toying with the idea of making this exact script, but I came across this blog post as I was looking for something else and had to give it a go. Saves me some time setting up my own. Smiley Wink

I do have a modification for your script, though. If you adjust the button anchor to use XHR, instead of a standard link, you can have it reload the page upon success, effectively allowing the user to stay on the same page when using it.

/* ------------------ BEGIN FUNCTION SECTION */

// Generate a universal "Student View" button on
var universalStudentView = function() {
    var isTeacher, isCourse, courseId, templateButton, sutdentViewVisible, studentViewURL, getPath;
   
    // Determine if the user is actually a teacher
    if (ENV['current_user_roles'].includes('teacher') && $('.ic-app-course-menu.list-view nav #section-tabs li.section a[title="Settings"]').is(":visible")) {
        isTeacher = true;
    } else {
        isTeacher = false;
    }

    // Get the current Course ID and path based on the url of the course
    isCourse = window.location.href.indexOf("/courses/") > -1;
    getPath = window.location.pathname;
   
    // Determine if the template button is currently visible
    templateButton = $('.btn.button-sidebar-wide.element_toggler.choose_home_page_link').is(':visible');
   
    // Determine if the user is currently in student view
    studentViewVisible = $('.ic-alert-masquerade-student-view').is(':visible');

    // Validate rendering the universal button based on the variables

    if(isTeacher && isCourse && studentViewVisible == false) { // If the user is truly a teacher for this course/student view is not already enabled, render the button
        courseId = getPath.split('courses/').pop().split('/')[0]
        studentViewURL = "/courses/" + courseId + "/student_view";
        $('.ic-app-nav-toggle-and-crumbs.no-print').append($('<a />').addClass('btn button-sidebar-wide quick-access').css({cursor: 'pointer'}).html('<i class="icon-student-view" role="presentation"></i> Launch Student View').click(function(e) {
            e.preventDefault();
            $.ajax({
                url: studentViewURL,
                type: 'POST',
                success: function() {
                    window.location = window.location.href;
                }
            });
        }));
    } else { // If the user is not a teacher or student view is already enabled
        $('.ic-app-nav-toggle-and-crumbs.no-print').remove('.btn.button-sidebar-wide.quick-access')
    }
}

/* ------------------ END FUNCTION SECTION */

/* ------------------ RUN ON PAGE LOAD */

$(document).ready(function () {
     universalStudentView()
})‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

For anyone interested, I've further modified/condensed the code into a userscript.

James
Community Champion

cesbrandt, Nice Work

That cracks the last piece of the puzzle -- getting it to stay on the same page. And I love userscripts Smiley Happy

thomast
Community Participant

Hi Christopher and  @markgarcia510 ‌

With one of the recent releases, I believe October 19, our instructors are now not seeing the Student View button.  Canvas now includes a button on the Home page list.  However, we now don't see the button at the top-right when accessing other areas (i.e., Modules, etc.).  Any suggestions?

Thank you,

Treva

cesbrandt
Community Champion

This is because Canvas is phasing out the use of jQuery, so some pages don't get it loaded at all.

I've been on vacation for the last week, but I do have a "quick" fix to allow it to work regardless of if jQuery was loaded to the page or not (without changing the code to standard JavaScript). I'll try to get this script updated tomorrow.

msgarcia
Community Contributor
Author

cesbrandt‌ and  @thomast ‌ I have modified the original script as it was targeting an element that no longer exists (Instructure removed one of the identifying class names). 

I have the raw jQuery and now a pure ES6 solution for the persistent student view button, which I have created by simply branching off of my original repo. You can find links to each below. At this point I don't know that you would want to implement the jQuery build, but I did it anyway so that users have a choice.

jQuery Branch

ES6 Branch

cesbrandt
Community Champion

It's odd, I have the button loading, but the XHR on my modification (to reload to the current page) keeps producing a 422 response. :S

Might take a bit longer than I had hoped to get my modification updated. Smiley Sad

msgarcia
Community Contributor
Author

cesbrandt‌, this is most likely to Instructure's adoption of ReactJS. I had an issue with it in another implementation that I attempted to build for our institution. Because ReactJS relies on a pure, non-mutable state, any Javascript that is implemented in a way that could modify that state outside of React is ignored as it is treated like an "injection attack."

I attempted to modify your XHR in ES6 and received several 422 responses. Additionally, I added in the appropriate data tags to the anchor tag, along with the API one that Instructure uses for any injected anchors in the RCE, but still received either 422 or was simply redirected back to the primary page of the course. Let me know if you find a way around it.

cesbrandt
Community Champion

It was the CSRF token. I decided to go ahead and cut out the jQuery and didn't think about it. Someone with a development server tried it out and found the issue in the server logs.

I've updated my userscript with the new jQuery-free code to keep you on the same page as where you clicked that button: https://raw.githubusercontent.com/cesbrandt/canvas-javascript-studentViewAnywhere/master/studentView....

msgarcia
Community Contributor
Author

cesbrandt I took your code and converted it from XHR to fetch, so between the two repos, yours and mine, we should have everyone across the spectrum of implementation covered for the foreseeable future!

jsimon3
Community Participant

this is cool I didn't know you guys were still keeping it going, I always converted any cool projects I find in the community to plain js simply because I didn’t like jQuery... This was my take on your project I did this a while ago... I will make sure to post after I do these things in the future

function studentView() {
const checkIfTeacher = async selector => {
while (document.querySelector(selector) === null) {
await new Promise(resolve => requestAnimationFrame(resolve));
}
return document.querySelector(selector);
};
checkIfTeacher('.ic-app-nav-toggle-and-crumbs.no-print')
.then((element) => {
console.info(`%c add student view`, 'color:#f7f300');
let isTeacher, isCourse, courseId, templateButton, studentViewVisible, studentViewURL, getPath;
let visibleMenu = document.querySelector('.ic-app-course-menu.list-view nav #section-tabs li.section a[title="Settings"]');
// Determine if the user is actually a teacher
if (ENV['current_user_roles'].includes('teacher') && visibleMenu !== null) {
isTeacher = true;
} else {
isTeacher = false;
}
console.log(isTeacher);
// Get the current Course ID and path based on the url of the course
isCourse = window.location.href.indexOf("/courses/") > -1;
getPath = window.location.pathname;
// Determine if the template button is currently visible
templateButton = document.querySelector('.btn.button-sidebar-wide.element_toggler.choose_home_page_link');
if(templateButton !== null){console.log(templateButton)}
// Determine if the user is currently in student view
studentViewVisible = document.querySelector('.ic-alert-masquerade-student-view');
// Validate rendering the universal button based on the variables
console.log(isTeacher,isCourse,studentViewVisible);
if(isTeacher && isCourse && studentViewVisible === null) { // If the user is truly a teacher for this course/student view is not already enabled, render the button
courseId = getPath.split('courses/').pop().split('/')[0];
studentViewURL = `/courses/${courseId}/student_view`;
document.querySelector('.ic-app-nav-toggle-and-crumbs.no-print').innerHTML +=`<a class="btn button-sidebar-wide quick-access" href="${studentViewURL}" rel="nofollow" data-method="post"><i class="icon-student-view" role="presentation"></i> Launch Student View</a>`;
} else { // If the user is not a teacher or student view is already enabled
if(document.querySelector('.ic-app-nav-toggle-and-crumbs.no-print') !== null && document.querySelector('.btn.button-sidebar-wide.quick-access') !== null){document.querySelector('.ic-app-nav-toggle-and-crumbs.no-print').removeChild('.btn.button-sidebar-wide.quick-access');}
}
});
}
jsimon3
Community Participant

@here if anyone would like to help me tackle converting https://github.com/sdjrice/msgobs to plain js I would be estatic. 

cesbrandt
Community Champion

I hadn't thought to switch to fetch. Modernization of all my JavaScript is on the docket, but it's so hard to find the time to revisit completed scripts. ^^'