The Instructure Community will enter a read-only state on November 22, 2025 as we prepare to migrate to our new Community platform in early December.
Read our blog post for more info about this change.
This will be my first foray into adding custom JS and CSS into Canvas. I do have admin access and also have access to web server space outside of Canvas. I've seen this done on the New England Tech Canvas site. @James & @lbouthillier , my contact at Instructure recommended I tag you in this question. Any help would be greatly appreciated.
Fun.
Will probably have to consider...
Probably don't have to host externally, upload to sub account or global theme...
if you must, does the server where you'll host the file have HTTPS and CORS enabled?
Dynamically generated week number still needs to count weeks from a start point.
GET /api/v1/courses/:course_id {start_at, end_at}
var start = new Date(r.start_at);
var today = new Date();
var weeks = Math.round((today-start)/ 604800000);
console.log(weeks)When you update the course home page, will the home page be designed with this in mind? ie. Will you be targeting an element of the home page waiting for this value, or are you going to update the content of the page with the full message?
<h2>Week <span id="course-week-count">#</span> of Let's do some JavaScript</h2>
Hi Robert,
Thanks for the quick reply. Yes, the external server is HTTPS and CORS enabled. If I understand your second question, I was envisioning that the week number would be fetched on the fly while the home page is being rendered in the browser. I made a crude test by putting a text file on the external server and embedding it with <iframe> but the aesthetics were terrible.
More than one way to skin a cat...
You can do that... simply set a counter on an external html file and bring it into each course, that works. The displayed text probably won't include any course details, just a common string.
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body></body>
<script>
var start = new Date('2019-04-01'); <!-- set this every semester -->
var today = new Date();
var weeks = Math.round((today-start)/ 604800000);
var x = document.createElement('p');
var t = document.createTextNode(`Welcome to week ${weeks}!`);
x.appendChild(t);
document.body.appendChild(x);
</script>
</html>If you want something a little more robust, built into Canvas...
Doing this using the global JavaScript themes, the code will be loaded at the bottom of the page, this means anything we modify on the page will be done after the page is fully rendered. For most users, this will happen in milliseconds and will go unnoticed.
We might also need to know if the page we are on is the course home page, this is done easiest by calling the local ENV.WIKI_PAGE.front_page true|false. Externally, we'd have to make a separate API call. But this depends on my previous question, will you be targeting an element on the page to update, or will you just add a new HTML block to the top of any page course home page.
Adding content to the content area can be problematic. The page title is not visible on the course home page unless you view it from pages. So having a target is probably best. If you have an existing element on the page that could take the Week # before or after works too.
If you're into trying the out the Developer Tools Console, you could try experimenting and decide what works best for your situation.
// for prototyping purposes only, please avoid using jquery in production
if (/^\/courses\/\d+.*/.test(window.location.pathname) && ENV.WIKI_PAGE.front_page) {
$.getJSON(`/api/v1/courses/${ENV.COURSE_ID}`, function(r) {
var start = new Date(r.start_at);
var today = new Date();
var weeks = Math.round((today-start)/ 604800000);
//$('.show-content').prepend(`<b>Week ${weeks}</b>`);
//$('.page-title').after(`<b>Week ${weeks}</b>`);
$('#course-week-count').html(`<b>${weeks}</b>`);
})
}Your iframe solution will be a full width block, you'd have to style the content of the page in the HTML and set borders to make it look seamless. Modifying the page with JS, you can update the value within the template and it can be designed into your page.
Happy to help with either option, which direction do you like best?
I went with the JS option. Worked like a charm! Thanks!
JS option in the iframe or Global Theme?
If you're using the global theme/api option, I'd like to kill jQuery and do some testing before you throw that in production.
I put it in the Global Theme for the sandbox sub-account I'm currently working in. It'll go into production for the fall semester.
Neat... give me a couple days to do it without jQuery.
That's generous of you. Thanks so much.
I tweaked the code just a tiny bit to allow for an uncounted spring break week...
$.getJSON(`/api/v1/courses/${ENV.COURSE_ID}`, function(r) {
var start = new Date('2019-01-24'); // enter date of Thursday before week 1
var today = new Date();
var weeks = Math.round(today-start);
weeks = Math.round(weeks/1000/60/60/24/7);
// subtract a week if we're in a break week.
if (weeks > 7) {
weeks--;
}
//$('.show-content').prepend(`<b>Week ${weeks}</b>`);
//$('.page-title').after(`<b>Week ${weeks}</b>`);
$('#course-week-count').html(`<b>${weeks}</b>`);
})
Are you setting course dates or is that just for your example/testing?
If you're using a fixed date instead of course/term start date, we don't need the API call.
I'm not sure which approach makes more sense. Certainly, I'd love it to be more automatic. But I worry about dealing with the uncounted spring break week. I don't know if it will always be seven weeks into the semester. (The Fall semester doesn't skip a week; just a short week for Thanksgiving.) One thing that does remain constant is that the week number rolls between Sunday and Monday. I determined that setting the start date for the Thursday prior to the actual start date results in the correct calculation.
I guess I'd like to use the API call, evaluate whether we're starting in January or February to decide whether to allow for Spring Break, and massage the start so that Week 1 begins on Monday of the start week. That way I'd only have to edit the JS if the Spring Break changes weeks.
The math is correct no matter what day feed it for start date. The conditions on the other hand...
I'm trying to understand how much logic/conditions are necessary. It kinda seems that we don't want to calculate based on the course's start date anymore, but based on an attendance week encompassing the start of a term. In which case, we'd probably be OK without doing an API call, setting a start date for the first day of class, determine which term (for spring break) and then update the field whenever front_page = true.
This means you have to set the term start (once or twice a year in the file) but it's less expensive (time) in API call/waiting.
If your courses can have multiple start dates then we'd have to go with the API. In which case, I need a bit more clarity on the following.
Your attendance week is Monday-Sunday or Sunday-Saturday, or depends on course?
determined that setting the start date for the Thursday prior to the actual start date results in the correct calculation.
Why Thursday?
I guess I'd like to use the API call, evaluate whether we're starting in January or February to decide whether to allow for Spring Break, and massage the start so that Week 1 begins on Monday of the start week.
January or February or fall term?
List each term and it's start month for me.
Spring January
Summer ?
Fall August
Hi Robert,
I really appreciate all of your effort on this.
In my department, which beginning this fall be working with its own sub-account in Canvas, all the courses have the same calendar. Fall term begins in September (but could potentially start in August); Spring begins in either January or February. We're not running any summer courses this year, but if we did they could start either in May or July. The only term with a gap (uncounted) week is Spring. It seems that uncounted week tends to fall on what would be Week 8, but we treat it as an extension of Week 7.
We consider 12:01am Monday morning to be the the beginning of a new week (the moment when the week number should increment).
The original code you gave me contains math that calculates weeks between dates. Since we don't have a "Week Zero" we need to juke the formula by pushing the start date back a bit so the first day of class lands in Week 1. I found that using the prior Thursday as the start date results in Monday morning falling into Week 1.
I'm not sure why we need to worry whether we're on the Front Page or not in the JS. I've put this in my HTML on the Front Page:
We are currently in Week <span id="course-week-count">#</span>
Pages without that tag would just ignore the JS, right?
Thanks again!
You're welcome. It's fun, especially when waiting for large queries to run or data sets to import.
Math.round was wrong, because .57 of a week isn't week 1, this should fix backing up to 'last Thursday'. Floor should fix that... right @James ?
Then adding +1 to each week.
Couple other problems we have to address.
I'm really trying to over simplify this before it becomes annoying to manage, both code wise and/or you updating a file every semester. Try some different dates/course start dates in here and see if it's what you'd expect.
// date testing
var start = new Date('January 7, 2019');
var today = new Date();
var start_month = start.getMonth();
console.log(start_month);
var week = Math.floor((today-start)/ 604800000) +1;
console.log(week);
// handle spring break
var week = start_month <= 1 && week >= 7 ? week -1 : week;
console.log(week);
// api
$.getJSON(`/api/v1/courses/${ENV.COURSE_ID}`, function(r) {
var start = new Date(r.start_at); // 'January 7, 2019'
var today = new Date();
var start_month = start.getMonth();
console.log(start_month);
var week = Math.floor((today-start)/ 604800000) +1;
console.log(week);
// handle spring break
var week = start_month <= 1 && week >= 7 ? week -1 : week;
console.log(week);
})This depends on the date being the first day of class. If you are like us, where the first day of class is a Monday, but the attendance week is Saturday to Friday (like your Monday-Sunday)... we might have to back it off and start counting from Monday... ie, is it possible your semester might start on a day that ins't Monday?
I will give this a try on Monday. My department (and this Canvas sub-account) at Landmark College deals exclusively with online courses. So there is no real attendance week to worry about. The week number is basically a guide to help students follow the syllabus. DST and leap seconds are not a concern.
Presently, our pages have names like "Week of April 29." This creates a tremendous amount of manual work every time we copy the course to the next semester. We felt like going with Week Numbers would lighten our workload and minimize the opportunity for errors. At the same time, I felt it would be helpful to say, "This is Week 13." at the top of the home page to give students a clue.
Have a great weekend.
Robert, your JS code was close, but it broke on March 10 when DST went into effect. After March 10th, the week number increased on Tuesday morning instead of Monday morning. I put Google to work and came up with this:
function parseISODate(ds) {
var d = ds.split(/\D/);
return new Date(d[0], --d[1], d[2]);
}
function dateDiff(d0, d1) {
return Math.round((d1 - d0)/8.64e7);
}
var start = '2019-01-28';
var today = '2019-03-17';
var week = Math.floor(dateDiff(parseISODate(start), parseISODate(today))/7+1);
var week = start_month <= 1 && week >= 8 ? week -1 : week;
console.log(week);It seems to be unfazed by DST
Thanks for testing those dates. I was curious about DST it's a huge concern for us too, and modifying any date with JS is risky. JS doesn't handle dates very well which is why libraries like momentjs.com and date-fns exist, but we don't want to import libraries into Canvas. Thanks for the leg work @Landmark_Coll , I will plug that in and do some testing.
A trick that I use when finding the date is to pick a time that will always be in the same day, regardless of the DST. For example, I will pick noon rather than midnight.
There is another potential problem here, though, and Robert will see it more than I will. He has students all over the place and the timezone is based off of the local user's machine, while you're wanting the week number to be based off the course time, not the local user's time.
The ENV.TIMEZONE contains the local timezone for the user. The ENV.CONTEXT_TIMEZONE will contain the course timezone (when you're in a course).
I haven't tested this, I'm just responding between classes. It may be that since both the start date and current date are local time that it works itself out, but there's something that makes me think we should look further. Time manipulation in JavaScript is tricky without a library like moment.js.
Looked into ENV.TIMEZONE and ENV.CONTEXT_TIMEZONE
I think this is basically useless... at least not dependable from ENV. It depends on the user changing their timezone in user settings. A quick look up in our data shows
26 timezones exist in our user_dim
78% of all rows have NULL for timezone, so even Canvas isn't defaulting these as expected to the Account's Timezone.
15 timezones set for users who've logged in within the last 365 days
9 timezones set for users who've logged in within the last 30 days
In reality, we have over 1000 users in the last 30 days who've logged in from 23 timezones*, 4 continents and the Pacific.
* Mostly, those not found in user_dim.
Perhaps Canvas does something like this within the browser.
var userTimeZone = ENV.TIMEZONE || ENV.CONTEXT_TIMEZONE;That is, use the user specified timezone if it is there, otherwise default to the context timezone.
In Canvas Data, a NULL could mean that they haven't set it.
I know we don't have the diversity that you do, and it may be that Michael doesn't either. It may not be a big deal. I was just worried about telling people it's one week when it's something else based on their timezone.
I still like the idea of updating the homepage once a week and I know that there are other people who have asked about that before (because I've explained what would be involved). That would cut out the need to go to the home page, but take them directly to the page for that week, which would save them time.
And of course, I'm always concerned about students who don't see the home page because they only click on things that show up in the To Do list and go straight there. I don't have an idea of how many people that is, but I do know that a non-insubstantial number of my students don't use modules and miss out on the instructions to the assignment (for various reasons, I try to I keep my assignments short and put lengthy instructions in a separate page and then add reciprocal links between the instructions and the assignment).
Mobile JavaScript has been even more challenging. There was a big discussion about the inability to determine anything because the ENV variables aren't exposed the way they are in the browser. I suspect that some day, Canvas will clean up their code, move to React, and nothing will be exposed to the end-user unless we can convince them there's a compelling need for it. I hope not, but sometimes it's hard to not read the tea leaves.
If you update the home page each week, then everyone is on the same page -- literally. It doesn't depend on your timezone, it works with mobile. You don't have the issue of people missing the information at the top and you don't have to worry about the JavaScript breaking because Canvas did something.
Math.floor() will truncate a positive value down to an integer and give you 0 for the first week. Adding 1 will make it week 1. Adjusting for spring break is more challenging, especially if you try to read information from the API, but not so bad if you hard-code information into the file. You could do that for several semesters (years) ahead so you don't have to keep on updating it each year.
I would avoid the JSON call unless / until you first verify that you're actually in a course and the course_id exists. For example, the modules has ENV.COURSE_ID, but the syllabus, assignment, grades, people, collaborations, analytics beta, conferences, and outcomes pages do not. The modules and settings page have it as an integer, but the others have it as a string (that's not an issue for here, but shows the inconsistency of the ENV variable). You may be able to find it ENV.Course.id.
If this only needs to appear on one page, then I would make sure you're on that page to run the rest.
The ENV.WIKI_PAGE.front_page thing only works if you're using pages as your front page. If you're using modules as your front page, then it doesn't have that.
It's very unlikely any of this is going to work in the mobile apps.
The embedding an iframe approach would, but it would only work if you use a content page as your home page. I guess it would work if the syllabus page was the home page and you could put it in the place for the syllabus.
Another alternative, since you guys wrote a lot while I was teaching and I'm not sure I followed it all, may be to create separate home pages for each week and then programmatically change that each week through the API. Then the "This is week 1" could be pre-entered and it shows up as the main thing for the students for that week.
We definately have to make sure we only run it within a course and on the wiki page. Course is pretty simple, at least with a regex... handling home page options etc is a requirement we need to address.
@Landmark_Coll , how likely is it your instructors will choose different settings?
Good thinking about mobile, will test.
The different page per week option is pretty slick, for that use case though, each course would have to be preset with those created, probably with a naming convention and then likely some background server task to flip the page. But the potential there could be kinda cool.
Hi Robert,
That code I posted a little while ago had flaws. Here's an updated version:
function parseISODate(ds) {
var d = ds.split(/\D/);
return new Date(d[0], --d[1], d[2]);
}
function dateDiff(d0, d1) {
return Math.round((d1 - d0)/8.64e7);
}
var start = new Date('January 28, 2019');
var today = new Date();
var startISO = start.toISOString();
var todayISO = today.toISOString();
var start_month = start.getMonth();
var week = Math.floor(dateDiff(parseISODate(startISO), parseISODate(todayISO))/7+1);
var week = start_month <= 1 && week >= 8 ? week -1 : week;
$('#course-week-count').html(`${week}`);
var months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'],
days = ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'];
showtoday = days[today.getDay()]+', '+months[today.getMonth()]+' '+today.getDate()+', '+today.getFullYear();
$('#showtoday').html(`${showtoday}`);
console.log(week);
console.log(showtoday);The last bit is to show today's date.
I don't imagine instructors would monkey with course settings, but what setting were you specifically concerned about?
Do you want to show todays date as well, or is that for validating the code as we test?
As @James mentioned, will any instructors use something besides wiki_pages.front_page as their home page... even if they do, will this feature only work on the pages you prescribe/template it to do so? Would this go on the syllabus or any other pages?
Hi Robert,
Indeed. I decided to show today's date on the page as well. Here's a screenshot of what I've got so far...
You'll notice I've got the Week Number and today's date up at the top, and I've also added JS to highlight the hyperlink to this week's page. We will always use wiki_pages.front_page as the home page. I don't envision needing any conditional formatting or text on any other course content.
If you're going to use the default string values for the showtoday date, we don't need to define month and day arrays, and we can just split.
start = new Date('January 28, 2019')
// Mon Jan 28 2019 00:00:00 GMT-0800 (Pacific Standard Time)
split = today.toString().split(/\s/)
// ["Mon", "Jan", "28", "2019", "00:00:00", "GMT-0800", "(Pacific", "Standard", "Time)"]
showtoday = `${split[0]}, ${split[1]} ${split[2]}, ${split[3]}`
// "Mon, Jan 28, 2019"If that's satisfactory, here's an updated version.
(function () {
if (/^\/courses\/\d+.*/.test(window.location.pathname) && ENV.WIKI_PAGE.front_page) {
const parseISODate = ds => {
var d = ds.toISOString().split(/\D/);
return new Date(d[0], --d[1], d[2]);
},
dateDiff = (d0, d1) => Math.round((d1 - d0) / 8.64e7);
$.getJSON(`/api/v1/courses/${ENV.COURSE_ID}`, function (r) {
let start = new Date(r.start_at),
start_m = start.getMonth(),
today = new Date();
var week = Math.floor(dateDiff(parseISODate(start), parseISODate(today)) / 7 + 1),
week = start_m <= 1 && week >= 8 ? week - 1 : week,
split = today.toString().split(/\s/),
showtoday = `${split[0]}, ${split[1]} ${split[2]}, ${split[3]}`;
$('#course-week-count').html(`${week}`);
$('#showtoday').html(`${showtoday}`);
console.log(week);
console.log(showtoday);
})
}
})();This is mostly cleaned up for scope, conditional execution, and smaller file size... if this is good I'll start working on the DOM changes without jQuery.
I've swapped in your code and it seems to be working well. The only thing I changed was to add these lines at the bottom to highlight the current week's hyperlink:
for (var i = 1; i <15; i++) {
if (week == [i]) document.getElementById("w" + i).style.backgroundColor = "yellow";
}
What happens if there are more than 15 weeks?
The instructor adds a week without adding the id="w16"?
The nature of the programs we offer in this division would limit the number of weeks in a semester to 15, but I suppose if we can calculate the number of weeks using the start and end dates it would be more elegant.
As for the instructor adding the correct IDs, I've got that covered. I've written a nifty PHP script that resides on our in house Apache server. The instructor creates a CSV file with the modules and weeks along with the Canvas file numbers for the module images. The PHP script parses the CSV file and creates all the HTML for the Canvas home page. Then it's just copy and paste the HTML into Canvas. Boom goes the dynamite.
Too many off-stage moving parts for this magic show, people are watching. ![]()
Does this work for you?
Line 17, give each UL that holds week links a class of course-week-links, this limits the scope of the DOM search
Line 24, will go through an undefined amount of links, and complete on match.
(function () {
if (/^\/courses\/\d+.*/.test(window.location.pathname) && ENV.WIKI_PAGE.front_page) {
const parseISODate = ds => {
var d = ds.toISOString().split(/\D/);
return new Date(d[0], --d[1], d[2]);
},
dateDiff = (d0, d1) => Math.round((d1 - d0) / 8.64e7);
$.getJSON(`/api/v1/courses/${ENV.COURSE_ID}`, function (r) {
let start = new Date(r.start_at),
start_m = start.getMonth(),
today = new Date(),
split = today.toString().split(/\s/),
showtoday = `${split[0]}, ${split[1]} ${split[2]}, ${split[3]}`,
links = document.querySelectorAll('.course-week-links a');
var week = Math.floor(dateDiff(parseISODate(start), parseISODate(today)) / 7 + 1),
week = start_m <= 1 && week >= 8 ? week - 1 : week;
$('#course-week-count').html(`${week}`);
$('#showtoday').html(`${showtoday}`);
Object.keys(links).forEach(function (i) {
if (links[i].textContent == `Week ${week}`) {
return links[i].style.backgroundColor = 'yellow';
}
});
console.log(week);
console.log(showtoday);
})
}
})();About the same, but not so dependent on your backstage pyrotechnics.
Others can edit if their links don't exist or have different values, and the solution can help more than 1.
The first line is greedy and likely to throw an uncaught TypeError (or some similar fatal error that may keep the rest of whatever is in the global custom javascript from running.
Since you're running on just the front page, don't put .* at the end. That makes it run on every page that starts off courses/\d+ not just the front one.
The ENV.WIKI_PAGE.front_page isn't defined if you're not on a page, which will happen once you move off the front page.
Change line 1 to line 2
if (/^\/courses\/\d+.*/.test(window.location.pathname) && ENV.WIKI_PAGE.front_page) {
if (/^\/courses\/\d+\/?$/.test(window.location.pathname) && typeof(ENV.WIKI_PAGE) !== 'undefined' && ENV.WIKI_PAGE.front_page) {
Greedy by design... what if they get to the course's home page from pages, or alternative/link/method?
courses/123/pages/my-course-home page
also has ENV.WIKI_PAGE.front_page == true
When this happens do we not execute the code?
Nope, we don't. People shouldn't be linking to the homepage from another page -- unless you're rotating the homepage like I suggested. They should link to the course.
However, if you really want to include it on those pages, then you modify the pathname test (I didn't escape this for readability).
^/courses/\d+(/pages/.*)?$
This is like watching magicians wave magic wands. Nice work magicians.
Robert and James are the magicians here. I'm just the volunteer from the audience. ![]()
Current progress
no jQuery
with 'greedy' matching for page url. #comment-143926
(function () {
if (/^\/courses\/\d+(\/pages\/.*)?$/.test(window.location.pathname) && ENV.WIKI_PAGE.front_page && typeof (ENV.WIKI_PAGE) !== 'undefined') {
const isoDate = ds => {
let d = ds.toISOString().split(/\D/);
return new Date(d[0], --d[1], d[2]);
},
dateDiff = (a, b) => Math.round((isoDate(b) - isoDate(a)) / 8.64e7),
setWeek = ds => {
let start = new Date(ds),
today = new Date(),
day = today.toString().split(/\s/),
showtoday = `${day[0]}, ${day[1]} ${day[2]}, ${day[3]}`,
wk = Math.floor(dateDiff(start, today) / 7 + 1),
week = start.getMonth() <= 1 && wk >= 8 ? wk - 1 : wk,
links = document.querySelectorAll('.course-week-links a'),
cg;
document.getElementById('course-week-count').textContent = week;
document.getElementById('showtoday').textContent = showtoday;
Object.keys(links).forEach(i => {
cg = links[i].textContent.match(/Week (\d+)/);
if (cg != null && cg[1] == week) {
return links[i].style.cssText = "background: yellow; font-weight: bold; padding: 1.5px";
//return links[i].style.backgroundColor = 'yellow';
//return links[i].classList.add('active-week');
}
});
}
fetch(`/api/v1/courses/${ENV.COURSE_ID}`, {
'headers': {
'accept': 'application/json',
'content-type': 'application/json'
}
})
.then(response => response.text())
.then(r => setWeek(JSON.parse(r).start_at));
}
})();Could also pass a class at line 23 with classList.add()
I think for mobile we can make a couple changes and pass static dates to get around the ENV.
Robert, I like that you're keeping this code generic enough that others can take advantage of it as well. I'd hate to be the only beneficiary of your largess.
I like what you did with the Week hyperlinks. I just need to tweak it a tiny bit because I allow instructors to have an optional descriptor in the hyperlink, such as: "Week 1 - Orientation." (Sorry there was no example of that in the screenshot I shared earlier.) I think we could use search() or indexOf() for that.
I used indexOf() because we're not dealing with regular expressions.
I updated the code in #comment-143979
Why aren't we using regular expressions with a capture group? Match /Week (\d+)/
Then you could handle whether spring break threw off the counting.
Sorry if it sounds like I'm coming in and second guessing all the design decisions. I'm busy teaching and trying to get the last final completed (finals start next Friday) and trying to find 5 minutes here or there to respond. Time to start class.
Community helpTo interact with Panda Bot, our automated chatbot, you need to sign up or log in:
Sign inTo interact with Panda Bot, our automated chatbot, you need to sign up or log in:
Sign in
This discussion post is outdated and has been archived. Please use the Community question forums and official documentation for the most current and accurate information.