To Our Amazing Educators Everywhere,
Happy Teacher Appreciation Week!
Found this content helpful? Log in or sign up to leave a like!
Hello all,
I'm looking for help with some custom JavaScript on the Admin/Courses page. We have a large number of courses and we would like to add a direct link to "People" and "Files" as shown in the graphic below to streamline our workflow.
Any thoughts on how I could implement this in our custom JS theme file?
Thanks
v/r
MJH
Solved! Go to Solution.
I'm adding both contributions to this reply, so the answer is easy to find at the top.
Both provide the same solution and the user should be able to run either one.
User Script by @James
James' version of the solution is up on the Canvancement GitHub site: Admin Course Links
It is a user script that should work without modification as a global JavaScript file.
Global - Canvas Theme Editor by carroll-ccsd
My version is also available on Github: ccsd-canvas/admin-course-links
It is intended to be used in the Canvas Theme Editor, along with any other global changes you make there.
Good times!
@canvas18 ,
I'd recommend that you turn on the new "Account Course and User Search" feature option. It lets you add users directly from the account page and a couple other features that might meet your needs. It has all the buttons you are looking for there except for files. And since that'll be the new default screen eventually, I'd recommend adding customizations to it.
Jeremy,
Thanks! I was unaware of this new feature option. Unfortunately, we are on self-hosted Canvas and about 6 months behind. Eventually we should be able to get this (perhaps by Christmas). Until then, we'd really like to mod the Courses page.
Mike,
This should work for you. To keep it simple I didn't resort the list of links. I also added Analytics... you can add/remove links pretty easily.
// edit: the code that was here no longer works, there are now two options available
// see #comment-113056
Robert,
This is perfect! Already tested and implemented. My LMS and student services folks are incredibly pleased.
This is why I love this community.
v/r
MJH
Mike Hower wrote:
...This is why I love this community...
Yep!
TIL: V/R = Very Respectfully
This is really cool. I can't wait to test this in our system.
This has been an amazing tweak to the system for the last 3 months, but we've now moved to the new Course and People search page. Our folks who grew used to the shortcuts from the Admin...Courses page now lament its loss.
I've looked at the code to see if I could implement a change in theme-level JS, but it now appears to be built with React (which makes my head hurt even more).
Would anybody have an idea how to recode the above to do something like the below? (While I like the icon mockup on line 1, my student services team would prefer the text-based mockups on line 2 so that they don't have to change their procedures).
Any thoughts?
You're not the only one, React makes my head hurt too.
I think I can do this. I will probably try row of text links though. The unmodified UI seems to provide more space there without obstructing the layout.
I'll try and play with this later this week and reply with the results.
Still exploring this. I have a bit of it working, however, because React and multiple events and triggers that fetch new data it's not as simple. Not only do I have to check for mutations like James mentions, but I also have to look for more than just clicks in the pagination. All of the filters at the top.
@James ,
I'm probably getting ahead of myself here... but I think you enjoy a good debate.
Does Canvas suggest building entire LTI's that completely replace the UI features where it's apparent they're also NOT the users of Canvas? Admin Tray - Sub Account Menu
The problem with this growing question, and Canvas being open to development is that, there are pain points for several institutions where the user interface and experience is neglected. I am also empathetic that Canvas should spend more time developing :smileycool: features that solve problems, instead of collecting and tweaking the UI, and fully aware that you are aware of all of this... :smileygrin:
It also gives us, the institution, the ability to prototype things in the community to better understand what feature requests we want Canvas to spend time on vs. what the Community can collaboratively support. Some of your Canvancements have probably seen more release cycles than some of the engineers at Canvas.
How many features would we trade?
How much time did you spend, that has continuously worked for several terms, that would have taken from Canvas creating...x?
How many institutions would suffer because they can't support LTI hosting? a lot
How many institutions would develop a solution and never contribute it to the community? most
How many LTI's would we have to build and enable to allow a half a dozen UI tweaks?
How many instructors are you alone empowering with userscripts because even their institution can't/won't support tools that make things easier? 8 days ago - Major rewrite canvancement/grades at master · jamesjonesmath/canvancement · GitHub
How does Canvas continue supporting us in supporting our users as they move to React. Adding a Class or ID to an element isn't really enough, because the number of mutations involved that are consistently happening as more React components get added, means our JS has to juggle all that without interfering. This is where modifying the DOM gets unpredictable.
While I was working on this the other night, I found about 3-4 requests were happening in the UI that I wasn't deliberately triggering (unread_count, help_links on hover). I spent a few hours the other night trying to create a hook into XHR, so that along with mutation observers I could catch the API response URL (/\/accounts\/\d+\/courses\?/) of the information I was expecting to change. It's not kosher, Canvas would hate it, so I carry on, defining conditions for mutations.
How do we explore this with Canvas?
Too much here for me to address right now. I actually don't like debate as much as healthy discourse that leads somewhere productive. I don't see what we do as debate. Maybe I've got a skewed definition of "debate" where the two sides are opposed to each other?
As for Admin Tray - Sub Account Menu , do you find that cdn.rawgit.com updates the code when you update it in GitHub? I thought I had tried that originally with QuizWiz and then read that it was a snapshot of the script the way it was the first time it was called and never updated after that. So I had to go find another place, probably less reliable but the code was obviously forked from rawgit, that would update the scripts more regularly.
As for your question about it, and I'm reading a lot into this. I think that if you're going to do major hacks to Canvas, they would like you to self-host and do whatever you want to do or contribute back to the source code, although there's no guarantee that what you do would make it in since it may not line up with their plans. They are wanting to move to InstUI and React and not make everything based on classes that people can modify. There are reasons for that and don't imagine the decision to switch was as simple as "Hey, I heard about ReactJS last night, how about we switch our entire codebase over to use it? Ok, sounds good to me. Awesome, lets' do it!". But I do feel that there's a happy middle ground somewhere that hasn't been reached yet, but I'm not a React developer. I did have the basics of the virtual DOM and what they were doing explained by a Canvas engineer in April 2017.
I take statements like "If there is any other way besides custom JavaScript to do it, I would go that way" as active discouragement. That was surrounded by "If we need to add more to the custom theme editor, let us know." There was a hinge of hope with "If you need custom classes, make your case." All of those are paraphrased, of course.
I think the reasoning is more of a (again, reading my own experiences into this) case of "We're not guaranteeing that we'll keep the classes or whatever else it is that you're hooking into, so there's a good chance it will break at some point. It would be better if you would look for a different way to start with." You know, the standard, "You're on your won if you do this in a non-standard, non-supported way." This is a change from when I started when the answer to all of the individual needs was custom JavaScript.
In other words, they're making the web UI more like the mobile apps where you can't do as much. Mobile apps are now written using React Native and many of the things in the Style Guide don't apply there. That causes frustration for those who use the Style Guide to style user content -- a purpose it was never intended for but many people discovered it worked. Canvas caught some flack from that, the big jQueryUI deprecation discussion you referred to, and so now they're out front telling people "What you're doing is not supported." The forward facing customer support people may not be so forthright with their statements, but the higher-ups are starting to say it.
Regarding the list of questions, I think you overestimate the usage of Canvancements. Or maybe I underestimate them?Canvas develops for the middle 60% of the users. Those on the very low end or very high end get frustrated. Unfortunately people like admins, instructional designers, and math/science teachers fall outside that middle 60%. People in the Community rant about how painful something is to do, but to Canvas (again trying to put my mind into theirs) they feel that you only need to design the course once and then you can copy and tweak it. That is, a lot of work that people waste with Canvas is doing things a less-efficient way or trying to hack Canvas to do something that it doesn't do -- and perhaps never will because it's bad design.
The problem with LTIs is that they are outside of Canvas, so tweaking the UI is not their role. But then how many of the tweaks that we make to the UI are designed for the vast majority of the users? How many of them are designed to take away functionality from people (e.g., let's remove the course reset button because one of my faculty accidentally clicked it). Many times the solution is education, but we're getting a lot of people who don't have that, or feel that they are trained in Canvas because they went through what their college offered three years ago. Come on folks, Canvas changes every three weeks.
When I looked at the course page, I didn't think it was quite as bad as you described. I did the first time, then it wasn't the second time, but if I look at it again, it may be. Basically, you can attach one mutation observer for the entire block and have it check for the conditions. You shouldn't need to look for button clicks or anything, just watch for when the content on the page changes -- make sure you get that subtree in there. Then go through and see if your information is there. There's a lot of should in there. Actually testing normally reveals more complicated things.
One of the things I had to decide when I was doing Canvancements is how much benefit is there to this? Is it a hack for one person? Will others be able to benefit? How much time is it going to take for me to do? Do I have that time available?
That last one has become a bigger issue for me. Our entire college staff is going through leadership training and I started mine last October. I had kind of checked out of work except for the teaching and now I'm back in. But that means lots of meetings and doing more stuff, so time for the fun stuff like solving a problem for the Community is harder to find than it was.
What I think would be a cool thing -- although we're 1700 miles apart -- would be to collaborate on a project where we could see each other's screens, work on the same code, talk about the problems we're having, get immediate feedback from the other person. You could say "Here's what I'm seeing" and I could say "Oh yeah, let's try this.". I'm not even sure how that would work technically, it just sounds like a really cool idea. Kind of like a virtual hack night. There's the logistics of getting a time set up where both people were available and productive (example, my productivity goes way down after teaching all day on Mon, Wed, and Fri). It kind of goes in spurts and I never know when I'm going to have a breakthrough until it starts to happen -- I haven't figured out how to schedule "Productive coding time from 3-4 on Thursday"
Anyway, I do know that I need to get back to preparing for classes. I was up until 2 this morning and was being productive, but then had to go to bed. It's now 11 hours later and I haven't gotten back to it yet.
I actually don't like debate as much as healthy discourse that leads somewhere productive.
No, I think you're correct. This is more fun too.
RE: RawGit, I had noticed that as well, and just tested to confirm. It is something I want to address in a future release.https://raw.githubusercontent.com/robert-carroll/ccsd-canvas/master/admintray-subaccmenu/admintray-s...
It was a quick fix to make it easily available, without signing up for some service.
I think that if you're going to do major hacks to Canvas...They are wanting to move to InstUI and React and not make everything based on classes that people can modify...
But I do feel that there's a happy middle ground somewhere that hasn't been reached yet, but I'm not a React developer.
I take statements like "If there is any other way besides custom JavaScript to do it, I would go that way" as active discouragement. That was surrounded by "If we need to add more to the custom theme editor, let us know." There was a hinge of hope with "If you need custom classes, make your case." All of those are paraphrased, of course.
I think the reasoning is more of a (again, reading my own experiences into this) case of "We're not guaranteeing that we'll keep the classes or whatever else it is that you're hooking into, so there's a good chance it will break at some point. It would be better if you would look for a different way to start with." You know, the standard, "You're on your won if you do this in a non-standard, non-supported way." This is a change from when I started when the answer to all of the individual needs was custom JavaScript.
Agreed here too. I remember when adding custom JS/CSS meant emailing it to the CSM.
I also don't want to be responsible for hindering Canvas' decision to develop and use new technologies to improve the product.
I think the middle ground would be having a discussion about what they would be willing to support or enable something that allows theme modification 'at your own risk'. Right now we are trying to build workarounds that are a little less predictable.
Regarding the list of questions, I think you overestimate the usage of Canvancements. Or maybe I underestimate them?
Maybe, maybe not. Do you consider the exponential effect of 1 institution enabling 1 Canvancement for all users? We have 2 in our JS, Dashboard and Rubric Sorting, and Adjust All Assignment Dates on One Page is pretty popular with the 40+ teachers in the room next to me. I'm not sure how to pull metrics on who has sorted their dashboard cards or rubrics, but we had 39,000 students (~1/10) and 10,000 employees (1/4) use Canvas in the past 12 months.
One of the things I had to decide when I was doing Canvancements is how much benefit is there to this? Is it a hack for one person? Will others be able to benefit? How much time is it going to take for me to do? Do I have that time available?
We have to make the same assessments, with two full time developers.
...Unfortunately people like admins, instructional designers, and math/science teachers fall outside that middle 60%...
...That causes frustration for those who use the Style Guide to style user content -- a purpose it was never intended for but many people discovered it worked...
Probably the best use of the Theme Editor would be allowing templating/styling/interactive components for content.
I'm starting to work with our instructors and designers to figure out how we can use HTML elements without huge injected libraries or external/expensive LTI tools. Several of us are enrolled in the Canvas Hacks Classroom now, so we can start looking at and getting some play time with HTML in Canvas, with the goal that if we get acquainted with HTML I can style the components globally, and instructors would only need to support the html... trying to make HTML more readable in Canvas... that's a hack. https://community.canvaslms.com/message/111843-re-canvashacks-classroom?commentID=111843#comment-111...
The problem with LTIs is that they are outside of Canvas, so tweaking the UI is not their role. But then how many of the tweaks that we make to the UI are designed for the vast majority of the users? How many of them are designed to take away functionality from people (e.g., let's remove the course reset button because one of my faculty accidentally clicked it). Many times the solution is education, but we're getting a lot of people who don't have that, or feel that they are trained in Canvas because they went through what their college offered three years ago. Come on folks, Canvas changes every three weeks.
Some modifications require less overhead, some schools have no technical staff or budget.
Create a Sandbox Course via Web Application/API?
There's a handful of solutions in that thread... how many people can support ruby, spin up a server to get this working in a day. Adding a button with JS is portable. Additionally, another situation where this could be solved in Admin Settings.
I agree, removing features is probably not the best solution, and education/training is helpful.
Then again, sometimes, no amount of education will keep a teacher from running their own class room.
Keeping students out of the teacher role is a huge deal for FERPA, when the student has access to other students grades. Customizing Add People Dialog with Custom Javascript
For this, I don't even stop there. Using Canvas Data, I delete them nightly.
-- generate an enrollment import to delete students enrolled as teacher
SELECT
CAST(course_sis_source_id AS NVARCHAR(20)) AS course_id,
sis_user_id AS user_id,
CASE
WHEN enrollment_type = 'teacheraid' THEN 'ta'
WHEN enrollment_type = 'teacher' THEN 'teacher'
WHEN enrollment_type = 'designer' THEN 'designer'
END AS role,
'' AS section_id,
'deleted' AS status
FROM CANVASLMS.dbo.user_enrollment_type_vw uet
JOIN CANVASLMS.dbo.pseudonym_dim pd ON uet.user_id = pd.user_id AND pd.workflow_state = 'active'
JOIN CANVASLMS.dbo.enrollment_term_dim et ON uet.enrollment_term_id = et.canvas_id
WHERE
enrollment_type IN ('teacher', 'teacheraid', 'designer')
AND GETDATE() BETWEEN et.date_start AND et.date_end
AND ISNUMERIC(sis_user_id) = 1
AND enrollment_status = 'active'
Been at this one for awhile, and have been interrupted too many times to stay focused on all the points.
Last thoughts.
Our sandbox instance could make collaboration fun. Create a new fiddle - JSFiddle has a collab mode.
I'm down for figuring out virtual hack nights. Maybe just a decent chatroom, maybe audio? Google Hangouts? Slack? Discord?
I find my most productive coding time is coming into the office, getting coffee, putting on my headphones and being reminded it's time for lunch. On the other hand, if I start with email, community, meetings, assisting, or trouble shooting my day is gone before I opened the IDE. I try to give myself time for both. Some days I focus on 1 or the other, while others I can mix it up a bit when tasks are smaller. Starting a new school year isn't so hard with our code, we maybe adjust some term numbers, update the databases, purge unused courses. I also really enjoy hacking away into the middle of the night, or tinkering while watching TV.
Then again, sometimes, no amount of education will keep a teacher from running their own class room.
Have you ever seen where English readers tend to read the beginning and end of a word and fill in the middle? I don't know if it's the font or what, but in the original message, I originally read that as ruining and thought, "sad, but true."
Actually, was discussing this with my wife over dinner last week...
Which if I recall correctly, became the topic of discussion after we realized that some drivers don't ever look past the car in front of them, making negotiating difficult... bringing us to a comment about reading one word at a time, and then, most experienced readers scan forward (even unconsciously) getting context while they read.
Because I learned it here Research Reasons for Avoiding All Caps
From @DeletedUser 's #comment-108771
Today... You See Less Than You Think - WSJ
/totally off topic
Here is a link to another related feature idea that wants to add things to the course search page: https://community.canvaslms.com/ideas/11332-display-course-code-in-admin-course-search-results" modi.... I'm sharing because any solution may be similar.
There I shared some quick research I did after InstructureCon about what might be involved. It looks like attaching a Mutation Observer to the page to see when the content reloads is the first step. The second is not to rely on CSS classes, but to look at what column in the table it's in. Hopefully that doesn't vary based on whose looking at it like it does with the course roster (People) page.
At InstructureCon, one of the Canvas VP's I talked to actively discouraged using custom JavaScript and CSS to accomplish things like this and said it's going to happen more and more when they move more to React. His suggestion was to use the API or LTI if possible, and if you had a use case for the inclusion of classes, then make it.
Warnings and discouragement aside, I was an EMT once and know the benefit of bandage
...and clinical review, to see if we can make improvements.
Also admitting, James's warnings pushed me to try different things.
Think I learned a few new tricks, and want to make a few adjustments to some other project's mutations.
No classes, just available DOM selectors.
Attempts at preventing multiple executions and memory leaks.
It should execute once, when the DOM is updated and the new links haven't been added.
I'd run it on test first with the console open, let me know if you see any errors.
@James , anyone, feel free to review/critique.
[code redacted so it doesn't get used, keep reading]
A couple of things that jump out before I try it out.
After trying it out ...
After that, it mostly seemed to be a matter of coding and personal preference, so take this as suggestions. The first three items should probably be fixed.
I also noticed that there was a mix of statements ending with semicolons (other people's code) and one's not (probably your code). You may want to be consistent. I think best practice is to end statements with semicolons.
I would probably look for some optimizations using the mutation observer so that you don't have to scan through every single one of them looking for the right thing. The problem is getting the right selector in the absence of a good :not. I want all of the "tbody[data-automation="courses list"] tr td:nth-child(2) > a" that exist but are not followed by a "span.extra-buttons". If all of them had a span but without the .extra-buttons, it would be easier and doable. The best I've been able to come up with so far is something like this:
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) {
// we need to add some links
addLinks($('tbody[data-automation="courses list"] tr td:nth-child(2)'));
}
That code block is the entire mutation observer, you don't need to iterate through each mutation. Timing wise, it didn't seem to make a huge difference. I did some logging and it took 0.017 seconds your way and 0.021 seconds my way to do the mutation that added the content to the page and the next 16 mutations after that. A little hit to performance, but it seems like it's easier to understand what's going on. Probably more resilient if Canvas changes something.
By the way, the mutations happen every time you mouse over the page, so good job in finding the one that sparked everything.
You're also using jQuery for just a few things. If I were doing this, I would go those last few steps to remove it and make it pure JavaScript.
I also threw it into a user script so that admins can use it without having to install it in their global JavaScript.
I would probably make the font smaller and perhaps gray on the items you add so they are more easily distinguished from the course title.
I would also throw in a configuration option so that people could decide which items they wanted to add. I know they can edit the code, I've just tried to make my user scripts as easy as possible for the non-techy person to use.
Thanks for the comments, a little sloppy on my part, focusing only on the mutations.
Tried to tackle all of those, [was a git-gist, deprecated]
[ ]Having a problem with the regex your suggesting, without trying a different format, adding the suggested $ isn't working for me. Thanks for catching that, I saw that if I logged something on another page it executed, while the conditions weren't doing anything to catch the error.
Added config options...
Capturing the right mutation was originally why I tried to create a hook into the XHR, but it was an anti-pattern. Fun exercise though. I found that it seems those mutation properties always existed, so 'undefined' shouldn't be a problem to catch them. Happy I found them. It appears your knowledge/combination of Math and DOM selectors is complimentary. Yours is more elegant. I will try this approach in the future.
For semicolons, I usually only add the ones that cause problems for minification, I didn't test minified. I generally don't touch other peoples code. Thoughts?
Getting passed jQuery is an arduous task for DRY coding. I've been playing with some vanilla wrappers etc to provide a small library of quick access to selectors and functions. Continuing to use jQuery while working out some of these issues, a little bit lazy a little bit tackle later.
Do you find that with user scripts you end up doing more end user support?
Thanks Again!
I'm really close to putting together my suggestions in a user script. I'll hold off looking at your changes until I'm done and then it will be interested to see how close they ended up. Mine might have a race condition, so I'll have to check for that.
Alright, I'm done with converting it to a user script. I tried to duplicate your result without duplicating your code -- other than borrowing the selector that you used. I still haven't taken a serious look at your revised script.
Notes:
Responses to your questions:
I want to share the code, but I don't want to release it on GitHub if you're willing to look at it and make comments. I haven't figured out how to do development branching on GitHub, so everything goes into the Master branch, which makes me think that people might think it's ready for use.
Besides, I need to look and see what you added to yours and see if there are any great ideas I want to include. Ultimately, we need to figure out what to do with it. It's really your baby, you did the hard work, but I think we should make a user script version available for people.
For now, I'll put it here and move on to other projects (filtering the Canvas Commons search data). I'm not getting much done for my classes this weekend, but I'm getting some code written, which is a lot more fun.
Note: The code that was here has been removed since there is now an updated and ready to use version. I will put an announcement in the Community when I can. For now, the code is posted on the Canvancement GitHub site: Admin Course Links
This looks good. I'm going have to review later, or tomorrow morning, company over for tacos++
I'm not worried about ownership, more, 'can we figure out the best way'.
This is fun.
Cheers!
First things first
Questions
I added //draft to my gist after your comment, to make sure no one uses it in production yet. I put up the gist, because it took 24 hours before my [previous] reply was moderated and you saw it. Couldn't handle another 24 hours of anticipation. I don't have magic posting permissions. :smileygrin:
I really really like yours, except for the coded CSS/style, because I'd probably push a mod like this into global. Makes complete sense for a user script. Don't think this particular one applies to us, no was has complained about this UI update yet.
Race Condition
OMG! After company left, I grabbed my laptop and went through your notes and started tinkering with your code to see about making it smaller for themes. Also just absorbing the different solutions for a user script... CSS.
Eventually went to bed... and about 45 minutes later couldn't shake the race condition.
I was wondering if you were expecting the race condition from the XHR, the mutation or your own functions? I eventually started trying to identify the issue with some console.log() placement. What I found makes me wonder what's going on here in mutations. Maybe you know, maybe we need to look it up, maybe it's unexpected. The reason, in my first submission, for the mutation.target conditions to prevent multiple executions was because I found that that while we have 15 rows, I was getting 30 mutations with tbody.tr and each had 15 childNodes. Maybe I was unaware of a race condition, but memory leak and multiple executions are in my wheelhouse.
Going back to my code, and the for loop in the mutation observer, before you provided a better solution, which fixes it for my code. Now, I find that I have a similar experience with your code, but only after making a new call. You aren't using a callback, so checking the mutations is harder without deconstructing it. Maybe you know what you saw writing it?
Now, I feel wierd copying your code/modified back to you. Just wondering if you add the console.log() to line 46, and then click a Pagination link, if you see what I see.
vscode - copy/paste formatting included :smileygrin:
I have some school things to tackle today, but had to get this out. DW is off to a show tonight, so I have a free evening and I plan to study this a bit more. Also, if you want to try JSFiddle/Collaborate mode.
I prefix a lot of our internal code with ccsd or rc, but have felt weird signing code for the community. I guess I should get over that.
Yes, I do because it's less likely to clash with something that they may be using. I didn't want to preface everything with 'canvancement_', so 'jj_' seemed to work. It has nothing to do with ownership, it was more of a case of making sure I didn't pick something that was already used or might be used at some point. I guess we could go the React way and come up with class names like _w587Yz
and few less semicolons and var keywords.
Since my code was being reviewed, I decided to list the var's near the top of the function and save space by putting a comma between them. I almost never do that, even if I need a var declared outside a loop to avoid the warnings about declaring a variable within a loop (the scope with var is to the whole enclosure, but const and let are scoped to the block).
I ❤️ minifcation
What does a fallen-over ice cream cone mean?
I really really like yours, except for the coded CSS/style, because I'd probably push a mod like this into global. Makes complete sense for a user script. Don't think this particular one applies to us, no was has complained about this UI update yet.
Much of what I do is for other people and not something I'll use myself. It just feels good.
This was the first time I've tried, scratch that, managed to include CSS without reverting to a // @ grant statement in the header. I thought I'd try to see what it would take. I originally started to code it onto every element that needed it, then decided that it would be more extensible and allow for external styling if I did it the way I did.
I was wondering if you were expecting the race condition from the XHR, the mutation or your own functions?
I don't know that I'm expecting a race condition. That's the thing. I don't know if I need to be worried about it or not. I'm not a JavaScript programmer, or at least not formally educated in the ways. It just seemed that my script would get called a lot and I didn't want it to try and run while it was already running.
The reason I don't think it matters is because I saw a really good video on JavaScript that explained how it is single threaded and when you have a function, it gets added to a stack waiting it's time to run, but then once it runs, it doesn't leave that. Even when you have promises, they get added and when the promises resolve, they have to wait their turn in the stack before they finish. Of course, that video might have said that there was just a single loop handling the event thread. I found the video, here's the link: Philip Roberts: What the heck is the event loop anyway? | JSConf EU
So, I don't really think that there will be a race condition. However, I do think that my function will get called many times between the time that the mutations start happening and they're done -- it's going to called 20 times or so (I don't know exactly). The isRunning doesn't really keep it from doing the check, unless it's currently inside the block doing something, which it shouldn't be because the block is a single function.
Except .. that the buildLinks() function is a different function and so I don't know if that means a different thread, which might lead to a race condition.
The line 46 must be your line 46, because it doesn't make sense in my original code. But that doesn't even seem to be the place to add the console.log(). It seems like it should go at line 52 instead.
if (isRunning) {
console.log('Should have bought a squirrel'); // Add it here
return;
}
Putting it where you put it would flag as a race when it was already running. I was more interested in preventing the race, so if it was already running and it got called again, then I had a race condition. Where you have it would make it run with every mutation observer check UNLESS there was a race condition, but you don't know how many mutations there are to know how many times it should be called to see if one was missing. By putting it in the if() block, then you don't want it to appear at all.
With mine, I don't get anything logged, but that's the problem with race conditions, it's very timing based, so reloading the page three times and seeing nothing doesn't mean much.
With yours, I see this:
But even if JavaScript is single threaded today, should we count on it always being that way? Web Workers introduced some kind of multithreading.
What I would love to do is say "Once you run this, don't run it again for 250 ms", but that gets into timeouts and what if someone clicked and got a new load within that 250 ms? I mean, the initial load for me is slow, so it's very conceivable I could have typed something in the search box before it was finished loading and that comes back really quickly after the first load. I also really, really, really don't like using setTimeout() --- it's very machine specific and almost always the wrong way to do something.
Canvas uses them in their code, by the way.
They also do things like I did with checking for the existence of any items that should be marked up but haven't been yet. I don't see them running it more than once, but they've also announced they're going to deprecate the .enhanceable_content class where they do that.
What does a fallen-over ice cream cone mean?
Probably, that someone is very unhappy.
When I first looked up race condition for javascript last night I found this, and then read the comments.
No, there are no race conditions in JavaScript
I will have to watch that video. Thanks.
Whenever I try a for ... in without the hasOwnProperty(), my editor gives me a warning
What editor and browser do you use?
My 'line 46' was off, because I had removed your comments to work in the console.
Agreed about setTimeout.
Because this is Canvas, and our ability to modify it is a blessing, I'm always trying not be this, Leeroy Jenkins HD (High Quality) - YouTube
So, after reading the comments on the article, I think I don't have to worry about checking to see if it is running and avoid running it again. Unless something else (not JavaScript) changes the DOM at the same time. Or even then?
That's how I understood it.
Good news, btw, I learned that we don't have to omit the trailing comma in JavaScript. At least not on objects since ECMAScript 5. I think I was confusing it with JSON or maybe the documentation I saw that excluded it was for an earlier version of JS/ES. Trailing commas - JavaScript | MDN . There is pretty universal support for it in the browsers. It was always awkward to leave off the last comma in something you expected people would be commenting out.
I finally got around to looking at your draft code rewrite.
I do like that you used an array for the menu items and determined the name from the path. I couldn't figure out how to get it to say "People" out of "users" and at the time didn't know that was open to debate. The object thing I did was a lot of overkill for one thing, kind of like the Syllabus was for you. I tried, but quickly abandoned, array of objects [ { users : 'People'} ] as just being plain ugly and not maintainable.
Your code has the benefit of everything being wrapped in a single function, which you pass to the onPage() thing. I don't do that because mine is already wrapped in the iife enclosure that keeps the variables local. But if you were using onPage() for several different functions, then it makes it nice to just have a sequence of onPage() calls.
We often miss out on the beauty of the system when we look at isolated parts. In other words, a framework looks like overkill and a lot of wasted code if you just consider the task at hand. But if you consider all of the things that you need to do in other places, all of a sudden it seems only logical to reuse the code. Maybe that's the DRY programming style you were talking about. I'm not a programmer by training and the last formal programming class I had was data structures using Pascal, so I certainly don't know the best way to do things. The only thing dry about my programming is it's humor.
I find myself rewriting code that I've written before, figuring I might find a better way to write it the second or third time as I learn more. But if I was doing this day in and day out, I wouldn't have that luxury, my boss would want more out of me.
The Syllabus hack caught my eye. When I looked at it closer, I saw you were using for ... in. There's a warning about using for ... in with arrays on MDN's site, which makes it authoritative in my book as a guy who doesn't know much about JavaScript.
A for...in loop iterates over the properties of an object in an arbitrary order
a little later ...
Note: for...in should not be used to iterate over an Array where the index order is important.
In the case where you create your own constant object, you're probably okay, but I found that I whenever I used for ... in, I ended up using hasOwnProperty() or I use Object.keys() to and then operate as an array.
Whenever I try a for ... in without the hasOwnProperty(), my editor gives me a warning
The body of a for in should be wrapped in an if statement to filter unwanted properties from the prototype
Basically, the experiences have taught me to not use for ... in very often.
Then I got to the part about the Syllabus hack. It seemed not as extensible as it could be to hard-code certain properties in there. What if there were other paths that we found, like pages/about (let's say an institution had a blueprint course with an about page that the instructor was supposed to go in and change and you wanted quick access to it). Then the hack doesn't work, it says 'Pages/about'.
Here's what I came up with. It takes the last piece (or the only piece) as split on a /, converts it to a string, and then does your capitalization function.
for (var i = 0; i < opts.length; i++) {
var parts = opts[i].split('/');
var title = parts.slice(-1).toString().replace(/^\w/, c => c.toUpperCase());
links.push('<a href="'+curl+'/'+opts[i]+'">'+title+'</a>');
}
#eee may be too light for gray on white. Not enough contrast there.
For the observer, I found I did not need to watch for attribute changes. Are you sure you need that? That adds extra mutations, especially if Canvas does opacity changes for fading. There was one time I say like 50 mutations as they faded something out.
Gotta say, I really miss not having an sprintf() function in JavaScript.
I recently saw a tweet along the lines of...
When the boss asks me how close I am to being finished, first line or last line I'm always 93%
(source: it's really hard to re-find things on twitter, even with google)
I don't think anything I write is ever complete, usually just done for now, often going back as I learn things.
DRY (Don't Repeat Yourself), often use of OOP/Classes, but also helps when things get larger than 1 script. For example, our JS directory for global modifications. Note, the util directory, these are used in many of the other files, but compiled and minified are 1 line, so we don't have to manage 1000+ lines of code. I'm not sure how others do it, some are probably just pasting scripts on top of each other.
It's also a reminder of what is and isn't supportable. SHEBENE, recently asked about putting a community hack in place in our Canvas. After review it, I said, that hack has more lines than our entire compiled set (this lands in dist-pretty). I told him we'll have to test and evaluate it, but that's a lot of code to support. Since it's also only necessary for a subset of users it would need to be hosted on AWS/S3, and included for the correct Roles. benefit > maintenance
What I did with the Syllabus, was definitely not extensible, it was a first try to give the user a config, without them having to code the HTML. Your example replacement looks good. The config at the top, is what I've been trying to do when bringing code to the Community. Aaron wrote the original ccsd-canvas/create-workroom.js code. Before adding it to Github I refactored it and placed the config at the top. Either way, the end user still probably has to fish out the URL paths they want to include and manually edit. We should focus on keeping them out of the code, until they want to. fruit at the bottom...sorry
For...in, I have either been using it so long and not to noticed the warning, didn't see it, or haven't experienced undesirable effects, or (d)
I have been trying use Object.keys() - JavaScript | MDN more. Here's something recognizable.
function buildLinks(base) {
var div = document.createElement('div');
div.classList.add(wrapperClass);
var ul = document.createElement('ul'), item, a;
Object.keys(linksToAdd).forEach(function(key) {
item = document.createElement('li'),
a = document.createElement('a')
a.href = base + '/' + key,
a.textContent = linksToAdd[key];
item.appendChild(a);
ul.appendChild(item);
});
div.append(ul);
return div;
}
Have you seen Template literals - JavaScript | MDN
not exactly sprintf(), but has some cool functionality. Template Literals | CSS-Tricks
var opts = {
'analytics' : 'Analytics',
'grades':'Grades',
'users':'Users',
'files':'Files',
};
links = Object.keys(opts).map((key) => `<a href="${key}">${opts[key]}</a>`).join(' | ')
console.log(links); // <a href="analytics">Analytics</a> | <a href="grades">Grades</a> | <a href="users">Users</a> | <a href="files">Files</a>
Object.keys(styles).forEach(function(key) {
sheet.insertRule(`.${wrapperClass} ${key} {${styles[key]}}`, sheet.cssRules.length);
});
I had seen the template literals, but forgot about them. I really need Eclipse to do a better job with ES6 support or I need to find a new IDE. I can't the formatter to tidy any code that uses arrow functions, yet that seems a much cleaner way of coding than what we did here. I think you used it once on your capitalization routine. I also like that const and let are scoped better and not hoisted.
I'm using Visual Studio Code, it is working very nicely for the various languages and has a nice extension marketplace.
Has anyone else noticed that a course search using the old URL format continues to display its results in the old course list format? I discovered this because for years I've used a Firefox keyword bookmark to search for courses, and it still works: https://samuelmerritt.instructure.com/accounts/99999/courses?course[name]=%s
I feel bad for ignoring your comment this long, but I honestly have no idea, because I'm rarely 'inside' Canvas as a user. However, it could make sense, shouldn't it be the same API endpoint?
I had not noticed it until you said something, but I checked it out and it's more than just displaying in the old format. If you go to /accounts/XXX/courses, you still get the old course search with a course search results for "" and no courses found.
Thanks for pointing that out. I'm not sure how long it will last, but for those of us who aren't yet fans of the long wait on the new one, the old one is still available.
That means that the old script that Mike was original asking for should still work, but only if you run it on the /admin/XXX/courses page.
carroll-ccsd, thanks, no problem! I was just offering this up in case anyone was interested.
@James , it took me a while to even realize the new search was active, because I almost always enter Canvas by opening a new browser tab and typing one of my keywords (cu for user, cc for course) and a search term. I'm one of those who are going to continue using the old search until I become a fan of the new one.
Well, it was nice while it lasted. Today that URL format is giving a page error.
Holy cow! I go away for a week and come back to 31 replies. It will take me time to wade thru all of this, but I must admit -- you folks are great!
On behalf of 30,000+ Air Force students world wide, thanks for taking your time to look at this.
I'm sure I will have tons of followup questions!
v/r
MJH
I think we're both still working out our final contributions, don't install yet. :smileygrin:
How many users does this solution help at your institution?
To participate in the Instructure Community, you need to sign up or log in:
Sign In