Giter Club home page Giter Club logo

Comments (18)

jorgebucaran avatar jorgebucaran commented on May 4, 2024

@rbiggs Can you provide a concrete example?

It would be nice if there was some way to delegate the events off of the view root.

Like attaching an onclick to the window and reuse the function for doing some computation? Similar to how choo and HyperApp handle routing/anchors.

from hyperapp.

jorgebucaran avatar jorgebucaran commented on May 4, 2024

In all your examples you have events created directly on elements in your hyperx code.

That's just for conciseness.

Does this make sense?

const clickHandler = msg => e => msg.text(e.target.textContent)

app({
  model: 0,
  update: {
    text: (_, data) => data
  },
  view: (model, msg) => html`
    <div>
      <h1>${model}</h1>
      <button onclick=${clickHandler(msg)}>1</button>
      <button onclick=${clickHandler(msg)}>2</button>
      <button onclick=${clickHandler(msg)}>3</button>
      <button onclick=${clickHandler(msg)}>4</button>
    </div>
  `
})

CodePen

from hyperapp.

rbiggs avatar rbiggs commented on May 4, 2024

Certainly. Say you have a list, and each list item hold several actionable buttons for different purposes, whatever that might be (add, delete, selector, etc.). Adding events for each button in each list item on a long list results in sluggish behavior, especially on mobile. Delegating the event means attaching it to the parent element, in our case the UL and capturing the event target and seeing if it is the intended button or or not. If it is, fire the callback.

Below is how you delegate an event in vanilla JavaScript:

function delegate(element, event, selector, callback) {
  const delegateEl = document.querySelector(element)
  const eventListener = function(e) {
    let el = e.target
    const els = Array.prototype.slice.apply(delegateEl.querySelectorAll(selector))
    do {
      els.forEach((elem) => {
        if (el === elem) {
          callback.call(elem, e)
          return;
        }
      })
    } while (el = el.parentNode)
  }
  delegateEl.addEventListener(event, eventListener)
  return eventListener
}

Then you would delegate your list item button like this:

const buttonHandler = function(e) {
  const button = e.delegateTarget;
  if(!button.classList.contains("selected"))
    button.classList.add("selected")
  else
    button.classList.remove("selected")
};

const list = document.querySelector('ul')
/**
 * Delegate button events onto list:
 */
delegate('ul', 'click', buttonHandler)

This lets you add or remove list items without worrying about removing the events from the buttons or any other child element events you might have registered. All events get registered on the view's root. Then if you want to destroy the view, you have only one element to remove events from.

from hyperapp.

rbiggs avatar rbiggs commented on May 4, 2024

See, that's the problem I'm talking about. You have onclick=${clickHandler(msg)} on each button. Where event delegation would have the click event on the parent div and check the event target to see if the user clicked on a button. If the event target was the button, it would handle the event. If the user just clicked between the buttons directly on the div, nothing would happen.

from hyperapp.

rbiggs avatar rbiggs commented on May 4, 2024

You example is simple. Imagine you have a list with 300 items. You want your items to be interactive, the user clicks to select, or whatever. Registering an event on each list item is inefficient. Delegation solves this by registering the event on the list itself and listens for the lick on the list items. One event to rule them all.

from hyperapp.

jorgebucaran avatar jorgebucaran commented on May 4, 2024

@rbiggs Your delegate function third argument is selector, but you are passing the buttonHandler.

Did you mean

delegate('ul', 'click', list, buttonHandler)

?

from hyperapp.

jorgebucaran avatar jorgebucaran commented on May 4, 2024

@rbiggs What's delegateTarget inside buttonHandler?

from hyperapp.

jorgebucaran avatar jorgebucaran commented on May 4, 2024

@rbiggs Figured it out. Here's a working CodePen.

function delegate(element, eventName, selector, callback) {
    
  const delegateEl = document.querySelector(element)

  const eventListener = function(e) {
    let el = e.target 
    let els = delegateEl.querySelectorAll(selector)
       
    do {
      els.forEach((elem) => {

        if (el === elem) {
          e.delegateTarget = el
          callback.call(elem, e)
          return;
        }
          
      })
    } while (el = el.parentNode)
  }
  
  delegateEl.addEventListener(eventName, eventListener)
      
  return eventListener
}
  
const buttonHandler = function(e) {
  const button = e.delegateTarget;

  if(!button.classList.contains("selected")) {
    button.classList.add("selected")
  } else {
    button.classList.remove("selected")
  }
};


delegate('ul', 'click', "li", buttonHandler)

And here's some HTML to go with it.

<ul>
    <li class="selected">One</li>
    <li class="selected">Two</li>
    <li class="selected">Three</li>
    <li class="selected">Four</li>
    <li class="selected">Five</li>
</ul>

And some CSS.

.selected {
    background-color: lightblue;
    width: 5%;
}

from hyperapp.

jorgebucaran avatar jorgebucaran commented on May 4, 2024

Now that I understand the issue. Let me ask you, what do you think HyperApp should do in this case? and how would the API look like?

How can we accomplish this in a functional manner?

app({
    model: [
        { text: "One", selected: false}, 
        { text: "Two", selected: false}, 
        { text: "Three", selected: false}
    ],
    view: model => {
        const clickHandler = e => {
            // detect list item and check if the class is selected
            // dispatch a "changeText" action to update model
        } 
        
        return html`
            <ul onclick=${clickHandler}>
                ${model.map(i => html`
                    <li class=${model.selected ? "selected" : ""}>${i.text}</li>
                `)}
            </ul>
    `
    }
})

from hyperapp.

rbiggs avatar rbiggs commented on May 4, 2024

Are you sure that Codepen example is working?
For implementing, when the user puts an event such as onClick=${whatever} on a view's element, you would delegate that automatically to the view's root. Vue, React, Angular and most other frameworks already do this. But since you're using Hyperx underneath to create the DOM nodes, diff, patch, etc. I'm not sure how feasible this is. I already opened an issue on the Hyperx repository because I could see how they would delegate events. If Hyperx can and doesn't want to add it, I what I would do is parse the template literal first before passing it to Hyperx, check for events, if events, remove from string and create delegate event on view root, then pass cleaned script to Hyperx. That unfortunately would mean you would have to create your own html function as the intermediary to the Hyperx one. :-/.

You should probably wait and see how Hyperx people respond to my question about event delegation. It is Friday night, so it will probably take a few days.

from hyperapp.

rbiggs avatar rbiggs commented on May 4, 2024

Actually in my own code I have a separate function for wiring up events on views. It's a method the takes a array so you can register multiple events:

const myView = View({
   element: 'ul',
   template: `<li>${person.name.first} ${person.name.last}</li>`,
   events: [
      // Register event on list:
      {
         element: 'ul',
         event: 'click',
         callback: function(e) {
            alert('You clicked the list!')
         }
     },
     // Delegate event for list items:
     {
         element: 'li',
         event: 'mousedown'
         callback: function(e) {
            alert(this.textContent)
         }
      }
   ]
});

I'm not saying that's how you SHOULD do, this is the pattern I've been using for my stuff just to give you some idea. Elm does this under the covers for you. Not quite sure how Choo does it. Using my pattern, I can bind events for any child elements of the view. I can add and delete child elements without worrying about their events. I only handle removing events if I need to destroy the view itself.

from hyperapp.

jorgebucaran avatar jorgebucaran commented on May 4, 2024

@rbiggs How would the API look like? I mean, could you sketch some pseudo code?

I'm willing to sacrifice readability to some extent for performance, but I'd never trade user friendliness for anything.

Btw choo uses hyperx too, so I don't think they do anything.

from hyperapp.

rbiggs avatar rbiggs commented on May 4, 2024

Yeah, I'm getting a bit groggy. So tomorrow. I need to think a bit about your current API and how that would fit best. I like the pattern, pretty much Elm Architecture in JavaScript. And you kept it simple. Choo is kind of a lot of things going on underneath do to too many dependencies. You don't want to introduce anything too soon so that you're stuck with it. I'm really interested to see what the Hyperx guys say. If they have a way to delegate events, then you can just use that.

from hyperapp.

jorgebucaran avatar jorgebucaran commented on May 4, 2024

Thanks @rbiggs! I really hope they get back at us though!

from hyperapp.

aeosynth avatar aeosynth commented on May 4, 2024

as a counter point, Vue doesn't use delegation: https://forum.vuejs.org/t/is-event-delegation-necessary/3701

Well, delegation has two main advantages: one is practical - it saves you from having to add (and remove!!) those listeners individually. But Vue already does that for you.

The other one is performance / memory. But since every click listener in a v-vor loop would use the same callback, this is minimal unless you have hundreds or thousands of rows.

from hyperapp.

rbiggs avatar rbiggs commented on May 4, 2024

OK, so I set up some tests adding and deleting hundreds of events with list items and watch the memory usage in Chrome Dev Tools and Safari Web Inspector. I didn't see the disaster I was expecting. Memory usage stayed pretty flat over time. As such I'm gonna say this isn't a big problem for the current implementation.

from hyperapp.

jorgebucaran avatar jorgebucaran commented on May 4, 2024

Going to close here since there's nothing actionable. Thanks @rbiggs.

from hyperapp.

jorgebucaran avatar jorgebucaran commented on May 4, 2024

V1 and V2 share a common event proxy now.

https://github.com/hyperapp/hyperapp/blob/6669b7f893af6789596cf61fbfa02eea8656c441/src/index.js#L155-L157

from hyperapp.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.