Celebrate Excellence in Education: Nominate Outstanding Educators by April 15!
Found this content helpful? Log in or sign up to leave a like!
Hi,
Im trying to figure out how to rename the pre-set category "Teaching assistants" in Inbox compose message.
I want it to say "Mentors" instead. I'm trying to solve this with javascript uploaded into the theme.
My work in-progess looks like this, and I dont understand why it's not working:
var x = document.getElementsByClassName("result-name");
var i;
for (i = 0; i < x.length; i++) {
if(x[i].innerHTML.match("Teaching assistants")){ x[i].innerHTML = "Mentors"; }
}
This screenshot show you where and what I mean by category:
This screenshot show you why my work in-progress code looks like it does:
Can anyone help me out?
Thanks
Regards
Fredrik
I saw this over the weekend and each time I sat down for a few minutes I wanted to tinker with this, but time is tough to find at the moment. Anyway, what you are doing is entirely possible. I'm not sure if there's a way to actually rename the Teaching Assistant role globally, which would be the preferred choice for many schools. Maybe someone else knows how to do that. However, I wanted to give you some info so you could make some more progress and come back here and ask for assistance if you still have questions.
Canvas UI components are slowly becoming more based on ReactJS. Often DOM elements we're expecting to be on the page aren't available until the user performs an action that means that we have to wait for them to be available. The best way to handle this is with MutationObserver - Web APIs | MDN. Essentially, you need to fire some code when the user performs the action and specifically wait for the elements to be available, then perform your 'hack'.
The best resource and explanation for this can be found by @James on https://community.canvaslms.com/thread/35621-how-to-adapt-to-the-undocumented-javascript-loading-seq...
I've also done a small hack to the Add People modal, which might be useful discussion and example.
Customizing Add People Dialog with Custom Javascript
I'd be happy to offer any additional assistance if those resources aren't enough.
Thanks, but I really dont understand how to work with this 😕
My work in-progress so far..
// Set my element:
var myElement = document.querySelector('b.result-name');
// Generate the observer:
var observer = new MutationObserver(function(mutations){
mutations.forEach(function(mutation){
if(myElement.textContent == "Teaching assistant"){
myElement.textContent = "Mentors";
observer.disconnect();
}
});
});
// Start observing:
observer.observe(myElement, {attributes: true, childList: true, subtree: true});
We don't need to iterate over mutations, that was an early misunderstanding of mine too. Once the mutation is complete, the elements exists, we don't need to check through each one.
We also don't want to use setTimeOut at all, as the Mutation Observer is running until... hopefully we identify the element we are waiting for.
I stepped through this to get you started. Try working on this in the Developer Tools console and use the console and TamperMonkey to test the scripts without having to update the theme for each test.
I don't have any teaching assistants so I made the string to replace avariable on line 8.
I used XPath to find the list item we want to update.
Current issue is making it work again after the modal closes.
Needs heavy testing and evaluation, not for production.
// deprecated sample, skip to new version #comment-154649
Wrangled this together for the modal so far.
The page also includes the option for the user to select the course, and recipient without using the Compose Message Modal. To be thorough and UX complete, we should also make this work for that recipient list. Additionally, after selecting the Recipient Item, from the list, it puts a pill in the TO area that shows the original list item, so another mutation would be necessary there too. This is one of those instances where I typically question how much code (time, support, download file size, etc) is necessary for the benefit. You can weigh that yourself. We can keep tinkering if it's worth it to you, the challenge is fun.
I'm going to leave this as is for now, unless you want to keep working through it using this as an example. I left comments, and the console.log lines in place to give an idea of what fires and stops and where that happens. This is the most deepest cascade of mutations I've attempted. The tricky bit is getting here without crashing the browser a couple times, leaving some observer.disconnect() in place might save a few restarts until you need to account multiple mutations, or working through the mutations the code presents. I tried something new here as well, as soon as the modal was opened I disconnected and started watching for mutations specifically within the modal.
Here's the updated version. Tagging @James for QA or thoughts?
Tested in TamperMonkey so far, not yet in Theme Editor.
// always use IIFE, to isolate code from global scope
(function () {
'use strict';
// only run this code on the conversations page
if (/^\/conversations/.test(window.location.pathname)) {
let swap_str = 'Teachers';
let with_str = 'Mentors';
const renameRecipientItem = function () {
let result_items = document.evaluate(`//div[@id="ac-result-list-1"]/div/div/ul/li/b[contains(., "${swap_str}")]`, document, null, XPathResult.ANY_TYPE, null);
let ta_item = result_items.iterateNext();
if (ta_item != null && ta_item.textContent === swap_str) {
console.log(`replacing ${swap_str}/Mentors`)
ta_item.textContent = with_str;
}
};
const newMessageMdl = function (mtx, obs) {
let watchResults = document.getElementById('ac-result-list-1');
// the result list is available
if (watchResults) {
// wait for the result list to update
const watchForResultsList = function (mtx, obs) {
let resultList = document.querySelector("#ac-result-list-1 > div > div > ul");
if (resultList) {
console.log('result list updated');
renameRecipientItem();
// do not disconnect
}
};
// start observing for changes to the result list
const observer = new MutationObserver(watchForResultsList);
observer.observe(watchResults, {
childList: true,
subtree: true
});
}
// stop watching the message modal
obs.disconnect();
};
// wait for the modal to be open
const watchForModal = function () {
console.log('compose btn clicked');
const uiDialog = function (mtx, obs) {
let composeMdl = document.getElementById('compose-new-message');
// the modal is found
if (composeMdl) {
console.log('compose modal open');
// compose modal opened, stop watching document.body
obs.disconnect();
// watch the compose new message modal instead
const observer = new MutationObserver(newMessageMdl);
observer.observe(document.getElementById('compose-new-message'), {
childList: true,
subtree: true
});
}
};
// watch document.body for modal
const observer = new MutationObserver(uiDialog);
observer.observe(document.body, {
childList: true,
subtree: true
});
}
// start when the user clicks the compose button
document.getElementById('compose-btn').onclick = watchForModal;
}
})();
I hadn't used xPath selectors before, so I had to do some digging. Sounds like a good way to find text rather than checking each result afterwards. It is very dependent upon the structure, but I'm guessing that's because Canvas doesn't have an ID or classname further down that you can use (without looking, I'd guess this section has been reactified). That means that the code is more prone to breaking and harder to trace, but such is what we have to deal with nowadays.
I wrote some code a couple of weeks ago to export rubrics that had levels of mutation observers and ended up writing some generic code so that I could reuse it -- the DRY principle you like so much. The first time you call it, you give it a parent element, the selector to check for and the callback to make when found. On subsequent invocations, it's used as its own observer, so it just gets the mutations and the observer. I would like to see it promisified so I could do a .then() rather than having to have a callback, but that's where it's at right now. Of course, that complexificates things and since you want the observer to be left listening sometimes but not others. In this case, it might be easier to repeat yourself.
Sorry for being out of it -- start of the semester and still trying to get one of my classes planned more than 1 day in advance.
I felt like XPath might be a better way to address the specific issue. Since there are 2 result lists on the page (each has a unique id), but I wanted to try new things, might not be necessary, but worth a shot. It seems like there will be cases where we try to modify the DOM and the XPath might be the only unique way to traverse.
I've been wondering what abstracting to something reusable might look like. This is the first time, with 2 or more in a script it starts to make sense. Ideally a promisified and chain-able method might work best. Some mutations might be disconnected while others are not, how that gets simplified I'm not sure. Kinda sounds like by the end there would be some reusable methods, but a lengthy list of options/arguments to pass to each. I should count the mutations in our global JS and see if I can produce something reusable in as many lines, not today.
Thanks guys for all the information and help. You have given me the insight of the complexity this task is. I will have a look at the examples and see if I can handle this. Dont want to crasch the browser for my users ofcourse ^_^
To participate in the Instructure Community, you need to sign up or log in:
Sign In