Cannot read property 'addEventListener' while building nested dropdown

Morning y’all!

Does anyone know how to refactor the components in EmberMap’s Building A Nested Dropdown code (specifically, dropdown-list-item and dropdown-list) in light of RFC 486, which deprecated support for mouseEnter/Leave/Move Ember events?

My nested dropdown (dropdown-list-item, dropdown-list, and ui-nav) stopped working after the RFC while throwing Cannot read property 'nextSibling' of null and Cannot read property 'parentNode' of null.

Tried completely replacing my dropdown in ui-nav with @samselikoff’s DropdownList in nav-menu, which is:

<div class="m-4">
  <DropdownList class='list-reset flex' as |List|>
    <List.item class='w-64 p-4 bg-grey-light cursor-pointer' as |Item|>
      Menu A {{if Item.isActive '(active)'}}
    </List.item>
    <List.item
      data-tether-id="menu-b"
      class='w-64 p-4 bg-grey-light cursor-pointer'
    as |Item|>
      Menu B {{if Item.isActive '(active)'}}
      {{#if Item.isActive}}
        {{#modal-dialog
          tetherTarget='[data-tether-id="menu-b"]'
          targetAttachment='bottom left'
          attachment='top left'
          animatable=true
          hasOverlay=false
        }}
          <Item.sublist class='-mt-2 list-reset bg-white shadow' as |List|>
            <List.item class='p-4' as |Item|>
              Submenu 1 {{if Item.isActive '(active)'}}
            </List.item>
            <List.item class='p-4 relative' as |Item|>
              Submenu 2 {{if Item.isActive '(active)'}}
              {{#if Item.isActive}}
                <Item.sublist class='absolute bg-white shadow' as |List|>
                  <List.item class='p-2' as |Item|>
                    Sub-submenu 1 {{if Item.isActive '(active)'}}
                  </List.item>
                  <List.item class='p-2'>
                    Sub-submenu 2
                  </List.item>
                </Item.sublist>
              {{/if}}
            </List.item>
          </Item.sublist>
        {{/modal-dialog}}
      {{/if}}
    </List.item>
  </DropdownList>
</div>

This throws: Cannot read property 'addEventListener' of null and seems to point to a problem here, which is:

    let { firstNode, lastNode } = Ember.ViewUtils.getViewBounds(this);

    let firstElement = firstNode instanceof Text ? firstNode.nextElementSibling : firstNode;
    let lastElement = lastNode instanceof Text ? lastNode.previousElementSibling : lastNode;

    if (firstElement !== lastElement) {
      throw `DropdownListItem: An Item's content must have a single root HTML element.`;
    }

    this.rootElement = firstElement;

    this.handleMouseEnter = this.handleMouseEnter.bind(this);
    this.handleMouseLeave = this.handleMouseLeave.bind(this);

    this.rootElement.addEventListener('mouseenter', this.handleMouseEnter);
    this.rootElement.addEventListener('mouseleave', this.handleMouseLeave);
  },

After adding console.log(Ember.ViewUtils.getViewBounds(this)), the log came back:

There were more nulls than that, but you get the idea.

Haven’t solved any node issues yet, so not sure what to do with this information.

Any ideas?

See https://developer.mozilla.org/en-US/docs/Web/API/NonDocumentTypeChildNode/nextElementSibling.

The NonDocumentTypeChildNode.nextElementSibling read-only property returns the element immediately following the specified one in its parent's children list, or null if the specified element is the last one in the list.

You need to have wrapping ul/li elements, as DropdownList and List.item are tag-less components.

<div class="m-4">
  <DropdownList class='list-reset flex' as |List|>
   <ul>

    <List.item class='w-64 p-4 bg-grey-light cursor-pointer' as |Item|>
      <li>  <!-- required for nextElementSibling() to be found -->
      Menu A {{if Item.isActive '(active)'}}
     </li>
    </List.item>
    <List.item
      data-tether-id="menu-b"
      class='w-64 p-4 bg-grey-light cursor-pointer'
    as |Item|>
     <li> <!-- required as ListItem is a tagless component -->
      Menu B {{if Item.isActive '(active)'}}
      {{#if Item.isActive}}
        <!-- ... snip ... -->
      {{/if}}
    </li>
    </List.item>
   </ul>
  </DropdownList>
</div>
1 Like

Oh wow. Okay, that makes sense. Looks like I’ve got some code to refactor. Thank you!