Utilizing external clicks with Lit-Elements in your project

Currently, I am working on developing a custom dropdown web component using LitElements. In the process of implementing a feature that closes the dropdown when clicking outside of it, I have encountered some unexpected behavior that is hindering my progress. Inside the async firstUpdated() function, I have set up an event listener as per the guidelines mentioned in their documentation at this link. While logging out dropdown works fine, the issue arises when I try to log out target, as it returns the entire root element

<custom-drop-down>...</custom-drop-down>
each time, which although somewhat correct, lacks specificity. Any suggestions or alternative approaches to handle outside click events are welcome.

As a reference, I am attempting to replicate something similar to what has been already created (though the source code is not visible) at this link:

async firstUpdated() {
  await new Promise((r) => setTimeout(r, 0));
  this.addEventListener('click', event => {
    const dropdown = <HTMLElement>this.shadowRoot?.getElementById('dropdown-select');
    const target = <HTMLElement>event.target;
    if (!dropdown?.contains(target)) {
      console.log('im outside');
    }
  });
}    


@customElement('custom-drop-down')
public render(): TemplateResult {
  return html`
    <div>
      <div id="dropdown-select" @action=${this.updateValue}>
        <div class="current-selection" @click=${this.toggleDropdown}>
          ${this.value}
        </div>
        <div class="dropdown-options" id="dropdown-options">
          ${this.dropdownItems(this.options)}
        </div>
      </div>
    </div>
  `;
}

Answer №1

The click event's target property is set to the topmost event target.

The highest element in the rendering order capable of being an event target is designated as the topmost event target.

Due to the encapsulation of its internal structure by shadow DOM, the event is retargeted to the host element. In this scenario, the click event's target is redirected to the custom-drop-down since it is the initial and uppermost element able to be a target.

If there is a necessity to target an element within the custom element, various approaches can be adopted:

  1. Incorporate an event listener inside the element itself.
  2. Utilize the <slot> element to populate the custom element with elements specified in the light DOM.
  3. Employ the composedPath on events bearing composed: true to ascertain the internal element from which the event originated through bubbling.

An illustrative example demonstrating the desired functionality is provided below. By clicking on the Custom Element, it switches between active and inactive states (akin to toggling a dropdown). Clicking elsewhere on the page deactivates the custom element (similar to concealing a dropdown upon losing focus).

// Defining the custom element type
class MySpan extends HTMLSpanElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
    
    const text = 'Custom Element.';
    const style = document.createElement('style');
    style.textContent = `
      span {
        padding: 5px;
        margin: 5px;
        background-color: gray;
        color: white;
      }
      
      .active {
        background-color: lightgreen;
        color: black;
      }      
    `;
    this.span = document.createElement('span');
    this.span.innerText = text;
    this.span.addEventListener('click', (event) => { 
      console.log('CUSTOM ELEMENT: Clicked inside custom element.'); 
      this.handleClick(event);
    });
    
    this.shadowRoot.append(style, this.span);
  }
  
  handleClick(event) {
    // If the event is initiated from the internal event listener,
    // the target will correspond to our span element, enabling us to toggle it.
    // If the event originates from outside the element, 
    // the target cannot be our span element, thus necessitating deactivation.
    if (event.target === this.span) {
      this.span.classList.toggle('active');
    }
    else {
      this.span.classList.remove('active');
    }
  }
}
customElements.define('my-span', MySpan, { extends: 'span' });

// Embedding an instance of the custom element
const div1 = document.querySelector('#target');
const mySpan = new MySpan();
div1.appendChild(mySpan);

// Adding an event listener to the page
function handleClick(event) {
  // If the click is not targeted at the custom element, notify it
  // about the event so that it can deactivate its span.
  if (event.target !== mySpan) {
    console.log(`PAGE: Clicked on ${ event.target }. Notifying custom element.`);
    mySpan.handleClick(event);
  }
}
document.body.addEventListener('click', (event) => handleClick(event));
p {
  padding: 5px;
  background-color: lightgray;
}
<p id="target">This is a paragraph.</p>
<p>This is a second paragraph.</p>
<p>This is a third paragraph.</p>
<p>This is a fourth paragraph.</p>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

When using AngularJS in conjunction with Karma-Jasmine, it is important to note that the functions verifyNoOutstandingExpectation() and verifyNoOutstandingRequest() may not

There is an unresolved HTTP request that needs to be flushed. When I use the following code afterEach(function(){ $httpBackend.verifyNoOutstandingExpectation(); $httpBackend.verifyNoOutstandingRequest(); }); The code functions correctly and I ...

When filling options within an optgroup in a selectbox, the data for each option may override one another

UPDATE: I made a change in my code: $('select[name=productSelect]').setOptions(["All products|ALL", "Products visible to all|VISIBLETOALL=1"]); I updated it to: $('select[name=productSelect]').prepend(["All products|ALL", "Product ...

Utilizing Selenium to inject javascript permanently or on every page refresh

In my selenium webdriver test using Python 3, I have implemented a semi-automated process. This means that some routines are automated while other tasks require user interaction with the browser, such as page refreshes or redirects. My challenge is to inj ...

IE9 presents a frustrating issue where the jQuery select box value is returning as

I've been battling a bug for over 2 hours now. My javascript code is supposed to generate a two-level select box for tasks and subtasks from JSON data. However, I recently discovered that this code doesn't function properly on IE9, and possibly ...

Top method for verifying email existence in ASP.NET database

In our current asp.net web application, we are facing some challenges with using the update panel on the user registration page to check for existing users. These issues include: 1- The update panel tends to slow down the process. 2- The focus is lost wh ...

Despite attempts to exclude them, types in node_modules continue to be compiled in TypeScript

During my attempt to compile my typescript source code, I've noticed that the compiler is also attempting to compile the types found within my node_modules directory. I am currently utilizing typescript version 2.6.1 and have my tsconfig file set up ...

How to extract a particular value from a JSON object using AJAX?

In my test.php file, I have implemented a code where ajax sends a request from index.php to this page. Within this page, I have created an array, converted it to JSON, and returned it as follows: <?php $arr = array( "status"=>200, "result"=>array ...

Is it possible to maintain component data in Angular while also incorporating dynamic components?

All the code you need can be found here: https://stackblitz.com/edit/angular-keep-alive-component?file=src/app/app.component.ts Is it possible to maintain the state of entered values when switching components? I am currently utilizing dynamic component r ...

Can javascript be used to swap out the folder in my URL?

I have been searching for solutions on how to change the language of my website using jQuery, but so far I have not found anything that works for me. Let's take my website as an example: www.domain.com I have separate folders for different languages. ...

Struggling to transfer information from JavaScript to Python Flask?

I am currently working on a basic configuration where I need to send a string to my Flask application through Ajax, but unfortunately, I am encountering Error code 400: Bad request. Here is the JavaScript code snippet: headers = { 'Content-type&a ...

Sort the inner array within an outer array

const dataArray = [ { id: 1, title: "stuffed chicken is tasty as anything", picture: "./img/chicken.jpg", tags: ["oooo", "tasty", "stuffed"] }, { id: 2, title: "noodles with shrimp and salad", ...

Creating a task management system using HTML, CSS, and JavaScript that utilizes local

I have been extensively researching how to create a to-do list using JavaScript with local storage, but unfortunately, I have not been able to find exactly what I am looking for. Is there a way for me to set up a to-do list and input data before actually ...

Using Angular's ngBlur directive on multiple input fields

My current task involves capturing and saving all content on a webpage after it has been edited. For example, when a user clicks into an input field, enters data, and then clicks elsewhere, I would like to trigger a function to collect all the model data a ...

What is the method for retrieving the active element with Waypoint?

Currently, I am implementing Waypoint (version 7.3.2) in my React project using React version 16. My goal is to create a scrollable list of items where each item fades out as it reaches the top of the container div. My main inquiry is how can I obtain a re ...

What could be causing passport.authenticate to not be triggered?

After multiple attempts to solve my first question, I am still unable to find the answer. Maybe it's due to being a newbie mistake, but I've exhausted all my efforts. This is how I created the new local strategy for Passport: passport.use(new ...

Include the aria-labelledby attribute in the jQuery Datatable Pagination

Check out the HTML output created by the Jquery Datatable plug-in for pagination(https://datatables.net/plug-ins/pagination/full_numbers_no_ellipses): <div class="dataTables_paginate paging_full_numbers" id="my-table_paginate" aria-l ...

What is the best way to establish a two-way connection between two arrays?

Within my application, there is an ItemsService responsible for fetching Items from the server and storing them as JSON objects in its cache. These Items may be displayed in various formats such as tables, graphs, or charts. For instance, when setting up ...

asp.net website fully compressed using GZip

I am currently developing a Single Page Application (SPA) and I have encountered an issue with the size of my JavaScript files, which are about 300 KB. Additionally, the web services (.asmx files) that generate jsdebug files add another 350 KB to the bundl ...

Tips for implementing multiple selectors in YUI

Is there a way to use multiple selectors in YUI (YUI 2) similar to jQuery? $('h1, h2, el1, el2, .content, .title').css('color', 'red'); How can I achieve the same in YUI without having to individually add classes using YAHOO ...

Navigating to Protected Mobile Platform

I am experiencing an issue with accessing a .NET website with SSL on my Blackberry device. When I try to view the site, I receive the message "HTTP ERROR 403 FORBIDDEN - You're not authorized to view this page. Please try loading a different page". Th ...