cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 
tr_jbates
Community Champion

Customizing Add People Dialog with Custom Javascript

Jump to solution

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:

  • Remove "Email Address" as an option altogether, since we only want users to be added via Login ID and SIS ID.
  • Set the default option to "SIS ID", and possibly reorder the options
  • Change "Login ID" and "SIS ID" text to conform with our institutional equivalents.
38 Replies
dgrobani
Community Champion

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.

ismail_orabi
Community Participant

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.

buellj
Community Contributor

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

ismail_orabi
Community Participant

 @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();
}
})();
buellj
Community Contributor

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.

 @ismail_orabi ‌,

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.

buellj
Community Contributor

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. 

kl3020
Community Member

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();
}