Collapse & Expand Modules

mcowen
Instructure Alumni
Instructure Alumni
14
11308

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 Smiley Happy .)

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

**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.

14 Comments
irishb
Community Contributor

Hi Deactivated user‌!

Thanks so much for developing and sharing this lil' gem of functionality - we tested it last week and just added it to our college's production instance today, will let you know what our faculty and students think! : )

Best wishes,

Bridget Irish

Curricular Technology Support | Canvas Admin
The Evergreen State College, Olympia WA

Boekenoogen
Community Contributor

This is a nice bonus feature that Instructure should add to their code. Thanks for sharing this with the community.

mcowen
Instructure Alumni
Instructure Alumni
Author

This is a common question I receive, so I thought I'd post it here...

Question:

Does this mean that the Modules page would load up quicker where there is a very long Modules list? I have a problem with very slow load times on a long Modules list currently and am looking for a solution?

Answer:

Yes. My intention when I wrote it was purely cosmetic, but an added benefit is faster load times Smiley Happy Yay!

As always, make sure to try any code in your TEST or BETA instance before using it in production.

dtod
Community Contributor

Latest release broke this for us. I think the issue is that the module content is loading asynchronously. Updated version below, which also remembers which modules are expanded and collapsed during a session - called by calling expand_module():

function showModule(obj){
     var relatedObj = obj.getAttribute('aria-controls');
     //console.log(relatedObj);
     //console.log(document.getElementById(relatedObj).style.display);
     if (document.getElementById(relatedObj).style.display == "" || document.getElementById(relatedObj).style.display == "none"){
          obj.click();
          // try again
          setTimeout(function(){ showModule(obj); },500);
     }
}

function expand_module() {
     var anchor = window.location.hash;
     var underscore = anchor.indexOf("_") + 1;
     var characters = anchor.length;
     var module = anchor.substring(underscore,characters);
     var thishref = window.location.pathname;
     if (module =="" && sessionStorage[thishref]){
          module = sessionStorage[thishref];
     }
     
     var modules=module.split(',');
     //console.log(module);
     
     if (module!=""){
          for (var x=0; x<modules.length; x++){
               var selector = "span[aria-controls='context_module_content_"+modules[x]+"'][aria-expanded='false']";
               //console.log(modules[x]);
               if (document.querySelector(selector) && modules[x]!=""){
                    //console.log('clicking ' + modules[x]);
                    //console.log(selector);
                    //console.log(document.querySelector(selector));
                    //document.querySelector(selector).click();
                    showModule(document.querySelector(selector));
               }
          }
     }
     else if (document.querySelectorAll("span[aria-controls^='context_module_content_']")[1]) {
          //console.log('default');
          //document.querySelectorAll("span[aria-controls^='context_module_content_']")[1].click();
          showModule(document.querySelectorAll("span[aria-controls^='context_module_content_']")[1]);
          sessionStorage[window.location.pathname]="," + document.querySelectorAll("span[aria-controls^='context_module_content_']")[1].parentNode.id;
     }
     
    //console.log("click attempted; now add button");
     
     //
     // 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') && !document.querySelector('#expand-collapse-modules')) {
           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>');
     }

     if (thishref.indexOf("modules") > -1){
          // capture expand and collapse ajax calls
          $(document).ajaxSuccess(function(event, request, settings) {
                 //console.log(settings);
                
                 if (/^\/courses\/\d+\/modules\/\d+\/collapse$/.test(settings.url) && settings.type=="POST"){
                    var urlComponents=settings.url.split('/');
                    module=urlComponents[urlComponents.length-2];
                    //console.log(module);
                    var expandedModules="";
                    var update=false;
                    if (sessionStorage[window.location.pathname]){
                         expandedModules=sessionStorage[window.location.pathname];
                    }
                    if (settings.data.indexOf('collapse=1')>-1){
                         //collapsed
                         update=true;
                         expandedModules = expandedModules.replace("," + module,"");
                    }
                    else if (module!="undefined" && expandedModules.indexOf("," + module)==-1){
                         //expanded
                         update=true;
                         expandedModules = expandedModules + "," + module;
                    }
                    if (update) sessionStorage[window.location.pathname]=expandedModules;
                 }
          });
     }
}

// 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();
    }
}

					
				
			
			
			
				
			
			
			
			
			
			
		
sharon_kitching
Community Contributor

Just trying this out on BETA, and the button isn't generating. Any idea why? It's a fantastic idea Smiley Happy 

jwakeman
Community Novice

 @dtod  Based on your reply, your revised code will automatically expand the module the user last had expanded - is that correct?  The code originally linked in the blog currently works for us, but your code does not generate the button.  We would love to have it remember the last module you were working in and have that expanded.

dtod
Community Contributor

Joe, that's correct. The way my code works it only functions on the modules page, not on a home page that has modules on it iirc.

jwakeman
Community Novice

Yep, on the modules page we're not seeing the button generated via your code.  Also doesn't remember which modules you had left expanded previously when you browse away and back.

327032_Screen Shot 2019-10-18 at 10.28.36 AM.png

sgarcia1
Community Participant

A big THANK YOU to @mcowen @dtod and everyone who contributed to this. Just what my organization needed and works perfectly for me right now.

The only thing I can add is that the Collapse All / Expand all button is already implemented in the latest versions of Canvas, so there is no need for the second part of the Javascript code: "2. Create a button on the modules page to Expand/Collapse all modules".

No clue why this is not a native option in Canvas but again, thank you, guys.

asergay
Community Participant

Anyone still working on this? We don't need the button any more, and it doesn't remember the expanded module. I'm tinkering with it but I'm no coder.

+1 Should be a native feature

NoahBoswell
Community Coach
Community Coach

@asergay I might be wrong, but I don't believe so. Most of this conversation was from 4-5 years ago, and Canvas has released several updates, especially with modules, since then which means it likely no longer applies to the current canvas interface.

robotcars
Community Champion

@asergay,

As others have noted there is a native collapse/expand button now. As a Student or Admin I can collapse all modules, expand one, refresh and the expanded module remains open.

Not sure if still working on this is a status I'd choose. I think I've seen chatter that Canvas is working on a module redesign?

But in 2020, I was – actively trying to do a mashup of module modifications for my InstructureCon presentation. If memory serves, I was at a point where I was trying to get this to work as a student and maintain the states on refresh, which probably needs to incorporate some localStorage.

https://gist.github.com/robert-carroll/f70594aa4b6f6b8acfe7887eeb838b06

https://gist.github.com/robert-carroll/12717270d15c885f2908371e7bfab838

In the second gist, noticed the css in the gist duplicated at line 34. Fine in the first.

Unfortunately, I don't see myself having the bandwidth to progress this, but I'll share if it helps the conversation or someone else get a starting point.

My mashup was to combine Mike Cowan's buttons, @dtod's changes, and David Lyons' filters, in an effort to make large module pages more manageable and provide focus. I was also planning to implement @jbell0385's Module Completion Tracker

In addition to the comments on the gist, my other files had the following comments.

 

/*
issues: in student view, the collapse/expand ajax isn't sticky... either 'clicking' the wrong link, or can't duplicate
  odd behvior on student view of modules
  expand/collapse button:
    expand collapse works as expected
    -- but save state of student view doesn't seem to hold on refresh sometimes?
    -- on page load, clicking the collapse button collapses, and on refresh the state of each module is collapses
    
    -- but expanding, and refreshing does not result in open modules... am I clicking the right selectors for the POST?


state of expand/collapse is incorrect after a few clicks, incorpoate state management for expand button
*/

 

asergay
Community Participant

Yes, Modules does now have its own collapse all button. The old script works to open the Modules page with all modules collapsed, which we like. It doesn't remember the open module, which we don't like. Having all modules collapsed at opening but remembering the open one would be a huge boon to our teachers who feel overwhelmed by the amount of stuff on the page.

I just thought maybe there was a simple revision that could make the memory part work, as it seems to have at one point. 

I'd also like to be able to filter module items. I actually need a third layer: course/supermodule/module. We would like to give teachers curriculum material in a blueprint but make it easy to hide if they don't want to use it. We considered having the material in the Commons but teachers are not adept at importing.

robotcars
Community Champion

@asergay,

To use the native collapse/expand feature, but collapse them all if a specific one or some are not open might look something like the example below. However, if the teacher has chosen to expand them all on a specific course, you might want to store that as not to frustrate their workflow.

 

if(document.querySelectorAll('.collapsed_module').length == 0 && ['teacher', 'admin', 'root_admin'].some(a => ENV.current_user_roles.includes(a))) {
	document.getElementById('expand_collapse_all').click();
}

 

 There is also ENV.COLLAPSED_MODULES array, which might be useful for someone.