Skip to content

Instantly share code, notes, and snippets.

@theotherdy
Created August 3, 2018 09:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save theotherdy/60ced3e955813861142f60bd3ea70ff4 to your computer and use it in GitHub Desktop.
Save theotherdy/60ced3e955813861142f60bd3ea70ff4 to your computer and use it in GitHub Desktop.
/*
* 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