/* This function fires off all subsequent functions for this code. It relies on the ENV variable being available in Canvas. If the user has a role matching "observer", and the user is currently on the "All Courses" page then the underlying functions will fire off. If the user is enrolled as a teacher or an admin in Canvas, the code will not run. Once the function finishes all the observees and courses, then it will remove the original tables so that there is no duplicated data. The following line of code can be called within window.onload() in the Canvas custom JavaScript theme file to automatically run this function once the rest of the page has loaded: $(document).ready(function(){validateObserverRole()}) */ async function validateObserverRole(){ if((ENV.current_user_roles.indexOf("observer")>=0) && (ENV.current_user_roles.indexOf("admin")<1) && (ENV.current_user_roles.indexOf("teacher")<1) && (window.location.pathname == "/courses")){ console.log("Reformatting table"); ENV.observees = await getObservees(ENV.current_user['id']); current_enrollment_table = document.getElementById("my_courses_table"); past_enrollment_table = document.getElementById("past_enrollments_table"); // Store current uses's courses that they are enrolled as a student in to be listed separately from observer enrollments. all_user_courses = await getCourses(ENV.current_user['id']); user_student_courses = []; for (course in all_user_courses){ enrollments = all_user_courses[course]['enrollments']; for (role in enrollments) { if (enrollments[role]['type'] == 'student') { user_student_courses.push(all_user_courses[course]); } } } ENV.favorites = await getFavorites(); if(Object.keys(ENV.favorites[0])[2] == 'enrollment_term_id') { ENV.favorites = {}; } ENV.nickames = await getNicknames(); await baseTableInner(current_enrollment_table, past_enrollment_table, ENV.current_user['id'], "Your Courses", user_student_courses) for (key in ENV.observees){ ENV.observees[key]['courses'] = await getCourses(ENV.observees[key]['id']); await baseTableInner(current_enrollment_table, past_enrollment_table, ENV.observees[key]["id"], ENV.observees[key]["name"], ENV.observees[key]["courses"]); } if(past_enrollment_table){ past_enrollment_table.remove(); } if(current_enrollment_table){ current_enrollment_table.remove(); } } return } /* Passes the base HTML template for a table back to the calling function. This table will have a header value equal to the student's name and three columns -- one for course name, one to show the observer enrolled, and one to show the course is published */ async function get_html_template(){ template = `

###########

Favorites Course Nickname Enrolled as Published
`; return template; } /* Creates the table to be inserted into the DOM and replaces the placeholder strings with the actual student values (student name and id). */ async function make_tables(element_to_insert_before, observee_name, table_id){ var html_template = await get_html_template(); var base_table = document.createElement('div'); html_template = html_template.replace("###########",observee_name); html_template = html_template.replace("table_$$$$$$$$",table_id); base_table.innerHTML = html_template; element_to_insert_before.parentNode.insertBefore(base_table, element_to_insert_before); } /* Populates the table with rows and cells with the appropriate values based off the current course. Checks ENV.favorites to see if the course being added is currently favorited, and adds the appropriate HTML for a favorited or un-favorited course. Adds an event listener on the star icon to check if the star is clicked to favorite course. If so, calls clickStar(). */ async function rows_and_data(table_name, course_name, nickname, course_id, active){ newRow = table_name.getElementsByTagName('tbody')[0].insertRow(-1); newRow.classList.add("course-list-table-row"); course_favorited = newRow.insertCell(0); course_name_value = newRow.insertCell(1); course_nickname = newRow.insertCell(2); enrolled_as = newRow.insertCell(3); published = newRow.insertCell(4); if(active) { course_favorited.classList.add("course-list-star-column"); let favorited = false; for(key in ENV.favorites) { if(ENV.favorites[key]['id'] == course_id){ favorited = true; } } if(favorited) { favorite_html = ` Click to remove from the courses menu. `; } else { favorite_html = ` Click to add to the courses menu. `; } } else { favorite_html = ` This course cannot be added to the courses menu unless the course is active. `; } favorite_html = favorite_html.replaceAll("INSERT_NUMBER", course_id); course_favorited.innerHTML = favorite_html; course_html = ` INSERT_COURSE_NAME `; course_html = course_html.replace("INSERT_NUMBER", course_id); course_html = course_html.replaceAll("INSERT_COURSE_NAME", course_name); course_name_value.innerHTML = course_html; course_nickname.innerText = nickname; enrolled_as.innerText = "Observer"; published.innerText = "Yes"; if(active) { course_favorited.addEventListener("click", clickStar); } } /* Favorites or un-favorites a given course. Changes the HTML for the star icon to make it favorited or unfavorited, and calls removeFavorite() or addFavorite() on the current course. After favoriting or unfavoriting, calls getFavorites() to update the favorites stored in ENV. */ async function clickStar() { var target = this.getElementsByClassName("course-list-favoritable")[0]; var course_num = target.getAttribute("data-course-id"); var favorite = target.classList.contains("course-list-favorite-course"); if(favorite) { star_html = ` Click to add to the courses menu. `; await removeFavorite(course_num); } else { star_html = ` Click to remove from the courses menu. `; await addFavorite(course_num); } star_html = star_html.replaceAll("INSERT_NUMBER", course_num); this.innerHTML = star_html; ENV.favorites = await getFavorites(); if(Object.keys(ENV.favorites[0])[2] == 'enrollment_term_id') { ENV.favorites = {}; } } /* Creates either one or two tables depending on enrollment. If the student is enrolled in an active course, there will be two tables (current_enrollment and past_enrollment). If not, then there will only be past_enrollment. For each course in courses_array, the function determines the following: - Is the course currently active or in the Default Term enrollment term? - If true, then does the table already exist? - If it does, populate the table with the needed new row. If not, then create the table - If the course is not currently active or in the Default Term enrollment term, then make the past_enrollment table - If we're making the past_enrollment table, does it already exist? - If true, then populate the needed rows. If not, then create the table */ async function baseTableInner(current_enrollment, past_enrollment, observee_id, observee_name, courses_array){ // Gets a list of the current user's favorite courses. If no courses are favorited, Canvas will return all the active courses. // If no courses are favorited, the keys in the returned courses will be listed in a different order. // This code checks the order of the keys and clears out the returned favorites if the keys are in the order given when no courses are favorited. for(key in courses_array) { courses_array[key]["nickname"] = ""; } for (key in ENV.nickames) { for(course in courses_array) { if (ENV.nickames[key]["course_id"] == courses_array[course]["id"]) { courses_array[course]["nickname"] = ENV.nickames[key]["nickname"]; } } } for(key in courses_array) { // Enter the currently active canvas term IDs here. This should be updated each new term. // Term ID's can be found through the Enrollment Terms API: "GET /api/v1/accounts/:account_id/terms" active_terms = [ENTER ACTIVE TERMS HERE]; if (active_terms.includes(courses_array[key]["enrollment_term_id"])) { var table_id = "curr_table_"+observee_id; if(!document.getElementById(table_id)){ await make_tables(current_enrollment, observee_name, table_id); } var table = document.getElementById(table_id); if (courses_array[key]["nickname"] == "") { original_name = courses_array[key]["name"]; } else { original_name = courses_array[key]["original_name"]; } await rows_and_data(table, original_name, courses_array[key]["nickname"], courses_array[key]["id"], true); } else { var table_id = "past_table_"+observee_id; if(!document.getElementById(table_id)){ await make_tables(past_enrollment, observee_name, table_id); } var table = document.getElementById(table_id); await rows_and_data(table, courses_array[key]["name"], courses_array[key]["nickname"], courses_array[key]["id"], false); } } return; } /* Accepts a user id as a parameter and returns an array of courses for a student. Function will go and pull all the courses the student is enrolled in. If the student is enrolled in more than 100 courses, subsequent API requests will be made to grab additional pages of enrollments. If the data is successfully fetched, the array of courses will be returned. */ async function getCourses(user_id){ var url = window.location.origin + "/api/v1/users/"+user_id+"/courses?per_page=100&page="; var page = 1; var current_url = url + page; var repeat = true; var courses = []; while(repeat) { try { let fetched = await fetch(current_url); if(fetched) { let links = fetched.headers.get('Link'); let data = await fetched.json(); for(course in data) { courses.push(data[course]); } if(!links.includes('rel="next"')) { repeat = false; } else { page++; current_url = url + page; } } } catch (error) { throw new Error(error.message); } } return courses; } /* Accepts a user id as a parameter and returns an array of students assigned to an observer. Function will go and pull all the observees for the current user logged in. If the data is successfully fetched, the array of users will be returned. */ async function getObservees(user_id){ let url = window.location.origin + "/api/v1/users/"+user_id+"/observees?per_page-100"; try { let fetched = await fetch(url); if(fetched) { let data = await fetched.json(); return await Promise.all(data); } } catch (error) { throw new Error(error.message); } } /* Defines the constant CSRFtoken which reads the current CSRF token from Canvas's cookies. */ const CSRFtoken = function() { return decodeURIComponent((document.cookie.match('(^|;) *_csrf_token=([^;]*)') || '')[2]); } /* Returns an array of a user's currently favorited courses. Assumes that a user has not favorited more than 100 courses. */ async function getFavorites(){ let url = window.location.origin + "/api/v1/users/self/favorites/courses?per_page=100"; try { let fetched = await fetch(url); if(fetched) { let data = await fetched.json(); return await Promise.all(data); } } catch (error) { throw new Error(error.message); } } /* Makes a POST request to add a new favorite course with the passed course_id. */ async function addFavorite(course_id){ fetch(window.location.origin + '/api/v1/users/self/favorites/courses/'+course_id, { method: 'POST', headers: { 'content-type': 'application/json', 'accept': 'application/json', 'X-CSRF-Token': CSRFtoken() }, }) .then(response => response.json()) .then(data => { console.log('Success:', data); }) .catch((error) => { console.error('Error:', error); }); } /* Makes a DELETE request to remove a favorite course with the passed course_id. */ async function removeFavorite(course_id){ fetch(window.location.origin + '/api/v1/users/self/favorites/courses/'+course_id, { method: 'DELETE', headers: { 'content-type': 'application/json', 'accept': 'application/json', 'X-CSRF-Token': CSRFtoken() }, }) .then(response => response.json()) .then(data => { console.log('Success:', data); }) .catch((error) => { console.error('Error:', error); }); } /* Returns an array of the current user's courses including their nicknames. Assumes that a user has not nicknamed more than 100 courses. */ async function getNicknames(){ let url = window.location.origin + "/api/v1/users/self/course_nicknames?per_page=100"; try { let fetched = await fetch(url); if(fetched) { let data = await fetched.json(); return await Promise.all(data); } } catch (error) { throw new Error(error.message); } }