Access DOM when using hyper.Component

232 views Asked by At

When using HyperHTMLElement it's possible to access the contents of the component by simply using this.children or this.querySelector(), since it's an element.

But how would I achieve similar behavior when using hyper.Component?

The hypothetical example I have in mind is from React docs: https://facebook.github.io/react/docs/refs-and-the-dom.html - I'd like to focus a specific node inside my DOM.

I have a codepen sandbox where I'm trying to solve this: https://codepen.io/asapach/pen/oGvdBd?editors=0010

The idea is that render() returns the same Node every time, so I could save it before returning and access it later as this.node:

render() {
  this.node = this.html`
    <div>
      <input type="text" />
      <input type="button" value="Focus the text input" onclick=${this} />
    </div>
  `;
  return this.node;
}

But that doesn't look clean to me. Is there a better way to do this?

2

There are 2 answers

3
Andrea Giammarchi On BEST ANSWER

The handleEvent pattern is there to help you. The idea behind that pattern is that you never need to retain DOM references when the behavior is event-driven, 'cause you can always retrieve nodes via event.currentTarget, always pointing at the element that had the listener attached, or event.target, suitable for clicks happened in other places too within a generic click handler attached to the wrap element, in your demo case the div one.

If you'd like to use these information, you can enrich your components using an attribute to recognize them, like a data-is="custom-text-input" on the root element could be, and reach it to do any other thing you need.

onclick(e) {
  var node = e.target.closest('[data-is=custom-text-input]');
  node.querySelector('[type=text]').focus();
}

You can see a working example in a fork of your code pen: https://codepen.io/WebReflection/pen/RLNyjy?editors=0010

As alternative, you could render your component and address its content once as shown in this other fork: https://codepen.io/WebReflection/pen/LzEmgO?editors=0010

  constructor() {
    super().node = this.render();  
  }

at the end of the day, if you are not using custom elements but just basic, good'ol DOM nodes, you can initialize / render them whenever you want, you don't need to wait for any upgrade mechanism.

What is both nice and hopefully secure here, is that there's no way, unless you explicitly expose it, to address/change/mutate the instance related to the DOM element.

I hope these possibilities answered your question.

0
joshgillies On

This is something I've worked on in the past via https://github.com/joshgillies/hypercomponent

The implementation is actually quite trivial.

class ElementalComponent extends hyper.Component {
  constructor () {
    super()
    const _html = super.html
    this.html = (...args) => {
      this.node = _html.apply(this, args)
      return this.node
    }
  }
}

class HelloWorld extends ElementalComponent {
  render () {
    return this.html`<div>Hello World!</div>`
  }
}

This works really well and is inline with your question. However, it's worth noting hyperHTML can render not only a single node but also multiple nodes. As an example:

hyper`<div>Hello World!</div>` // returns a single DOM Node
hyper`<div>Hello</div> <div>World!</div>` // returns multiple DOM Nodes as an Array.

So this.node in the above ElementalComponent can be either a DOM Node, or Array based on what the renderer is doing.