Created
August 3, 2018 09:41
-
-
Save theotherdy/60ced3e955813861142f60bd3ea70ff4 to your computer and use it in GitHub Desktop.
A dashboard view of Modules in Canvas v2: https://learntech.imsu.ox.ac.uk/blog/a-dashboard-view-of-modules-in-canvas-v2/
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Add tiles at top of modules tool: | |
* - Tiles are generated by calling the Canvas api, not by scraping the Modules page as before (should be more reliable as Canvas in upgraded) | |
* - Added a drop-down arrow which gives you a quick link to the Module item (page, discussion, etc) | |
* - Tiles will show any images put into a specific folder in the Course’s Files (this defaults to looking for a ’tiles’ folder). If no folder or too few images for the number of Modules, colours are used instead | |
* - Modules further down the page gain a coloured border to help tie things together | |
* - Clicking the tile anywhere except the drop-down arrow scrolls you down the Modules page to the appropriate Module. | |
* - I’ve added a Top button to each module which scrolls you back up to the dashboard view | |
*/ | |
// TODO - show completion either on links or as e.g 10/12 | |
/* Global variables */ | |
var initCourseId = ENV.COURSE_ID; | |
var noOfColumnsPerRow = 4; //no of columns per row of tiles at top of Modules page - 1, 2, 3, 4, 6 or 12 | |
var tileImagesFolderName = "tiles"; | |
var moduleNav; | |
var divCourseHomeContent = document.getElementById('course_home_content'); | |
var divContextModulesContainer = document.getElementById('context_modules_sortable_container'); | |
var divContent = document.getElementById('content'); | |
/* colours are randomly selected from: https://www.ox.ac.uk/public-affairs/style-guide/digital-style-guide */ | |
var moduleColours = ['#e8ab1e','#91b2c6','#517f96','#1c4f68','#400b42','#293f11','#640D14','#b29295','#002147','#002147','#cf7a30','#a79d96','#f5cf47','#fb8113','#f3f1ee','#aab300','#043946','#be0f34','#a1c4d0','#a1c4d0','#122f53','#0f7361','#3277ae','#872434','#44687d','#517fa4','#177770','#be0f34','#d34836','#70a9d6','#69913b','#d62a2a','#5f9baf','#09332b','#44687d','#721627','#9eceeb','#330d14','#006599','#cf7a30','#a79d96','#be0f34','#001c3d','#ac48bf','#9c4700','#c7302b','#ebc4cb','#1daced']; | |
var tileImageUrls = []; | |
//From: https://stackoverflow.com/questions/4793604/how-to-insert-an-element-after-another-element-in-javascript-without-using-a-lib | |
function msd_insertAfter(newNode, referenceNode) { | |
referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling); | |
} | |
/* Wait until DOM ready before loading tiles */ | |
function msd_domReady () { | |
if(divCourseHomeContent && divContextModulesContainer){ | |
//we're in the modules page as a home page | |
//first delete any existing nav container | |
var existingModuleNav = document.getElementById('module_nav'); | |
if(existingModuleNav) { | |
existingModuleNav.parentNode.removeChild(existingModuleNav); | |
} | |
//create our nav container | |
moduleNav = document.createElement("div"); | |
moduleNav.id = "module_nav"; | |
moduleNav.className = "ou-ModuleCard__box"; | |
moduleNav.innerHTML = '<a id="module_nav_anchor"></a>'; | |
divContent.insertBefore(moduleNav, divContent.childNodes[0]); | |
//now get modules from api | |
msd_getSelfThenModules(); | |
} | |
} | |
//Function to work out when the DOM is ready: https://stackoverflow.com/questions/1795089/how-can-i-detect-dom-ready-and-add-a-class-without-jquery/1795167#1795167 | |
// Mozilla, Opera, Webkit | |
if ( document.addEventListener ) { | |
document.addEventListener( "DOMContentLoaded", function(){ | |
document.removeEventListener( "DOMContentLoaded", arguments.callee, false); | |
msd_domReady(); | |
}, false ); | |
// If IE event model is used | |
} else if ( document.attachEvent ) { | |
// ensure firing before onload | |
document.attachEvent("onreadystatechange", function(){ | |
if ( document.readyState === "complete" ) { | |
document.detachEvent( "onreadystatechange", arguments.callee ); | |
msd_domReady(); | |
} | |
}); | |
} | |
/* | |
* Get self id - actually only needed to show completion which isn't yet done | |
*/ | |
function msd_getSelfThenModules() { | |
var csrfToken = msd_getCsrfToken(); | |
fetch('/api/v1/users/self',{ | |
method: 'GET', | |
credentials: 'include', | |
headers: { | |
"Accept": "application/json", | |
"X-CSRF-Token": csrfToken | |
} | |
}) | |
.then(msd_status) | |
.then(msd_json) | |
.then(function(data) { | |
//msd_getModules(initCourseId, data.id); | |
msd_getTileFolder(initCourseId, data.id); | |
}) | |
.catch(function(error) { | |
console.log('getSelfId Request failed', error); | |
} | |
); | |
} | |
/* | |
* Get tileImages for courseId | |
*/ | |
function msd_getTileFolder(courseId, userId) { | |
var csrfToken = msd_getCsrfToken(); | |
fetch('/api/v1/courses/' + courseId + '/folders',{ | |
method: 'GET', | |
credentials: 'include', | |
headers: { | |
"Accept": "application/json", | |
"X-CSRF-Token": csrfToken | |
} | |
}) | |
.then(msd_status) | |
.then(msd_json) | |
.then(function(data) { | |
console.log(data); | |
var imagesFolderId; | |
data.forEach(function(folder){ | |
if(folder.name==tileImagesFolderName){ | |
imagesFolderId = folder.id; | |
} | |
}); | |
msd_getTileImageUrls(courseId, userId, imagesFolderId); | |
}); | |
} | |
function msd_getTileImageUrls(courseId, userId, imagesFolderId) { | |
/* temporarily getting file ids here - longer-term, replace with callbacks */ | |
var csrfToken = msd_getCsrfToken(); | |
if(imagesFolderId) { | |
fetch('/api/v1/folders/' + imagesFolderId + '/files',{ | |
method: 'GET', | |
credentials: 'include', | |
headers: { | |
"Accept": "application/json", | |
"X-CSRF-Token": csrfToken | |
} | |
}) | |
.then(msd_status) | |
.then(msd_json) | |
.then(function(data) { | |
console.log(data); | |
data.forEach(function(image){ | |
tileImageUrls.push(image.url); | |
}); | |
msd_getModules(courseId, userId, tileImageUrls); | |
}); | |
} else { | |
msd_getModules(courseId, userId); | |
} | |
} | |
/* | |
* Get modules for courseId | |
*/ | |
function msd_getModules(courseId, userId, tileImageUrls) { | |
var csrfToken = msd_getCsrfToken(); | |
fetch('/api/v1/courses/' + courseId + '/modules?include=items&student_id=' + userId,{ | |
method: 'GET', | |
credentials: 'include', | |
headers: { | |
"Accept": "application/json", | |
"X-CSRF-Token": csrfToken | |
} | |
}) | |
.then(msd_status) | |
.then(msd_json) | |
.then(function(data) { | |
console.log(data); | |
var newRow; //store parent row to append to between iterations | |
//run through each module | |
data.forEach(function(module, mindex){ | |
//work out some properties | |
var moduleName = module.name; | |
//create row for card | |
if(mindex % noOfColumnsPerRow === 0) { | |
newRow = document.createElement("div"); | |
newRow.className = "grid-row center-sm"; | |
moduleNav.appendChild(newRow); | |
} | |
var newColumn = document.createElement("div"); | |
// TODO work out classes for noOfColumnsPerRow != 4 | |
//create column wrapper | |
newColumn.className = "col-xs-12 col-sm-6 col-lg-3"; | |
newRow.appendChild(newColumn); | |
//create module div | |
var moduleTile = document.createElement("div"); | |
moduleTile.className = "ou-ModuleCard"; | |
moduleTile.title = moduleName; | |
var moduleTileLink = document.createElement("a"); | |
moduleTileLink.href ="#module_" + module.id; | |
var moduleTileHeader = document.createElement("div"); | |
moduleTileHeader.className="ou-ModuleCard__header_hero_short"; | |
if(tileImageUrls && tileImageUrls.length > mindex) { | |
moduleTileHeader.style.backgroundImage = "url(" + tileImageUrls[mindex] + ")"; | |
} else { | |
moduleTileHeader.style.backgroundColor = moduleColours[mindex]; | |
} | |
var moduleTileContent = document.createElement("div"); | |
moduleTileContent.className = "ou-ModuleCard__header_content"; | |
var moduleTileActions = document.createElement("div"); | |
moduleTileActions.className = "ou-drop-down-arrow"; | |
moduleTileActions.title = "Click for contents"; | |
var moduleTileArrowButton = document.createElement("a"); | |
moduleTileArrowButton.classList.add("al-trigger"); | |
//moduleTileArrowButton.classList.add("btn"); | |
//moduleTileArrowButton.classList.add("btn-small"); | |
moduleTileArrowButton.href ="#"; | |
var moduleTileArrowIcon = document.createElement("i"); | |
moduleTileArrowIcon.className = "icon-mini-arrow-down"; | |
moduleTileArrowButton.appendChild(moduleTileArrowIcon); | |
var moduleTileList = document.createElement("ul"); | |
moduleTileList.id = "toolbar-" + module.id + "-0"; | |
moduleTileList.className = "al-options"; | |
moduleTileList.setAttribute("role", "menu"); | |
moduleTileList.tabIndex = 0; | |
moduleTileList.setAttribute("aria-hidden",true); | |
moduleTileList.setAttribute("aria-expanded",false); | |
moduleTileList.setAttribute("aria-activedescendant","toolbar-" + module.id + "-1"); | |
/* Now create drop-down menu */ | |
module.items.forEach(function(item, iindex){ | |
var itemTitle = item.title; | |
//var moduleId = item.module_id; | |
var itemId = item.id; | |
var itemType = item.type; | |
var iconType; | |
switch(itemType) { | |
case "Page": | |
iconType = "icon-document"; | |
break; | |
case "File": | |
iconType = "icon-paperclip"; | |
break; | |
case "Discussion": | |
iconType = "icon-discussion"; | |
break; | |
case "Quiz": | |
iconType = "icon-quiz"; | |
break; | |
case "Assignment": | |
iconType = "icon-assignment"; | |
break; | |
case "ExternalUrl": | |
iconType = "icon-link"; | |
break; | |
default: | |
iconType = "icon-document"; | |
} | |
var listItem = document.createElement('li'); | |
listItem.className = 'ou-menu-item-wrapper'; | |
var listItemDest = '/courses/' + courseId + '/modules/items/' + itemId; | |
var listItemLink = document.createElement("a"); | |
listItemLink.className = iconType; | |
listItemLink.href = listItemDest; | |
listItemLink.text = itemTitle; | |
listItemLink.tabindex = -1; | |
listItemLink.setAttribute("role", "menuitem"); | |
listItemLink.title = itemTitle; | |
listItem.appendChild(listItemLink); | |
moduleTileList.appendChild(listItem); | |
}); | |
moduleTileActions.appendChild(moduleTileArrowButton); | |
moduleTileActions.appendChild(moduleTileList); | |
var moduleTileTitle = document.createElement("div"); | |
moduleTileTitle.classList.add("ou-ModuleCard__header-title"); | |
moduleTileTitle.classList.add("ellipsis"); | |
moduleTileTitle.title = moduleName; | |
moduleTileTitle.style.color = moduleColours[mindex]; | |
moduleTileTitle.innerHTML = moduleName; | |
moduleTileContent.appendChild(moduleTileActions); | |
moduleTileContent.appendChild(moduleTileTitle); | |
moduleTileLink.appendChild(moduleTileHeader); | |
moduleTileLink.appendChild(moduleTileContent); | |
moduleTile.appendChild(moduleTileLink); | |
newColumn.appendChild(moduleTile); | |
//now remove then add top buttons to each Canvas module to take back up to menu | |
var topButtons = document.querySelectorAll(".ou-top_button"); | |
topButtons.forEach(function(topButton) { | |
topButton.parentNode.removeChild(topButton); | |
}); | |
var canvasModuleHeaders = document.querySelectorAll(".ig-header"); | |
canvasModuleHeaders.forEach(function(canvasModuleHeader) { | |
newTopButton = document.createElement("a"); | |
newTopButton.className = "btn ou-top_button"; | |
newTopButton.href = "#module_nav_anchor"; | |
newTopButton.innerHTML = '<i class="icon-arrow-up"></i>Top'; | |
canvasModuleHeader.appendChild(newTopButton); | |
}); | |
//try and colour in each module | |
var canvasModuleDiv = document.getElementById('context_module_'+module.id); | |
canvasModuleDiv.style.borderLeftColor = moduleColours[mindex]; | |
canvasModuleDiv.style.borderLeftWidth = '10px'; | |
canvasModuleDiv.style.borderLeftStyle = 'solid'; | |
}); | |
}) | |
.catch(function(error) { | |
console.log('msd_getModules request failed', error); | |
} | |
); | |
} | |
/* Utility functions */ | |
/* | |
* Function which returns a promise (and error if rejected) if response status is OK | |
*/ | |
function msd_status(response) { | |
if (response.status >= 200 && response.status < 300) { | |
return Promise.resolve(response) | |
} else { | |
return Promise.reject(new Error(response.statusText)) | |
} | |
} | |
/* | |
* Function which returns json from response | |
*/ | |
function msd_json(response) { | |
return response.json() | |
} | |
/* | |
* Function which returns csrf_token from cookie see: https://community.canvaslms.com/thread/22500-mobile-javascript-development | |
*/ | |
function msd_getCsrfToken() { | |
var csrfRegex = new RegExp('^_csrf_token=(.*)$'); | |
var csrf; | |
var cookies = document.cookie.split(';'); | |
for (var i = 0; i < cookies.length; i++) { | |
var cookie = cookies[i].trim(); | |
var match = csrfRegex.exec(cookie); | |
if (match) { | |
csrf = decodeURIComponent(match[1]); | |
break; | |
} | |
} | |
return csrf; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment