2

I've been using the following code in JavaScript for several months and it appears to work without issue, but I started to wonder why it works and whether or not it is a safe method.

The purpose is simply to keep track of the currently selected HTML element and not perform the function code triggered by an event if the user clicks again upon the currently selected element.

    function elem_mouse_evt( evt )
      {
        if ( evt.currentTarget === elem_mouse_evt.e ) return;
        elem_mouse_evt.e = evt.currentTarget;  
        /* ... */
      }

This may be a rather stupid question on my part but why is this different than comparing two objects for equivalence? How does JavaScript make this comparison, since each represents a collection of values?

Thank you.

Addition I left out a very important point that came to mind while considering the posted answer, which is that the function property e has a larger purpose. There is a menu of actions that the user can choose to perform on the current selection and/or its content. I thought it would be more efficient to store the reference in the function property rather than traversing the DOM to search for it by one of its unique attributes. Is that a reasonable method?

Gary
  • 2,393
  • 12
  • 31
  • 3
    Bear in mind that variables _refer_ to objects. If you compare `a == b`, where `a` and `b` are two distinct objects (which may or may not have the same prototype), you are just comparing the references for those objects. – Francisco Hanna May 11 '19 at 23:03
  • Thanks for responding to my question. I realized that `elem_mouse_evt.e` wasn't a copy of `evt.currentTarget` but didn't even think about that when considering how the comparison was being made. So, it was a stupid question. Thanks again. – Gary May 12 '19 at 00:50

1 Answers1

0

The purpose is simply to keep track of the currently selected HTML element and not perform the function code triggered by an event if the user clicks again upon the currently selected element.

Rather than doing what you're asking (uniquely identifying an element), I'd flag that element in some way. One way to do that is to add something to its dataset. I'm guessing though that you'll want to change the styling of this selected element at some point though anyway so that users see it's already selected. In that case, we'll add a class.

// Add an event listener on the container for these elements.
// It's more efficient to have just one event listener, rather than thousands.
document.querySelector('.parent').addEventListener('click', (e) => {
  // Ensure what's actually clicked meets the filter
  if (!e.currentTarget.matches('.selectable')) {
    return;
  }

  // If it's already selected, don't do anything else!  (You can combine this with above.)
  if (e.currentTarget.matches('.selected')) {
    return;
  }

  e.currentTarget.classlist.add('selected');
  // Perform your other actions here
});
Brad
  • 159,648
  • 54
  • 349
  • 530
  • Thanks for responding to my question. I could use either of your suggestions, that is the `dataset` and the `class` because I use both now. The user adds the elements and I assign some values at that time to the `dataset` to be used in a compound key in indexedDB for saving the content, and make the newly added element the selected element and style it by adding a `class` at the same time. I could store one of those as the function property because I need to reference it in other places also. But is there anything wrong or inefficient in comparing the object references? – Gary May 12 '19 at 05:01
  • Thanks for the note about adding the event listener to the parent container. I had not considered that and have been adding a listener to each new element the user adds. I even had to add some code to make it possible to de-select a child element by clicking the parent but outside the child element. I think the approach you noted may simplify that and will definitely reduce the total number of event listeners. – Gary May 12 '19 at 05:09
  • I forgot to include something important which is the main reason the current selection needs to be tracked. There is a menu of actions that can be performed on the selected element or its content. When one of the menu buttons is clicked, it acts upon the selected element through referencing the `elem_mouse_evt.e` function property and there's no need to traverse the DOM to locate it. Is there a better way to do this? Thank you. – Gary May 12 '19 at 05:38
  • @Gary Assuming you're not using some underlying UI framework, a better way is to treat the DOM as a source of record. It takes no time at all for you to later do a `document.querySelectorAll()` and get all selected elements, and then `[...elements].map()` them to the dataset properties needed for whatever actions you're performing. – Brad May 12 '19 at 05:41
  • Thank you. I'm not implying in any way that you're not correct but would you please explain why it's better? I'd just like to understand. What's wrong with storing a reference to an object in a function property? To a novice like me, it seems like far less work for the browser. Is it better because it provides a "fresh" look at the DOM each time and reduces the chance of error in programmatically tracking the current selection, or is there another reason. Thank you. – Gary May 12 '19 at 05:50
  • @Gary Yes, it is certainly more work for the browser to walk the DOM than it is to simply look at a set of selected IDs you're keeping track of. But, this is something that happens relatively rarely, on a user click action. It's not like you'll be doing this in loops. It's typically done in less than a millisecond. Yes, my reasoning is that you can just update the DOM and have it be consistent. Easy to maintain. If you were using a UI library that based the DOM on data, then you would store selected *data* (not elements) in a set or array or something. – Brad May 12 '19 at 05:55
  • @Gary Since you're not using such a library, save yourself the hassle and update only one thing... the DOM. It's plenty efficient for your use case. – Brad May 12 '19 at 05:56
  • Thank you for explaining your reasoning. I appreciate your help and it makes sense to me. There's one thing I'm not understanding and I apologize for bothering you again. When you write `"store selected data (not elements)"`, do you mean there's a difference between what's stored in the function property when set to the HTML element and that which is returned by `document.querySelectorAll()` and stored in an array? I ask because this is what I was overlooking in my original question. In both cases, aren't the final values just references, like pointers, to the HTML elements or objects? Thanks. – Gary May 12 '19 at 06:17
  • @Gary I'm saying that for the purposes of good separation, your application code shouldn't really know or care about HTML elements, aside from the points at which you're handling click events and such. So, rather than storing an element, store the data from that element that you need instead. For example, if each selected item has a property `data-id` (corresponding to an ID in a database), then push that ID on your Array or Set, not the element. – Brad May 12 '19 at 15:36
  • 1
    I'd like to share that, after several months more work on this project, the method you shard of placing the listener on that parent container has worked great, especially as the interface and options have become more complex. My question was a bit stupid but the information you provided was quite valuable. I think this is a good example of why questions ought not to be closed too quickly. Someone in the know just might provide something unexpected such as not just answering the question but making sure the right question is being asked and teaching a better approach or method. – Gary Mar 05 '20 at 06:17
  • @Gary Thanks Gary. And, your question wasn't stupid or I wouldn't have up-voted it and answered it. :-) – Brad Mar 05 '20 at 16:46