Your Community is getting an upgrade!
Read about our partnership with Higher Logic and how we will build the next generation of the Instructure Community.
Found this content helpful? Log in or sign up to leave a like!
Instructure made significant changes to the Add People dialog in the last release, and our existing script for customizing the contents of that dialog no longer works. I was able to adapt my existing code to the new dialog, but the changes don't "stick". Clicking the "Start Over" button in the dialog resets it back to stock, and so does closing and reopening it.
My questions are: What is the proper way to customize the text in the new Add People dialog in a course? It seems to be using React, so is there a way to tie into the objects rather than traversing the DOM?
FYI, these are the customizations we're making:
Solved! Go to Solution.
Our CSM pointed us to this snippet on the Canvas GitHub account. It worked right out of the box, and it'll even detect when the window has reopened so it can make the changes again, but it doesn't detect when the "start over" button is clicked. Something like @James was suggesting would be needed for that case. For us, this is good enough. Here is the code we're using in production. It's basically the same as the snippet from Canvas, but with a few tweaks.
I am in the same boat. I hope someone has an answer to this. We turned off the option for anyone to have access to the +People except for the few of us that are Canvas Admins. It is not creating a lot of extra work for us by having staff and faculty request that people be added to their courses but it would be nice to give the power back to the teachers.
I don't have a good answer, but I wish I would have seen this a few weeks ago when I was talking with Canvas engineers and product managers about the switch over to React and how what they're doing is going to make it harder for us to do what we want to do. I was having trouble coming up with a concrete example as opposed to hypotheticals.
One engineer explained React like this (I might get it off slightly, but the gist is there). There is a virtual DOM and a real DOM. The real DOM is what is shown to the users inside the browser. The virtual DOM is where Canvas tells React to makes all of its changes. Then, when the code issues a render(), React compares the virtual DOM with the real DOM and very quickly writes any changes to the real DOM.
They have no plans to expose the React virtual DOM to us, and even said that us messing with things could end up breaking Canvas. After thinking about it a little more, I think the breaking Canvas might happen if we change the react-id that gets added, but I have not done extensive testing to verify this. A concern I have (as someone who knows little to nothing about React) is that might mean that even though they like React, we might not be able to use it because our react-id's might interfere with theirs.
The more likely scenario, which you're seeing, is that your changes are getting lost when Canvas re-renders the page.
I'll say what I think will work, as long as you accept the caveat that I haven't tested it, but I think it will work.
My best guess for a solution is to add a mutation observer that looks for when that object changes and then change it back every time Canvas messes with it.You will have to attach the observer to some parent that is present at the beginning and then either watch for childList or subtree depending on how far down it is.
I am almost done working on a script to move the current course to the top of the list when someone clicks on "Find a rubric" for an assignment, discussion, or quiz. I had to put one mutation observer on the document.body to watch for the creation of a div for the dialog and then once that was created, add another mutation observer on the dialog's div to watch for the content in it to appear. I could have added subtree on the document.body, but I figure that there are a lot of changes that happen somewhere in the body of a document and I didn't want it to slow things down.
After the dialog's div was created, I disconnected the observer on the document.body so it wasn't listening anymore. What I'm thinking you'll have to do is leave an observer running to continue to pick up changes after React re-renders() the virtual DOM to the real one.
Our CSM pointed us to this snippet on the Canvas GitHub account. It worked right out of the box, and it'll even detect when the window has reopened so it can make the changes again, but it doesn't detect when the "start over" button is clicked. Something like @James was suggesting would be needed for that case. For us, this is good enough. Here is the code we're using in production. It's basically the same as the snippet from Canvas, but with a few tweaks.
Hi Jacob! I'm getting a Jive 404 message when I click on the link to the snippet and I'm having a hard time finding it on GitHub. Do you happen to have a direct link to the script on Canvas' GitHub that you could share?
Thanks so much!
-Emily
Sure! Maybe I pasted the wrong link there. This one should work: canvas/branding/changes_to_add_people at master · unsupported/canvas · GitHub
Perfect, thank you!!
-Emily
I found this here, canvas/trigger_add_people_changes.js at master · unsupported/canvas · GitHub
But I am unable to get the changes to stick after the first modal instance. I still have to refresh the page to get it to modify the modal again... Anyone have similar issues or a fix?
That's what I used as the basis for my code. Unfortunately, I don't have a solution for the changes disappearing after the first modal instance. I believe it's possible by using a timeout to constantly check and make the change, but the real solution would be tying into the React library somehow to make the changes natively.
Thanks Jacob
I took a crack at this today with the Mutation Observer. This will watch for DOM Mutations, which will let you sneak in and make your Canvas UI changes, again and again and again. Since this is consistently observing changes to the DOM, I've added a utility function I'm sure everyone has for targeting specific pages written by Ryan Florence.
I did not have time to alter it for the use cases above... but I'm sure the sample below should get people started.
YES!!! Thank you so much!!! That worked perfectly.
carroll-ccsd and I worked together on using a mutation observer to add an alert message to the Add People dialog/pop-up. The alert reminds people to only use the Student role when enrolling students, and it works every time the Add People button is clicked.
The code is below. It is written in vanilla/pure JavaScript with no jQuery. Here's how it works:
At the very bottom of the code, we set an onclick listener that detects whenever someone clicks on the Add People button.
When the button is clicked, it starts the mutation observer (in the startObserver function), which does the following:
The final step of adding the alert is done in the aptly-named addAlert function. If we were using jQuery, we could do everything from this function in a single line of code, but since we aren't, we have to build the div that contains the alert message and then insert it.
(function() {
'use strict';
//ccsd.util.onPage(/^\/courses\/\d+\/users/, function() {
// use this -if- you don't want to include onPage
if(/^\/courses\/\d+\/users/.test(window.location.pathname)) {
var pplModal = {
cfg: {
// Id of the button that triggers the mutation observer
triggerId: 'addUsers',
// The id that will be searched for to confirm that the mutation we are looking for has occured
mutationId: 'add_people_modal',
// Id of the new message added to the people modal
newId: 'custom-addPeople-Notice',
// Class of the new message
newClass: 'alert alert-warning',
// The message
newText: "Please keep in mind, K-12 students should only be enrolled with the <b>Student</b> role.",
// Class of the element that the message will be inserted before.
elemClass: 'peoplesearch__selections',
}
}
pplModal.getByClass = function(targetClass) {
var elems=document.getElementsByTagName('*'), i;
for (i in elems) {
if (elems[i].className === targetClass) {
return elems[i];
}
}
}
pplModal.addAlert = function() {
var neededElem = pplModal.getByClass(pplModal.cfg.elemClass);
var newNode = document.createElement("div");
newNode.id = pplModal.cfg.newId;
newNode.className = pplModal.cfg.newClass;
newNode.innerHTML = pplModal.cfg.newText;
neededElem.parentNode.insertBefore(newNode, neededElem);
}
pplModal.startObserver = function() {
var detectedModal = false;
var mcb = function(mutationsList) {
var mutationHTML = '';
for(var mutation of mutationsList) {
mutationHTML = mutation.target.innerHTML;
if((mutationHTML).indexOf(pplModal.cfg.mutationId) > 1 ) {
detectedModal = true;
pplModal.addAlert();
break;
}
}
if (detectedModal === true) { observer.disconnect(); }
};
var observer = new MutationObserver(mcb);
observer.observe(document.body, { attributes: true, childList: true, subtree: true });
}
document.getElementById(pplModal.cfg.triggerId).onclick = pplModal.startObserver;
}//);
})();
The code smithb_ccsd provided above, just adds an alert, and isn't going to solve issues for different institutions exampled in the original post. However the pplModal.startObserver() is reusable, as is the onclick event, and anyone could alter the rest of the code to remove options, change defaults etc, with or without jQuery.
Here's the code on GitHub.
ccsd-canvas/course-add-people at master · robert-carroll/ccsd-canvas · GitHub
Robert, the message I was responding to disappeared between the time I started writing it and the time I realized I was in the Inbox and didn't have access to the advanced editor. Here's what I had so far, but there's no context right now for it.
Quick thoughts. I've got supper waiting for me.
rosterSort();
function rosterSort(mutations, tableObserver) {
var roster = document.querySelector('table.roster tbody');
if (!roster) {
if (typeof tableObserver === 'undefined') {
var sel = document.getElementById('content');
var obs = new MutationObserver(rosterSort);
obs.observe(sel, {
'childList' : true,
'subtree' : true
});
}
return;
}
if (roster && typeof tableObserver !== 'undefined') {
tableObserver.disconnect();
}
The food is probably getting cold now. I did something highly unusual for me and I grabbed all your code and pasted it into a Word document so I would be able to think about it and respond more fully.
And now that I'm still asleep, I notice that the roster && portion in line 15 is redundant. I think it was left-over from something I was trying but had the logic on. The main logic of the routine begins after line 18.
I'm running on way too little sleep, so I haven't looked at all of the logic. Should line 44 check for > -1 instead of > 1 or do you need it to be after the first two bytes?
Hi James, yes, you're right, but the ids we're looking for are never going to show up that early in the string. ":^)
Thanks for explaining, smithb_ccsd.
I rarely work with innerHTML, so there are probably extra characters at the beginning that wouldn't be there in textContent. Seeing the > 1 just looked werid, so I wasn't sure if it was a typo.
Since I'm a relative newbie to JavaScript programming, most of my lessons have come recently. So when I see something like innerHTML, what I read is still fresh enough that I remember something was said about it. In this case, my aversion to innerHTML is rooted in warnings I've seen about it vs textContent. For example, there are warnings about setting it at Element.innerHTML - Web APIs | MDN. Reading it may be okay, but there is a warning in that document that is ambiguous.
Warning: If your project is one that will undergo any form of security review, using innerHTML most likely will result in your code being rejected. For example, if you use innerHTML in a browser extension and submit the extension to addons.mozilla.org, it will not pass the automated review process.
I don't plan on submitting any browser extensions to addons.mozilla.org, but warnings like that are enough to make me look for other ways. That reminds me of pie charts from statistics. They can be done right, but so often they are done so poorly that many statisticians say you shouldn't ever use them. Even our textbook says "The only pie chart you will see in this book" meaning they thought they had to mention it, but they really don't recommend them.
I will readily admit, and carroll-ccsd will probably attest, that I don't always pick the shortest way to do something. I mean, why write one line of code when five will work?
I knew you'd respond about the innerHTML... Not that I wanted to leave it out there, but because it worked, and I wanted to see where this goes... some observations of mutations, if you will.
Yesterday Bruce and I spent about 45 minutes flushing out the following mutation observer, by looking through each mutation, expanding and breaking it down looking for anyway to access the right mutation, the add people modal. The sample we worked out uses the mutations classList to find the modal with the people class, but then fails to trigger any time the add people button is clicked.
// flag course add people modal
var mcb = function(mutationsList) {
for(var mutation of mutationsList) {
if(Object.values(mutation.target.classList).indexOf('people') > 1 ) {
if(mutation.addedNodes[0].children[0].firstChild.id == 'add_people_modal')
console.log('doo eet')
}
}
};
var watch = document.body;
var observer = new MutationObserver(mcb);
observer.observe(watch, { childList: true, subtree: true });
Bruce continued...
From what I can tell Googling for mutations, it is just not a thing. Most any developer I see who's dealing with mutations is either making them or writing observers to debug them in their own environment.
What I'm getting at is, each mutation we write against Canvas is basically an investigation worthy of a Badge/r.
For instance, you showed me the following with Custom JavaScript for Admin/Courses Page #comment-112302
Mine:
// mutation callback for accounts courses table
var callback = function (mList) {
for (var mutation of mList) {
// trigger when we see the right mutation
if (mutation.target.dataset.automation == 'courses list' && mutation.target.childNodes.length == init.length) {
try {
var doit = addLinks($('tbody[data-automation="courses list"] tr td:nth-child(2)'))
if (doit == true) break // stop
} catch (e) {
console.log(e)
}
}
}
};
Yours, doesn't even need the mutations:
var callback = function () {
var allItems = document.querySelectorAll('tbody[data-automation="courses list"] tr td:nth-child(2) > a');
var markedItems = document.querySelectorAll('tbody[data-automation="courses list"] tr td:nth-child(2) > a + span');
if (allItems.length > markedItems.length) {
console.log('courses list')
}
};
Bruce's listener is the magic I like, it's why I ignored innerHtml.
// flag add people modal every time, ignore everything else
(function() {
'use strict';
var pplModal = {};
pplModal.startObserver = function() {
var detectedModal = false;
var mcb = function(mutationsList) {
var mutationHTML = '';
for(var mutation of mutationsList) {
mutationHTML = mutation.target.innerHTML;
if((mutationHTML).indexOf('add_people_modal') > 1 ) {
detectedModal = true;
console.log('doo eet');
break;
}
}
if (detectedModal === true) { observer.disconnect(); }
};
var observer = new MutationObserver(mcb);
observer.observe(document.body, { attributes: true, childList: true, subtree: true });
};
document.getElementById('addUsers').onclick = pplModal.startObserver;
})();
...and last night while watching the Manchurian Candidate, I tinkered with what we tried yesterday and everything else I've tried. I've been trying to solve observer issues with Admin Tray - Sub Account Menu since the day I wrote it. While the rest of the code works really well and continues to improve, the the tray seems to take exponentially longer and longer to open the tray on repeated clicks. This is because the observer never disconnects, and disconnecting would prevent it from work on the next click. My current undocumented solution is don't open the links in a new tab. If I loop through every mutation I can fish out a very specific one, and reduce these issues. I'm still trying to disconnect the observer, but lately I'm mostly playing with properties within mutations.
// global nav admin tray open/close
varv mcb = function(mutationsList) {
for(var mutation of mutationsList) {
if(mutation.addedNodes.length >= 1 && mutation.target.id == 'nav-tray-portal') {
console.log('open tray')
}
if(mutation.removedNodes.length >= 1 && mutation.target.id == 'nav-tray-portal') {
console.log('close tray')
}
}
};
var watch = document.getElementById('nav-tray-portal');
var observer = new MutationObserver(mcb);
observer.observe(watch, { childList: true, subtree: true });
What do we do if we can't find a DOM element?
Combining his and yours, I start seeing reusable patterns for mutation observers.
(function() {
'use strict';
var pplModal = {};
pplModal.startObserver = function() {
var detectedModal = false;
var mcb = function() {
var detectedModal = false;
var findModal = document.getElementById('add_people_modal');
if(findModal) {
detectedModal = true;
console.log('add_people_modal');
}
if (detectedModal === true) { observer.disconnect(); }
};
var observer = new MutationObserver(mcb);
observer.observe(document.body, { attributes: true, childList: true, subtree: true });
};
document.getElementById('addUsers').onclick = pplModal.startObserver;
})();
I know, looking through your Canvancements, that you're pretty good at the observer.disconnect()
I'm trying to figure out best practices... maybe we can write a tutorial, with examples...
We should disconnect, use DOM when possible (because it's easier), and how to fish out mutation properties...
...or just a series of warnings.
I'm going to spend some time this weekend trying to solve my crux on the sub account tray observer. :smileygrin:
Attaching a mutation observer as an onclick seems highly suspect for why it slows down after doing this many times. You should only need one mutation observer, not a new one every time someone clicks. If they're being destroyed because React is overwriting the DOM, then we need to look further up the food chain for the place to watch.
Completely agreed. When I first wrote it, being new to mutations and trying to handle the clicks and the loading, I got hung up trying to detect and define logic to identify the various progress states. Also, didn't start noticing the buildup until recently.
After some sleep, my waking thoughts (other than how late did I sleep?) were that you might want to be using Event Listeners in combination with Mutation Observers for your subaccount tray. The Event Listener is similar to onclick. Since I knew little about either at the time, I ended up with Event Listener's as the preferred way, but don't fully remember why right now. The key is that you don't use the Event Listener to set the Mutation Observer, the Mutation Observer is already set. You use the Event Listener to let the Mutation Observer know that it's time to act.
I did this with QuizWiz. I added a button that would auto advance to the next person in SpeedGrader, but it had to wait for Canvas to finish processing the current person before it could do that. An Event Listener was added to the button to set a global flag. Then, when a mutation happened, it looked to see if the global flag was set and if it was then it did it's thing and turned the global flag off.
There were other Event Listeners to watch what Canvas would do and then duplicate the behavior in another spot so I had to make sure that I fell through to Canvas' handling of the event but watched for the field to change with a Mutation Observer and then I could duplicate it in my spot.
You might already have this figured out -- I was really tired yesterday and spent a lot more time looking at the add user script than the subaccount tray.
Are we talking about what happens when you click the the +People button on the Course People page?
It seems like it, but I'm seeing a very clear pattern when I click it and close it that makes the mutation observer look really simple compared to what you're describing. I'm starting to wake up now, which makes me think I might be missing something about what you're trying to do here.
My issue with an onlick observer for trays, I was referring to this, L167 admin sub account tray user script beta · GitHub, which I'm going to rewrite this weekend.
Ahh, I thought we were looking at the problem here and I was spending time looking for the element for the mutation observer for it.
I found it for this issue, I think, but it needs 2 observers. If you watch document.body with childList:true for 'span[data-ui-portal="true"]' to appear, then you have the initial launch. If the body doesn't contain that, then the window is being destroyed and you don't need to do anything. That gives you the initial appearance and will re-invoke itself every time the modal is recreated.
After the modal is created, there is a div.addpeople that seems to stay there. I haven't fully tested that part yet., but it looks like you can then attach a mutation observer to that add the warning text when people go back to the main screen after clicking "Back" or "StartOver". If you only need the warning the very first time the screen appears, then you don't need to bother with this.
Again, what I'm working on isn't fully tested and it's not intended to be flexible or reusable like Bruce's, but it might help see the logic of the ordering of the mutation observers and it's only about 34 lines, a good chunk of which is adding the text because I'm not using innerHTML.
Oh yeah, I noticed the regex being used on Bruce's script will run on pages that contain a specific user because there's no $ at the end.
carroll-ccsd, I put Bruce's code into Eclipse and other than the semicolon issue we've discussed before, it flagged that observer is being used before it is referenced. That's because of line 53 referring to observer.disconnect(); but observer doesn't appear until line 56. Variable hoisting occurs of course, so it's more of a warning than an error, but it can be avoided by passing the observer to mcb on line 43.
var mcb = function(mutationsList, observer) {
That's not going to fix the issue, but it was a perfect example to show how you can handle passing the observer to the callback in case you need to disconnect it.
Also, the pplModal.getByClass() function seems highly inefficient with getElementsByTagName('*'). That returns 380 items on my people page. If the class is not found, then it comes back as undefined.
Unless I'm missing something, the entire function could be replaced by a single line.
// This entire function
pplModal.getByClass = function(targetClass) {
var elems=document.getElementsByTagName('*'), i;
for (i in elems) {
if (elems[i].className === targetClass) {
return elems[i];
}
}
};
// and this line in the addAlert function
var neededElem = pplModal.getByClass(pplModal.cfg.elemClass);
// is mostly equivalent to this
var neededElem = document.querySelector('.' + pplModal.cfg.elemClass);
The only differences I see are you check for 'peoplesearch__selections' as the only class on the element, so if it appears more than one place but you need the case where it's the only thing, you would need to add an extra check. The other thing is that document.querySelector() returns null if nothing is found the getByClass doesn't return anything, so probably undefined.
If you need the class to contain only pplModal.cfg.elemClass, then you could use
var neededElem = document.querySelector('[class="' + pplModal.cfg.elemClass + '"]');
This is really good...
Bruce had actually updated the code before he left for the day, but I guess didn't update his post. I updated GitHub file before I left the office... his is slightly different, both are really nice, and fantastically interchangeable!
// course-add-people/course-add-people.js#L32
var neededElem = document.getElementsByClassName(pplModal.cfg.elemClass)[0];
Thanks for the additional info on mutations, rosters, etc.
I'm going to see what I can do this weekend with all the examples.
I hope I didn't wake you or keep you from dinner. It's hard not to reply, and then you come back with more to think about!
I need something besides ESLint in VSCode to get the errors you see in Eclipse.
I have seen the the first element from the getElementsByClassName as well. I never really dug into it before, figuring it was a hold-over from earlier versions of something (like people wanting to support IE 5). It turns out that may not be the case.
I originally thought the usage might be a case of querySelector not being supported is why people use that. But perhaps not. It might be speed related, although that's not touted in the MDN pages because it doesn't do benchmarking. These availability charts are from the MDN pages.
This is document.getElementsByClassName
This is document.querySelector
It looks like except for Opera, the querySelector was available earlier, but people shouldn't be using IE 8 or 9 with Canvas. The CanIUse website shows availability for both approaches at about 95.5% with the querySelector being just a smidge higher (95.64% vs 95.47%)
smithb_ccsd's new version. ccsd-canvas/course-add-people · GitHub
// https://github.com/robert-carroll/ccsd-canvas/tree/master/course-add-people
// on the course/users page
// add alert to the course +people modal
(function() {
'use strict';
//ccsd.util.onPage(/^\/courses\/\d+\/users/, function() {
// use this -if- you don't want to include onPage
if(/^\/courses\/\d+\/users/.test(window.location.pathname)) {
var addAlert = function() {
var neededElem = document.getElementsByClassName('peoplesearch__selections')[0];
var newNode = document.createElement("div");
newNode.id = 'custom-addPeople-Notice';
newNode.className = 'alert alert-warning';
var space = neededElem.parentNode.insertBefore(newNode, neededElem);
space.insertAdjacentHTML('afterbegin', 'Please keep in mind, K-12 students should only be enrolled with the <b>Student</b> role.');
}
var startObserver = function() {
var mcb = function() {
var findModal = document.getElementById('add_people_modal');
if(findModal) {
addAlert();
observer.disconnect();
}
};
var observer = new MutationObserver(mcb);
observer.observe(document.body, { attributes: true, childList: true, subtree: true });
}
document.getElementById('addUsers').onclick = startObserver;
}//);
})();
Thanks so much for this! I've just implemented a version of it with my own tweaks to the dialog box.
My first venture into mutation observers last year didn't get very far and I temporarily abandoned it. I'll be scrutinizing this more when I give observers another go.
carroll-ccsd Thank you this has been working well for us. We added some additional code to hide a few roles in the dropdown. It was working great for many months, but now it seems the dropdown has been updated to a React component where we can no longer remove the option by value. This is an example of what was working previously:
$('[for="peoplesearch_select_role"] option[value=3]').remove();
Have you run into any similar issue before and/or can suggest a possible solution? Many thanks!
I recently tinkered with adjusting the list item text of a React component in https://community.canvaslms.com/thread/36823-rename-inbox-category
If that looks like something you want to work on I'm happy to answer any questions or assist, if it's too much, I can find some time and take a shot at it.
@ismail_orabi - Did you ever solve this? We were also hiding a couple of roles and haven't been able hide them for the past few months.
@buellj Yes I was able to use carroll-ccsd's example to resolve this. Here is the code we are using
// always use IIFE, to isolate code from global scope
(function() {
"use strict";
// only run this code on the conversations page
if (/^\/courses\/\d+\/users$/.test(window.location.pathname)) {
$("#role_id option[value=3]").remove();
$("#role_id option[value=10]").remove();
$("#role_id option[value=11]").remove();
$("#role_id option[value=4]").remove();
$("#role_id option[value=9]").remove();
$("#role_id option[value=7]").remove();
const addPeopleModal = function(mtx, obs) {
let watchResults = document.querySelector(
'span > span > span > span > div[role="presentation"] > ul[role="listbox"]'
);
// the result list is available
if (watchResults) {
//Remove roles from "Add People" screen within a course using role id
$("#3").remove();
$("#10").remove();
$("#11").remove();
$("#4").remove();
$("#9").remove();
$("#7").remove();
}
};
// wait for the modal to be open
const watchForModal = function() {
const uiDialog = function(mtx, obs) {
let pplMdl = document.getElementById("add_people_modal");
// the modal is found
if (pplMdl) {
const defaultRole = document.getElementById(
"peoplesearch_select_role"
);
//set role value to blank, since Student is a hidden role
if (defaultRole.value === "Student") {
defaultRole.value = "";
}
//add warning to select role before proceeding
if (
$("#add_people_modal").length == 1 &&
$("#addPeople-yaleDirLink").length == 0
) {
$("#peoplesearch_select_role").css({
border: "2px solid red",
"border-radius": "4px"
});
var roleWarning =
"<h6 id='addPeople-yaleRoleWarning'>Please select a role to continue</h6>";
$(
'[for="peoplesearch_select_role"] span:first span:first'
).append(roleWarning);
}
//manage behavior of next button
if (
$("#peoplesearch_select_role").val() === "" ||
$(
".addpeople__peoplesearch fieldset:nth-child(2) textarea"
).val() === ""
) {
$("#addpeople_next").hide();
} else {
$("#addpeople_next").show();
}
//hide/show next button based on role select
if (
$("#peoplesearch_select_role").val() === "" ||
$(
".addpeople__peoplesearch fieldset:nth-child(2) textarea"
).val() === ""
) {
$("#addpeople_next").hide();
} else {
$("#addpeople_next").show();
}
if ($("#peoplesearch_select_role").val() === "") {
$("#peoplesearch_select_role").css({
border: "2px solid red",
"border-radius": "4px"
});
$("#addPeople-yaleRoleWarning").show();
} else {
$("#peoplesearch_select_role").css({
border: "",
"border-radius": ""
});
$("#addPeople-yaleRoleWarning").hide();
}
// watch the add people modal instead
const observer = new MutationObserver(addPeopleModal);
observer.observe(document.body, {
childList: true,
subtree: true
});
}
};
// watch document.body for modal
const observer = new MutationObserver(uiDialog);
observer.observe(document.body, {
childList: true,
subtree: true,
attributes: true
});
};
// start when the user clicks the compose button
watchForModal();
}
})();
Thank you @ismail_orabi - it's helpful to see a working example. It seems that the mutation observer piece is key but I was not able to make it work in our instance; I need a minimum of code to remove a role without all of the other stuff but I am struggling to understand how to update the mutation observer to wait for the Add People button to be pressed,modal to appear, role select box to be selected and then hide the role.
Is this code still working?
On beta it crashes my browser.
Limited time at the moment so have only briefly looked into this...
Hovering over the options, changes the value and the aria-activedescendant attributes. The IDs for activedescendent are 1934-1938, which is ID of the role in API and Canvas Data. The difference here suggests everyones will not be the same. Maybe we can create a customizable config option removing them by name?
However, I can't seem to break down the React components and find the actual options that are selected. The DOM updates, but I have not identified where in the DOM the elements land, and what selectors there are to hide them.
This code was not working for me when I tested - I updated the role info to match our instance but that didn't seem to solve the issue.
I wanted to write up a summary of what ended up working for me. I tried to design the code in such a way that it could be re-usable. I certainly borrowed concepts from what has been shared in the threads on this question. Thanks to all for what you have shared. I hope that others find what I have put together here helpful. Please see the comment about the "observerDaemon" in the code snippet below as it describes the meat of the strategy used here.
$(document).ready(function() {
// set up an observerDaemon for the +People modal on the courses users page.
if (/^\/courses\/\d+\/users$/.test(window.location.pathname)) {
observerDaemon("add_people_modal", peopleMutationCallback, peopleMods);
}
});
// The `observerDaemon` is a generalized tool for monitoring changes to the Canvas UI
// from this global columbia.js file.
//
// It works by spinning up a daemon (an infinite while loop) that intializes
// a `MutationObserver` javascript object. This object is a tool to detect changes
// to any part of the HTML within a given `elementId`.
//
// The goal of this object is to give developers a consistent way
// of monitoring a given element within the Canvas UI. Doing this is tricky task
// because of the way that "react-js" works (the JS framework used by Canvas).
//
// At high-level the strategy for customizing the Canvas UI can be stated as:
//
// 1. Pick the element that you want change, get it's ID.
// 2. Monitor that element for changes, and refresh the view of that object
// every 200ms.
// 3. Every time a change is detected for the element we are manipulating, execute
// the changes that we want once more.
//
// If we don't have this daemon running a while loop, we will not be able to respond
// to the "react" frameworks dynamic changes. Elements go in and out of existence with
// the "react" framework, when any arbitrary piece of "state" on the Canvas UI changes.
//
// `observerDaemon` requires three arguments:
//
// - elementId: the ID of the Canvas UI element you wish to change and therefore require
// monitoring on.
// - mutationCallback: a function that gets executed everytime a mutation occurs.
// - modificationFunction: a function that contains you jQuery or JS logic required to make
// the desired changes to the Canvas UI.
//
// References:
// - https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver
// - https://community.canvaslms.com/thread/15837-customizing-add-people-dialog-with-custom-javascript
async function observerDaemon(elementId, mutationCallback, modificationFunction) {
while (true) {
var observer = new MutationObserver(mutationCallback);
var config = { attributes: true, childList: true, subtree: true };
var targetNode = document.getElementById(elementId);
modificationFunction();
try {
//console.log("observing..");
observer.observe(targetNode, config);
} catch (e) {
if (e instanceof TypeError) {
//console.log(e)
} else {
throw e;
}
}
await sleep(200).then(function() {
//console.log("disconnecting...");
observer.disconnect();
});
}
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// This is a function that gets called any time a mutation
// happens according to the `MutationObserver` object. Please
// refer directly to documentation for the `MutationObserver`
// to understand more about the parameters here.
function peopleMutationCallback(mutationsList, observer) {
peopleMods();
};
// This is simply a function that get's executed each time
// a mutation is detected. In this case it modifies the +People
// tool.
function peopleMods() {
// remove the email input
var emailInput = $("#peoplesearch_radio_cc_path");
emailInput.parent().remove();
// remove the sis id input
var sisIdInput = $("#peoplesearch_radio_sis_user_id");
sisIdInput.parent().remove();
}
To 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