cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 
michael5
Community Contributor

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
cesbrandt
Community Champion

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
kmeeusen
Community Coach
Community Coach

Greetings,  @michael5 , 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

tdelillo
Community Champion

 @michael5 - 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.

cesbrandt
Community Champion

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();
                    }
               });
          });
     });
}‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍
saltman
Community Participant

Hi, my answer is simpler and may or may not meet your needs. 

When on the list of modules, there is a settings gear above each module. You can set rules for accessing the module.  For example, you came make a prerequisite of completing the previous module. Or you can set it to be available on a certain date. 

If you need more details or screenshots just let just let us know and someone will help you out!

Cheers!

Shauna

michael5
Community Contributor

Hello Christopher,

Thanks a lot for taking your time to put this together - however, it's not working. 😞 I uploaded it as a custom JS file (not globally, but on the sub-account with this specific course in it). I tried both in student view and in teacher view and on modules that have only one page and modules with several pages (assignments, quizzes and so on) but the next/previous buttons persist no matter what. 

Any idea if I did something wrong? I copied the code into sublime and saved it as a .js file and uploaded in the theme editor.

michael5
Community Contributor

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

michael5
Community Contributor

Hello Shauna,

Thanks for the tip but we've already experimented with this. None of the prerequisites are sufficient, unfortunately. 😞

cesbrandt
Community Champion

That was my fault, sorry! I had used a static course ID when I wrote it and forgot to change it to pull the ID dynamically. I've fixed the code, but it needs moderation before it'll appear.

So, simple solution is to replace the course ID in the API URL with "' + viewID + '"/

Edit: It's been approved, so the code should now work. Sorry about that!

saltman
Community Participant

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.