mikeal / gza

Functional custom HTML elements.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Feature: Post initialization handler to setup shadow dom element event handlers

avoidwork opened this issue · comments

Hi,

So I've trying to create a completely self-contained component and I need to hook an event handler on an embedded input; my current method feels pretty hacky (but functional), and I was thinking a 'post initialization' function would be really convenient; maybe it could appear as the last arg for gza (after render slot)?

I'm currently embedding a script inside the innerHTML of component with a setTimeout of 16ms to give the DOM time to exist:

<rose-chat data-user="Ada" data-text="Hey there, I'm Ada! It looks like you are new around here. Want me to show you the ropes?" data-placeholder="Chat for Help (What's a Calculated Metric?)">
  <script>
    const chat = document.querySelector('rose-chat');

    chat.addEventListener('keyup', ev => {
      if (ev.keyCode === 13) {
        ev.stopPropagation();
        ev.preventDefault();

        const input = chat.shadowRoot.querySelector('rose-input input');

        if (input !== null && input.value !== '') {
          chat._settings.conversation.push({user: chat.dataset.user, text: input.value});
          chat._render();
          setTimeout(() => chat.shadowRoot.querySelector('rose-input input').focus(), 16);
        }
      }
    });
  </script>
</rose-chat>

Full disclosure: I'm only a few hours into working with shadow dom since like '12? so I might be doing this wrong 😄

follow up; is pushing into _settings.conversation & calling _render() significantly wrong? i can't see a good way to put an array in the dataset for iteration in my template:

const gza = require('gza');

const msg = (user, text, initUser) => `
<rose-message class="${user === initUser ? 'left' : 'right'}">
  <rose-message-user>${user}</rose-message-user>
  <rose-message-text>${text}</rose-message-text>
</rose-message>
`;

gza`
${element => { /* initialization function */
  if (element.dataset.bot !== void 0 && element.dataset.text !== void 0) {
    element._settings.conversation.push({user: element.dataset.bot, text: element.dataset.text})
  }
  
  if (element.dataset.placeholder !== void 0) {
    element._settings.placeholder = element.dataset.placeholder;
  }
}}
<rose-chat ${{conversation: [], placeholder: ''}}></rose-chat>
<style>
{{STYLE}}
</style>
<rose-container>
  <rose-conversation>
    <rose-messages>
      ${settings => settings.conversation.map(i => msg(i.user, i.text, (settings.conversation[0] || {}).user || '')).join('\n')}
    </rose-messages>
  </rose-conversation>
  <rose-input>
    <input type="text" class="spectrum-Textfield" placeholder="${settings => settings.placeholder}" />
  </rose-input>
</rose-container>
<slot name="render"></slot>
`;

follow up; is pushing into _settings.conversation & calling _render() significantly wrong? i can't see a good way to put an array in the dataset for iteration in my template

heh, i literally just commented on this because I'm not happy with it :) #4 (comment)

Post initialization handler to setup shadow dom element event handlers

A few questions.

  1. Do we need an "after first render" handler or an "after every render" handler?
  2. Is this specific case handled better by enabling raw elements elements as values in the template? I have a proposal here #4

The problem here is that re-render of pure strings will blow away any handlers you had put on templatized elements.

The best path forward here is to figure out the ideal way to setup this kind of handler, once, and to keep the same element through re-render cycles.

Would this be ideal?

const init = element => {
  let btn = document.createElement('button')
  btn.onclick = () => console.log('click')
  element.set('button', btn)
}
rza`
${ init }
<my-element>
</my-element>
<shadow-div>
  ${ settings => settings.button }
</shadow-div>
`

BTW, this example doesn't work yet, need to implement the other features.

A few questions.

  1. Do we need an "after first render" handler or an "after every render" handler?
  2. Is this specific case handled better by enabling raw elements elements as values in the template? I have a proposal here #4
    The problem here is that re-render of pure strings will blow away any handlers you had put on templatized elements.

The best path forward here is to figure out the ideal way to setup this kind of handler, once, and to keep the same element through re-render cycles.

re: 1, I think that "every render" is needed considering the lifecycle you mention below. without getting into vdom craziness, I'm a fan of wiping the board when it makes sense.

Ok, I've got something in that should work for you now.

I added a test dealing with this in the easier/preferred way :) https://github.com/mikeal/gza/blob/master/tests/components.js#L86

awesome, thanks!