Add observers to email

jsimon3
Community Participant

Now this is not my idea... I found a git on gitHub that did exactly what I needed it to do the problem was that it was beefy(12k lines)(GitHub - sdjrice/msgobs: A JavaScript modification for the Canvas learning management system which a... ). So I forked it and was going to go to work on it, convert to plain JS modify, when I realized I had no idea WTH is going on. Seriously though, @sdjrice was on another level. So I scraped the project and decided to go in from scratch. I created a baby version of that. So it has a ton of stipulations.

One stipulation that will not disappear is that a course must be selected in order to use.  

  1. can only be used once per session--- this is a stipulation that I imposed because we have very click happy people. You can remove by switching the bool from {once: true} to false 
  2.  This does not work on the grade book.-I am no longer concerned w/this deemed not that important by staff
  3. If you were to decide to remove one of the students from the email there is no direct line to retract the parents from the email. I am no longer working on this as well.

337240_pastedImage_4.png

337238_pastedImage_2.png

I tried to embed the proof of concept no luck so here is the link:

https://www.iorad.com/player/1618642/Add-observers-to-email

Now anywhere you might see a set of // // those belong to a custom CSS animation I can share it if anyone is interested its the pacMan that you see nest to the button. Or you can use the integrated loading animation in Canvas.

function addObserverEmailButton() {
const checkIfNull = async selector => {
while (document.querySelector(selector) === null) {
await new Promise(resolve => requestAnimationFrame(resolve));
}
return document.querySelector(selector);
};
checkIfNull("#compose-btn").then(() => {
if (
ENV.current_user_roles.includes("teacher") ||
ENV.current_user_roles.includes("admin")
) {
const delay = ms => new Promise(res => setTimeout(res, ms));
let conversationsNav = document.querySelector(
"div.ui-dialog-buttonpane.ui-widget-content.ui-helper-clearfix"
);
let parentButton = document.createElement("div");
let classesToAdd = [
"ui-button",
"ui-widget",
"ui-state-default",
"ui-corner-all",
"ui-button-text-only",
"includeObserver"
];
parentButton.classList.add(...classesToAdd);
parentButton.setAttribute(
"style",
"margin:0 2px; min-width: 110px; background-color:wheat;"
);
parentButton.innerHTML = "Include Observers";
conversationsNav.insertBefore(
parentButton,
conversationsNav.childNodes[1]
);

// //let pmLoader = document.createElement("div");
// //let classesToAddPM = ["gameLoader", "pacMan"];
// //pmLoader.classList.add(...classesToAddPM);
// //conversationsNav.insertBefore(pmLoader, conversationsNav.childNodes[2]);

conversationsNav.querySelector(".includeObserver").addEventListener(
"click",
() => {
let course = document.querySelector(
'.message-header-input > input[type="hidden"]'
);
let courseNum = course.value.split("_")[1];
let parentCollection = { data: [] };
let kiddosArr = [];
let emailParents = [];

if (course.value) {
//loading animation
// //document.querySelector(".gameLoader.pacMan").style.setProperty("--playState", "play");
//loading animation
const fetchObservees = `/api/v1/courses/${courseNum}/enrollments?type[]=ObserverEnrollment&per_page=100`;

function nextURL(linkTxt) {
if (linkTxt) {
let links = linkTxt.split(",");
let nextRegEx = new RegExp('^<(.*)>; rel="next"$');

for (let i = 0; i < links.length; i++) {
let matches = nextRegEx.exec(links[i]);
if (matches && matches[1]) {
//return right away
//console.log(matches[1]);
return matches[1];
}
}
} else {
return false;
}
}
//Next define your main call:

const OPTIONS = {
credentials: "same-origin",
headers: {
accept: "application/json"
},
timeout: 5000
};
async function main() {
const response = await fetch(fetchObservees, OPTIONS);
let data = await response.json();
let res = {
data: data,
ok: response.ok,
headers: response.headers
};
parentCollection.data = res.data;
await loop(res);
}
//And then your loop

async function loop(parents) {
if (nextURL(parents.headers.get("Link")) === undefined) {
return;
//otherwise keep going.
} else {
const RESPONSE = await fetch(
nextURL(parents.headers.get("Link")),
OPTIONS
);
let data = await RESPONSE.json();
let res = {
data: data,
ok: RESPONSE.ok,
headers: RESPONSE.headers
};
if (res.ok) {
//console.log(res.data);
parentCollection.data = parentCollection.data.concat(
res.data
);
//console.log(res.data);
//console.info(Object.keys(parents.data).length);
//console.info(Object.keys(parentCollection.data).length);
}
await loop(res);
//await loop(res);
//if you want to wait for it.

//You need to call it from within an async function.
}
}
//Now all you need to do is call the main function:
const waitForMom = async () => {
await main();
};
//or:
//await main();
//if you want to wait for it.
//You need to call it from within an async function !impotanto.
waitForMom()
.then(async () => {
//console.log(parentCollection.data);
document
.querySelectorAll(
'div.message-header-input input[name="recipients[]"]'
)
.forEach(kiddo => {
kiddosArr.push(kiddo.value);
});
//console.log(kiddosArr);
})
.then(async () => {
kiddosArr = kiddosArr.map(x => {
return parseInt(x, 10);
});
})
.then(async () => {
parentCollection.data.forEach(element => {
//console.log(element.associated_user_id);
if (kiddosArr.includes(element.associated_user_id)) {
emailParents.push([element.user_id, element.user.name]);
}
});
//}
})
.then(async () => {
await delay(1000);
if (emailParents.length > 0) {
//loading animation
// //document.querySelector(".gameLoader.pacMan").style.setProperty("--playState", "paused");
//loading animation
emailParents.forEach(parent => {
console.table(parent);
document.querySelectorAll(
".ac-token-list"
)[1].innerHTML += `<li class="ac-token" style="background-color:wheat;">
${parent[1]}
<a href="#" class="ac-token-remove-btn">
<i class="icon-x icon-messageRecipient--cancel"></i>
<span class="screenreader-only">
Remove recipient ${parent[1]}
</span>
</a>
<input type="hidden" name="recipients[]" value="${parent[0]}">
</li>`;
});
} else {
popUpError(
`
( ⊙0⊙) - I am not seeing an observers associated. Sorry. - (⊙▂⊙ )`
);
}
});
} else {
popUpError(
`
(×_×;-In order to add parents you must select a course first. Re-fresh to try again. -(o。o;)`
);
}
},
{ once: true }
);
}
});
//---------------------------------------------------------------------------
const popUpError = msgTxt => {
const msgHolder = document.querySelector("#flash_message_holder");
const timeout = 9000;
const daMsg = (msgHolder.innerHTML = `<div role="alert" class="ic-flash-static ic-flash-error popUp">
<div class="ic-flash__icon" aria-hidden="true">
<i class="icon-warning"></i>
</div>
<h1>${msgTxt}</h1>
</div>`);
daMsg;
setTimeout(() => {
let popUp = document.querySelector(".popUp");
popUp.parentNode.removeChild(popUp);
}, timeout);
};
//---------------------------------------------------------------------------
}
addObserverEmailButton();

Edit: 17-12-19: modified script to pull in a 100 parents if there, as well as made a few more async calls for better error handling and lengthy forEach if >50 parents(merged classes & lectures)

Edit: 10-02-20: modified script to pull in all paginated pages if there is pagination now working in large courses like grade counselors w/400+ students and 1000+ parents