Additional Element IDs

Idea created by cesbrandt on Apr 6, 2018
    Open for Voting
    Score6
    • Brian Reid
    • Rob Ditto
    • Andrea G Schmidt
    • Daniel Grobani
    • Ron Marx
    • Gregory Beyrer

    I know, the title can be confusing for no web developers, so please bear with me as I explain.

     

    What am I referring to?

    I am referring to the HTML code that builds the various pages of Canvas. Each item on the page is at least one element, and, in many cases, groups of items will be under an extra element. These elements rely on class and/or ID names to apply the varied appearances and functionality of elements throughout the LMS. Without them, the LMS would not look very good.

     

    Requested Idea

    Alright, so with the title and explanation of what it refers to, the idea should be fairly self-explanatory: add more IDs to item-level elements. It will not hurt currently functionality in any way, and could even open up the possibilities to simplify some of the Canvas JavaScript/CSS element identification.

     

    Now, let me explain why, and this is going to be long and go into detail about why this is a needed change.

     

    Situation

    Currently, Canvas is riddled with classes and IDs that either seem like they have a naming convention of their own or are randomly generated. Take this link, pulled from the main navigation menu under the "Admin" panel, for example:

    <li class="_1dyDTaI undefined _1jLfonx _9PzDC58 _3ZmjBWk" data-reactid=".2.0.1.0.2.$all/=1$all"><span class="_3VIJ7BJ" data-reactid=".2.0.1.0.2.$all/=1$all.0"><a class="_5tkGIfX undefined" href="/accounts" data-reactid=".2.0.1.0.2.$all/=1$all.0.0">All Accounts</a></span></li>

     

    First, what is with the "undefined" classes? Second, what is with those other classes on the LI, SPAN, and A elements?

     

    These classes need to be mapped before we, as administrators and designers, can manipulate them. Is that particularly difficult? No, it is actually the same process, just with as much documentation as code.

     

    Situation Part 2

    Thought that was it, eh? Nah, that was just a teaser.

     

    The bigger problem is not the odd naming; it is the lack of unique identifiers on items that may have importance to be manipulated. Take the tertiary navigation of the course Settings page, for example:

    <aside id="right-side" role="complementary">
         <div>
              <a class="Button Button--link Button--link--has-divider Button--course-settings student_view_button" rel="nofollow" data-method="post" href="/courses/104/student_view">
                   <i class="icon-student-view"></i>Student View
              </a>
              <a class=" Button Button--link Button--link--has-divider Button--course-settings" href="/courses/104/statistics">
                   <i class="icon-stats"></i>Course Statistics
              </a>
              <a class="Button Button--link Button--link--has-divider Button--course-settings" id="course_calendar_link" href="https://ecpi.instructure.com/calendar?include_contexts=course_104">
                   <i class="icon-calendar-day"></i>Course Calendar
              </a>
              <a class="Button Button--link Button--link--has-divider Button--course-settings" href="/courses/104/confirm_action?event=conclude">
                   <i class="icon-lock"></i>Conclude this Course
              </a>
              <a class="Button Button--link Button--link--has-divider Button--course-settings" href="/courses/104/confirm_action?event=delete">
                   <i class="icon-trash"></i>Delete this Course
              </a>
              <a class="Button Button--link Button--link--has-divider Button--course-settings copy_course_link" href="/courses/104/copy">
                   <i class="icon-copy-course"></i>Copy this Course
              </a>
              <a class="Button Button--link Button--link--has-divider Button--course-settings import_content" href="/courses/104/content_migrations">
                   <i class="icon-upload"></i>Import Course Content
              </a>
              <a class="Button Button--link Button--link--has-divider Button--course-settings" href="/courses/104/content_exports">
                   <i class="icon-download"></i>Export Course Content
              </a>
              <a class=" Button Button--link Button--link--has-divider Button--course-settings reset_course_content_button" href="/courses/104/reset">
                   <i class="icon-reset"></i>Reset Course Content
              </a>
              <div style="display:none;" id="reset_course_content_dialog">
                   <p>Resetting course content will permanently delete all associated assignments, discussions, quizzes, modules, rubrics, pages, files, learning outcomes, question banks, collaborations, conferences, or any other content. This action is irreversible, and the data <em>cannot</em> be recovered. Are you sure you wish to continue?</p>
                   <form class="edit_course" id="edit_course_104" action="/courses/104/reset" accept-charset="UTF-8" method="post">
                        <input name="utf8" type="hidden" value="āœ“">
                        <input type="hidden" name="authenticity_token" value="Jdi3txUoqhK4SiZj1y/TuxpcDVLbVuFobotGvpN7t7xOtt/BUmrfJvo5XhK2baKDUh1rI7kw110JvCru1RSB7g==">
                        <div class="button-container">
                             <button type="button" class="btn cancel_button">
                                  Cancel
                             </button>
                             <button type="submit" class="btn btn-danger submit_button">
                                  Reset Course Content
                             </button>
                        </div>
                   </form>
              </div>
              <a class="Button Button--link Button--link--has-divider Button--course-settings validator_link" href="/courses/104/link_validator">
                   <i class="icon-link"></i>Validate Links in Content
              </a>
              <table class="summary">
                   <caption>
                        <h3>Current Users</h3>
                   </caption>
                   <tbody>
                        <tr>
                             <td>Students:</td>
                             <td>None</td>
                        </tr>
                        <tr>
                             <td>Teachers:</td>
                             <td>None</td>
                        </tr>
                        <tr>
                             <td>Faculty:</td>
                             <td>None</td>
                        </tr>
                        <tr>
                             <td>TAs:</td>
                             <td>None</td>
                        </tr>
                        <tr>
                             <td>Non-Editing Teacher:</td>
                             <td>None</td>
                        </tr>
                        <tr>
                             <td>Designers:</td>
                             <td>None</td>
                        </tr>
                        <tr>
                             <td>Observers:</td>
                             <td>None</td>
                        </tr>
                   </tbody>
              </table>
         </div>
    </aside>

    Of the ten buttons on this navigation, only six have any sort of unique identifiers with only one of them being an actual unique identifier (the ID for "Course Calendar").

     

    Due to the lack of unique identifiers, if we wanted to manipulate the "Course Statistics", "Conclude this Course", "Delete this Course", or "Export Course Content" buttons, we would need to do so by identifying them with a roundabout process (i.e., matching their content).

     

    "Why would we want to manipulate these buttons?" you ask? Well, without the ability to control what roles/users have the ability to perform certain actions (i.e., deleting a course), we need to find alternate ways to remove them. However, since we do not want to remove these functions for everyone, we cannot simply blanket it with CSS. Even if we could, there is still the issue of being able to identify the elements to be hidden/removed. Take the "Export Course Content" button from above, for example. We could hide it with CSS by using an attribute selector to match the HREF of the link:

    [href$="/content_exports"] {
         display: none !important;
    }

    Important Note: The !important really is important because we've found that some of these buttons are manipulated by JavaScript and the display value overwritten. Rather than identify which ones are affected, it is quicker to just blanket it.

     

    Back to the example. This works for hiding the button from the course Settings, but that same button appears on the course Homepage, but it is not hidden there. Why is it not hidden, too? Because it is not the same button:

    <button class="btn button-sidebar-wide" type="button"><i class="icon-export-content"></i> Export Course Content</button>

     

    On this second one, we can uniquely identify the icon on the button, but not the button itself. With CSS, you cannot manipulate a parent element by identifying a child element. Furthermore, if you want to manipulate it with custom JavaScript, you need to make your code recursive because Canvas JavaScript generates this button on the course Homepage. This means that your JavaScript may need to execute a few times to ensure it catches the button that was generated not by the page being loaded but by JavaScript execution.

     

    Here is how we are removing these buttons:

    var i = 0;
    var interval = setInterval(function() {
         i++;
         var removeList = /(Conclude|Permanently Delete) this Course|Reset Course Content/;
         $('.Button').each(function() {
              if($(this).text().match(removeList)) {
                   $(this).remove();
              }
         });
         $('.btn').each(function() {
              if($(this).text().match(removeList)) {
                   $(this).remove();
              }
         });
         if(i == 30) {
              clearInterval(interval);
         }
    }, 500);

     

    Simply put, we use JavaScript to detect elements with either the "Button" or "btn" class (when it was written, we found inconsistency across Canvas in which was used) and whose content matches the regular expression. This are then removed. After 30 executions at 1/2 second intervals, it stops. Checks could be added to stop execution upon completion, but that requires extensive hard coding of what buttons appear on what pages that really just is not worth it. This code, of course, is only executed for those users who do not meet our requirements for retaining the buttons. Due to the variations in that, this is just the simplified version.

     

    Here is the kicker; Canvas begins the execution of custom JavaScript last. This makes sense with regard to wanting to ensure the standard Canvas content is loaded properly. Theoretically, this should also prevent the issue I mentioned above regarding having content loaded after the custom JavaScript execution. It does not. The reason? Canvas loads many components of the LMS after the page is fully loaded, as part of XMLHttpRequests. This means the custom JavaScript can start its execution before everything is actually loaded.

     

    The second problem with being the last to execute is the time delay. For those users with a slow connection or computer, the execution could be delayed, granting them access to these buttons we would otherwise want hidden from them.

     

    With unique identifiers that were consistently applied, CSS could be used to hide these buttons from the beginning of the page load and JavaScript to reveal them to those who meet the requirements to use them.

     

    Disclaimer

    I am well aware for the development underway for more granular permission controls, and I honestly believe that is exactly what is needed, instead of individual institutions building their own messy solutions to this issue. The problem is, this has been 3 years in the making. I want to see it happen, and look forward to when it does, but adding these unique IDs is a fairly minor change that would significantly improve the feasibility of institutions being able to manage access to these features until it is completed.