cancel
Showing results for 
Search instead for 
Did you mean: 
Learner

Preventing previous/next jumps between modules

Jump to solution

Hello Canvas community,

This is a very circumstantial issue that I've been unable to solve. We're designing navigation in certain courses to work in a slightly different way than usual. 

I won't bore you with the lengthy details, but we need to prevent students from being able to jump between different modules using the Next/Previous buttons. It's fine that they jump between pages/assignments/quizzes within the same module. 

Is there any way to accomplish this? For now, the only solution I can see is to control access to modules by publishing/unpublishing but this is only a backup-plan for now.

Scripts, work-arounds or other clever solutions - anything goes.

Thanks in advance. 🙂 

Michael

EDIT - problem has been solved, see Christopher Esbrandt's solution for a functional script.


I was working on a lighter script - it is not fully functional yet, but if anyone wants to complete it, here it is. It works by scanning the content div for the tooltip "Næste modul" ("Next module") and then setting the display property of the next button to none. The setTimeout function was implemented because of the order of the content generation, ensuring that the next button is removed after it has been loaded (and not before, which would accomplish nothing). If the tooltip text "Næste modul" cannot be found, the script simply returns an error message.

function removeNext() {
   setTimeout(function(){ 
   var markup = document.getElementById("content").outerHTML;
   n = markup.includes("Næste modul:");
   if (n==true){ 
   $(".module-sequence-footer-button--next").css("display", "none"); 
   } 
   }, 1000);
   }
removeNext();

1 Solution

Accepted Solutions
Adventurer III

Assuming I understand what you're looking to do correctly, adding the code below to the global JavaScript should do it. However, I want to make it perfectly clear what this does and to also be perfectly clear that I have NOT conducted extensive testing. I literally just threw this together in my browser console and ran some quick tests, but I only tested against pages and assignments (quizzes, discussions, external links, external tools, and files may need tweaking). If you have any trouble with it, let me know and I'll do some more work on it, but if you don't tell me something in it isn't working, I won't know. Smiley Wink

So, here's the overall logic of the script:

Assumptions

  1. There will not be more than 100 modules per course
  2. There will not be more than 100 items per module

Logical Walkthrough

  1. Breaks down the current URL
  2. Uses the current URL breakdown to determine if the user is accessing a valid page to run the script
    1. Pulls a list of the modules in the course
    2. For each module
      1. Pulls a list of all items in the module
      2. Sorts the list of items by their position (I have no idea if this is already done, but I don't believe it is)
      3. Determines what GET variables are set in the current URL
      4. Identifies the first and last valid entry in the module (not Text Headers)
      5. If the module_item_id GET variable is set
        1. Test if this value matches the first and/or last module item
      6. If the module_item_id GET variable is NOT set
        1. For each module item
          1. If the item has a url set
            1. Split the url to get the ID of the linked item
            2. Test if the ID of the linked item matches the first and/or last module item
      7. If a match was for the first and/or last module item was found, hide the corresponding links

It's a little adaptive in that if there is only one item in the module, it should hide both links. However, it is also lacking in detection of multiple instances of the item. I've no idea if this even matters (we have a strict rule against linking items into multiple modules), but this comes into play when dealing with an activity that is loaded to multiple modules and does not have the module_item_id GET variable set. Again, I did not do extensive testing, but logically it should hide both links is it is the first item in one module and the last item in another. I would imagine, however, that anything not linked into the modules or linked into multiple locations would simply not have those links available.

Now, for the code (remember, I threw this together fairly quickly):

var url = window.location.href;
var leveledURL = url.split('/');
var view = url.match(/\.com\/?$/) ? 'dashboard' : leveledURL[3];
view = view.match(/^\?/) ? 'dashboard' : view;
var viewID = (view !== 'dashboard' && typeof leveledURL[4] !== 'undefined') ? leveledURL[4] : null;
var subview = (viewID !== null && typeof leveledURL[5] !== 'undefined') ? leveledURL[5].split('#')[0] : null;
var subviewID = (subview !== null && typeof leveledURL[6] !== 'undefined') ? leveledURL[6] : null;

if(view == 'courses' && (subview == 'assignments' || subview == 'pages' || subview == 'discussion_topics' || subview == 'quizzes' || subview == 'files' || (subview == 'modules' && subviewID == 'items'))) {
     $.ajax({
          url: '/api/v1/courses/' + viewID + '/modules?per_page=100',
          method: 'GET',
          dataType: 'json'
     }).done(function(data) {
          var output = '';
          $.each(data, function(key, value) {
               $.ajax({
                    url: '/api/v1/courses/' + viewID + '/modules/' + value.id + '/items?per_page=100',
                    method: 'GET',
                    dataType: 'json'
               }).done(function(data2) {
                    data2 = data2.sort(function(item1, item2) {
                         return item1.position > item2.position ? 1 : -1;
                    });
                    var vars = window.location.search.substring(1).split('&');
                    var formattedVars = {};
                    for(var i in vars) {
                         var splitVar = vars[i].split('=');
                         formattedVars[splitVar[0]] = splitVar[1];
                    }
                    var hide = [0, 0];

                    var firstItem = data2.filter(function(item) {
                         return /^SubHeader$/i.test(item.type) === false;
                    })[0].id;
                    var lastItem = data2.filter(function(item) {
                         return /^SubHeader$/i.test(item.type) === false;
                    });
                    lastItem = lastItem[lastItem.length - 1].id;

                    if(formattedVars.module_item_id !== undefined) {
                         hide[0] = formattedVars.module_item_id == firstItem ? 1 : hide[0];
                         hide[1] = formattedVars.module_item_id == lastItem ? 1 : hide[1];
                    } else {
                         $.each(data2, function(key2, value2) {
                              if(value2.url !== undefined) {
                                   var itemID = value2.url.split('/');
                                   itemID = itemID[itemID.length - 1];
                                   hide[0] = (subviewID == itemID && value2.id === firstItem) ? 1 : hide[0];
                                   hide[1] = (subviewID == itemID && value2.id === lastItem) ? 1 : hide[1];
                              }
                         });
                    }
                    if(hide[0]) {
                         $('.module-sequence-footer-button--previous').hide();
                    }
                    if(hide[1]) {
                         $('.module-sequence-footer-button--next').hide();
                    }
               });
          });
     });
}‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

View solution in original post

24 Replies
Community Coach
Community Coach

Greetings, michael@danes.dk, from https://community.canvaslms.com/community/instcon/2017?sr=search&searchId=3ef12379-0228-42d8-9363-bd...‌!

I wish that I knew the answer to your question, but I do know that outside of using Draft States (publish/unpublish), this is not currently possible in the UI. So I am going to share your question with the https://community.canvaslms.com/groups/designers?sr=search&searchId=7b244552-1f1a-4732-9bba-01c79cfd...‌ and https://community.canvaslms.com/groups/canvas-developers?sr=search&searchId=042ab352-18d9-442b-8f4a-...‌ groups where some truly awesome talent reside!

Agent K

Community Member

michael@danes.dk- There are many module settings that can introduce restrictions - I'm thinking lock dates and pre-requisites. Are you trying to keep students from moving on to the next module until a certain time? Are some modules restricted to a subset of students?  I think it would be helpful to know more about the use case.

Basically, I'm designing a course for very young students and we're avoiding the module view as we believe it will be confusing for them. We use a week-system, so we'd like to keep the quizzes and assignments for one week separate so they can't skip to content from other weeks by accident (some of the students can't read, so the system has to help them a lot). However, we need the material from previous weeks and one week ahead to remain open in case a student is behind/needs to work ahead, so the usual restrictions on modules isn't a very good solution as it needs to be flexible in some ways but also very, very restrictive in other ways. If the next/previous buttons we're unable to jump between modules we could control it easily by implementing a custom navigation system (that we've already developed).

I hope that made sense. 🙂

Since you have a custom navigation system, would it make sense if you could somehow toggle the next and previous buttons off?

On a side note, I would very much like to see your custom navigation system. I'm always interested in how Canvas can work for very young learners and English language learners.

Yes, it makes sense in our very specific way of organising our content - we use a week system and one period runs 10 weeks, so basically we have 10 buttons but only one can be clicked (the proper week). These 10 buttons lead to their corresponding week (a module) where the student can select 4 different types of content or sinmply click the next/previous buttons (which means they have to be unable to jump between modules). This is all based on images to circumvent the fact that they can't necessarily read yet. Mostly, this navigation design is about removing options and controlling that the students end up in the right place no matter what they do. Disturbing elements (such as course stream, information on assignments etc. is also removed as it does little to nothing except cause confusion for 6-8 year olds). For now, the front-page looks like this but we've still got a lot to tweak and produce (when we go live, most of the week buttons will be grayed out). 

244797_pastedImage_1.png

We actually have a similar setup, though we're not particularly concerned with restricting access to material early/late. Being a higher education institute, we encourage getting ahead and open access to previous material to support future material. However, we also concluded that simpler is better, so we are using out Syllabus pages as a homepage containing grid blocks that take the student directly to a particular unit. Other navigational options are available, but not all that Canvas offers.

That's an interesting angle. We only teach primary school students, but we definitely find that the basic Canvas navigation (startpage/modules) is working quite well for older students (10-16 years old). It's only for the very young kids that we need to tweak a lot. Do you not use the Module list at all? We've relied on that so far for most of our students. 

Oh, yeah, the grid blocks point to the Modules, but that was primarily because Canvas doesn't support dynamic loading of the Modules list to a Content Page. We originally had a homepage system that linked to everything in the unit, eliminating the need for the Modules list to be available to students, but it proved too much to maintain and we want to keep the custom dynamic code restricted from student access (if Instructure breaks our code, we don't want it to affect the student experience too much).

Adventurer III

Assuming I understand what you're looking to do correctly, adding the code below to the global JavaScript should do it. However, I want to make it perfectly clear what this does and to also be perfectly clear that I have NOT conducted extensive testing. I literally just threw this together in my browser console and ran some quick tests, but I only tested against pages and assignments (quizzes, discussions, external links, external tools, and files may need tweaking). If you have any trouble with it, let me know and I'll do some more work on it, but if you don't tell me something in it isn't working, I won't know. Smiley Wink

So, here's the overall logic of the script:

Assumptions

  1. There will not be more than 100 modules per course
  2. There will not be more than 100 items per module

Logical Walkthrough

  1. Breaks down the current URL
  2. Uses the current URL breakdown to determine if the user is accessing a valid page to run the script
    1. Pulls a list of the modules in the course
    2. For each module
      1. Pulls a list of all items in the module
      2. Sorts the list of items by their position (I have no idea if this is already done, but I don't believe it is)
      3. Determines what GET variables are set in the current URL
      4. Identifies the first and last valid entry in the module (not Text Headers)
      5. If the module_item_id GET variable is set
        1. Test if this value matches the first and/or last module item
      6. If the module_item_id GET variable is NOT set
        1. For each module item
          1. If the item has a url set
            1. Split the url to get the ID of the linked item
            2. Test if the ID of the linked item matches the first and/or last module item
      7. If a match was for the first and/or last module item was found, hide the corresponding links

It's a little adaptive in that if there is only one item in the module, it should hide both links. However, it is also lacking in detection of multiple instances of the item. I've no idea if this even matters (we have a strict rule against linking items into multiple modules), but this comes into play when dealing with an activity that is loaded to multiple modules and does not have the module_item_id GET variable set. Again, I did not do extensive testing, but logically it should hide both links is it is the first item in one module and the last item in another. I would imagine, however, that anything not linked into the modules or linked into multiple locations would simply not have those links available.

Now, for the code (remember, I threw this together fairly quickly):

var url = window.location.href;
var leveledURL = url.split('/');
var view = url.match(/\.com\/?$/) ? 'dashboard' : leveledURL[3];
view = view.match(/^\?/) ? 'dashboard' : view;
var viewID = (view !== 'dashboard' && typeof leveledURL[4] !== 'undefined') ? leveledURL[4] : null;
var subview = (viewID !== null && typeof leveledURL[5] !== 'undefined') ? leveledURL[5].split('#')[0] : null;
var subviewID = (subview !== null && typeof leveledURL[6] !== 'undefined') ? leveledURL[6] : null;

if(view == 'courses' && (subview == 'assignments' || subview == 'pages' || subview == 'discussion_topics' || subview == 'quizzes' || subview == 'files' || (subview == 'modules' && subviewID == 'items'))) {
     $.ajax({
          url: '/api/v1/courses/' + viewID + '/modules?per_page=100',
          method: 'GET',
          dataType: 'json'
     }).done(function(data) {
          var output = '';
          $.each(data, function(key, value) {
               $.ajax({
                    url: '/api/v1/courses/' + viewID + '/modules/' + value.id + '/items?per_page=100',
                    method: 'GET',
                    dataType: 'json'
               }).done(function(data2) {
                    data2 = data2.sort(function(item1, item2) {
                         return item1.position > item2.position ? 1 : -1;
                    });
                    var vars = window.location.search.substring(1).split('&');
                    var formattedVars = {};
                    for(var i in vars) {
                         var splitVar = vars[i].split('=');
                         formattedVars[splitVar[0]] = splitVar[1];
                    }
                    var hide = [0, 0];

                    var firstItem = data2.filter(function(item) {
                         return /^SubHeader$/i.test(item.type) === false;
                    })[0].id;
                    var lastItem = data2.filter(function(item) {
                         return /^SubHeader$/i.test(item.type) === false;
                    });
                    lastItem = lastItem[lastItem.length - 1].id;

                    if(formattedVars.module_item_id !== undefined) {
                         hide[0] = formattedVars.module_item_id == firstItem ? 1 : hide[0];
                         hide[1] = formattedVars.module_item_id == lastItem ? 1 : hide[1];
                    } else {
                         $.each(data2, function(key2, value2) {
                              if(value2.url !== undefined) {
                                   var itemID = value2.url.split('/');
                                   itemID = itemID[itemID.length - 1];
                                   hide[0] = (subviewID == itemID && value2.id === firstItem) ? 1 : hide[0];
                                   hide[1] = (subviewID == itemID && value2.id === lastItem) ? 1 : hide[1];
                              }
                         });
                    }
                    if(hide[0]) {
                         $('.module-sequence-footer-button--previous').hide();
                    }
                    if(hide[1]) {
                         $('.module-sequence-footer-button--next').hide();
                    }
               });
          });
     });
}‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

View solution in original post