Ad

How To Pass Event Into Method In A JavaScript Class?

Here is just a sample, I think I've done everything with or without using arrow functions, but still got nothing. I would need to add to the method event.altKey, but how can I pass event to that as we usually do without having class?

The HTML:

<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    <style>
      .selected {
        background: #0f0;
      }
      li {
        cursor: pointer;
      }
    </style>
  </head>
  <body>
    Кликни на элемент списка, чтобы выделить его.
    <br/>
    <ul id="ul">
      <li>Кристофер Робин</li>
      <li>Винни Пух</li>
      <li>Тигра</li>
      <li>Кенга</li>
      <li>Кролик. Просто Кролик.</li>
    </ul>
  <script src="script.js"></script>
  </body>
</html>

What I think should work but doesn't:

window.addEventListener('load', function(){
  class List{
    constructor(){
      this.lists = document.querySelectorAll('li');

      this.lists.forEach((list) => {
        list.addEventListener('click', (event) => this.click_green);
        list.addEventListener('onmousedown', (event) => this.no_select_text);
      });
    }

    no_select_text(event){
      event.preventDefault;
      return false;
    }

    click_green(event){
      if (event.ctrlKey || event.metaKey)
      {
        this.classList.add('selected');
        return;
      }
      let allLists = this.parentNode.querySelectorAll('li');
      allLists.forEach(function(list){
        list.classList.remove('selected');
      });
      this.classList.add('selected');
    }
  }
  new List;
});
Ad

Answer

  • The first task was to make the code run.
  • The second task is going to provide detailed explanation of what actually happens and why one does not need at all an approach that runs with a JavaScript class.

class ListItems{
  constructor(listItemQuery) {
    const listItems = this;

    listItemQuery.forEach((listItem) => {
      listItem.addEventListener('click', listItems.clickGreen, false);
      listItem.addEventListener('onmousedown', listItems.noSelectText, false);
    });
  }
  noSelectText(evt) {
    evt.preventDefault;
    return false;
  }

  clickGreen(evt) {
    const listItem = evt.currentTarget;

    if (evt.ctrlKey || evt.metaKey) {
      listItem.classList.add('selected');
      return;
    }
    const firstLevelListItems = Array.from(listItem.parentNode.children);

    firstLevelListItems.forEach(function(item) {
      item.classList.remove('selected');
    });
    listItem.classList.add('selected');
  }
}

function initializeListItems() {
  new ListItems(document.querySelectorAll('li'));
}
window.addEventListener('load', initializeListItems, false);
.as-console-wrapper { max-height: 100%!important; top: 0; }
<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    <style>
      .selected {
        background: #0f0;
      }
      li {
        cursor: pointer;
      }
    </style>
  </head>
  <body>
    Кликни на элемент списка, чтобы выделить его.
    <br/>
    <ul id="ul">
      <li>Кристофер Робин</li>
      <li>Винни Пух</li>
      <li>Тигра</li>
      <li>Кенга</li>
      <li>Кролик. Просто Кролик.</li>
    </ul>
  </body>
</html>

The now executable code got broken apart in order to better identify the different pieces of the OP's provided example.

If one makes use of JS classes, it's always better to let the constructor be responsible of just the most necessary bootstrapping part. Thus we have an initialization step that passes the DOM query directly to the constructor.

Next, naming the class List is misleading, for the OP is using it mainly as a structuring element that holds functionality that exclusively manipulates list-items. Let's rename it then to ListItems.

Each HTMLLIElement then adds two very own event handlers to itself. With the OP's example code it is twice an arrow function, each accepting an event argument but not passing it to the intended class method.

The code might have been fixed like that ...

list.addEventListener('click', (event) => this.click_green(event));
list.addEventListener('onmousedown', (event) => this.no_select_text(event));

... but the more intuitive way was assigning the class methods directly as event handlers like ...

listItem.addEventListener('click', this.clickGreen, false);
listItem.addEventListener('onmousedown', this.noSelectText, false);

Having come that far one starts struggling with the class approach due to both method's this context.

this within click_green(event) { ... this.classList.add('selected') ... } is not the HTMLLIElement, but this is always the sole instance of the OP's List class that got created at loading time.

The evt object as with the fixed code example provides a target and a currentTarget object. With the given HTML-structure both do hold a reference to the list item that was e.g. clicked on. As soon as a list item does not just contain text nodes anymore but also is parent to other HTML elements that could be the event-triggering targets, evt.currentTarget is the single source of truth because of both of its added event handlers.

Being sure now about the clicked list item one does not want to just query all list items of the parent list because list items might be containers for other lists and so on. One instead wants to get all of the list item's siblings. Targeting them via the parentNode one should be aware that parentNode.childNodes does list any node including text nodes that e.g. come from the new lines of the provided example code. One always is on the save side with parentNode.children which is a HTMLCollection that just lists element nodes. Converting such a collection into an array ... Array.from(listItem.parentNode.children); ... is necessary for processing its items via array methods.

There is nothing more to say about how to make the OP's code run as it might has been intended.

And with having hopefully gained a better understanding of the code, one might even get rid of the class syntax, replacing it with another approach of how to structure code ...

// list item module
//
// - written as immediately invoked function expression ...
// - ... mainly for encapsulation of domain specific code.
//
const ListItems = (function () {

  function preventSelection(evt) {
    evt.preventDefault;
    return false;
  }

  function selectItem(evt) {
    const listItem = evt.currentTarget;

    if (evt.ctrlKey || evt.metaKey) {

      listItem.classList.add('selected');

    } else {
      const firstLevelListItems = Array.from(listItem.parentNode.children);

      firstLevelListItems.forEach((item) => {
        item.classList.remove('selected');
      });
      listItem.classList.add('selected');
    }
  }

  function initializeListItems() {
    document.querySelectorAll('li').forEach((listItem) => {
      listItem.addEventListener('click', selectItem, false);
      listItem.addEventListener('onmousedown', preventSelection, false);
    })
  }

  // the module:
  return {
    initialize: initializeListItems
  };

}());

// another task ... 
window.addEventListener('load', ListItems.initialize, false);
.as-console-wrapper { max-height: 100%!important; top: 0; }
<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    <style>
      .selected {
        background: #0f0;
      }
      li {
        cursor: pointer;
      }
    </style>
  </head>
  <body>
    Кликни на элемент списка, чтобы выделить его.
    <br/>
    <ul id="ul">
      <li>Кристофер Робин</li>
      <li>Винни Пух</li>
      <li>Тигра</li>
      <li>Кенга</li>
      <li>Кролик. Просто Кролик.</li>
    </ul>
  </body>
</html>

Ad
source: stackoverflow.com
Ad