Comments (18)
@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.
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>
`
})
from hyperapp.
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.
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.
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.
@rbiggs Your delegate function third argument is selector
, but you are passing the buttonHandler.
Did you mean
delegate('ul', 'click', list, buttonHandler)
?
from hyperapp.
@rbiggs What's delegateTarget inside buttonHandler?
from hyperapp.
@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.
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.
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.
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.
@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.
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.
Thanks @rbiggs! I really hope they get back at us though!
from hyperapp.
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.
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.
Going to close here since there's nothing actionable. Thanks @rbiggs.
from hyperapp.
V1 and V2 share a common event proxy now.
from hyperapp.
Related Issues (20)
- A way to insert raw Html HOT 1
- TypeError: can't access property 0, newSubs is null, when setting the state to undefined. HOT 4
- Issue with null-vnodes HOT 1
- prevent rerender node HOT 2
- The dispatch initializer ends in an endless loop on init when dispatching any action HOT 7
- Injected classes gets removed when using object/array to define class props HOT 1
- hyperapp version HOT 3
- Memo Data Gotcha HOT 5
- Confusing doc for actions -> wrapped actions HOT 5
- Passing arguments to init HOT 4
- [Question] Headless mode is still possible? HOT 1
- Destroying a child app HOT 8
- @hyperapp/html: use a Proxy? HOT 9
- Actions returning other Actions HOT 5
- Compile template tag to hyperscript HOT 17
- A challenge to hyperapp community HOT 1
- Has 2.0 been dropped from development? HOT 3
- oldSub[2] is not a function HOT 3
- Cannot read properties of null (reading 'length') HOT 5
- Unlikely Use Case bug in HTML and SVG Packages HOT 9
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
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.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from hyperapp.