Add observers to email
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
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.
- 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
- This does not work on the grade book.-I am no longer concerned w/this deemed not that important by staff
- 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.
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