Adding anchors to our Shiny button observer

In my previous post, I demonstrated a JavaScript / jQuery event handler that waits for button clicks and sends the id of the button that was clicked to a single observeEvent() in Shiny. In this post I’m going to improve that code while also extending it to include anchor tags (<a id="xxx">). Anchor tags are typically used to create links and include an href attribute that holds the URL of the linked document.

This revised event handler doesn’t modify anchors with hrefs and URLs. But HTML5 also allows anchor tags to be used in menus without an href attribute, which can be handy in the Shiny context. Here’s an example of some menu code embedded in an unordered list and using anchor tags inside the list items:

<ul class="nav nav-bordered mb-0 clearfix">
   <li class="nav-item">
      <a id="um1_1" class="nav-link active">Active Users</a>
   </li>
   <li class="nav-item">
      <a id="um1_2" class="nav-link">Deleted Users</a>
   </li>
   <li class="nav-item">
      <a id="um1_3" class="nav-link">Clashes</a>
   </li>
</ul>

The code (with the help of Bootstrap 4) produces a menu that looks like this.

The real code also includes Aria attributes for assistive technology (eg, screen readers), but I’ve removed that here to simplify things.

Here’s the revised JavaScript / jQuery event handler (NOTE: this is an updated version of what originally appeared in this post, details below:)

// supports both button and anchor clicks 
$(document).on('click', 'button,a', function(e) {
   if(e.target.id.length == 0) { return } // No ID, not ours.
   if(e.target.nodeName == "A" &&           
     typeof e.target.href != "undefined" && // If it's a link  
     e.target.href.length > 0) {              // with an href
        return; }                      // don't mess with it.
Shiny.onInputChange("js.omclick", e.target.id + "_" + 
   (new Date()).getTime());
});

Where before the first line said 'button', now it says 'button,a'.  This is how you have one jQuery on() function watch for clicks on two different kinds of elements. I originally thought this was supposed to be in array syntax (['button','a']), but that was returning events on elements like <select…>. This sent me to Google where I found this Stack Overflow post explaining the right way to declare two elements to a jQuery on() function.

In the second line, I’m checking to see if the button or anchor has an ID. I’ve started using the Quill Editor, which has buttons for bold, italic, and so on. These buttons don’t have IDs, so this is an easy way to prevent my own code from being buzzed when they are clicked.

The next block of code examines anchors to see if they include a real href attribute and, in that case, to let the browser handle the page change. On the other hand, when one of my href-less anchors is clicked, Shiny receives the id of the menu item.

There are a couple of other changes from last time. I’ve removed the e.stopPropagtion() line, so far with no ill effects, after running across this post on The Dangers of Stopping Event Propagation.

Finally, rather than counting how many times the handler has been called and using that to force Shiny to see a changed input, this function uses the time in milliseconds since January 1, 1970. This changes quickly enough to provide a reliable method for making each click new to Shiny.

In the example menu code near the top of this post, note the ids are of the form uml_n, where “uml” tells our observer what menu was clicked and the attached number specifies the exact item in that menu. To handle the menu, you just add something like “uml” = {…} (where … represents your code) to the switch statement in the observeEvent() function shown at the end of the previous post.

Typically at that point my code changes what’s displayed on a Shiny output$ to something different. For example the sample menu code shown earlier, in real life, changes the display from active records to deleted records.

NOTE: There’s an update to this event handler that causes it to ignore clicks on items that include the class disabled.

One thought on “Adding anchors to our Shiny button observer”

  1. this is great! been looking for this for years.
    might be worth adding, for non-experts like myself, that this and your previous script could be modified to respond only to elements with a certain class like myclass, just by replacing “button” with “.myclass”.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.