Element lifecycle; calling .focus()
callum opened this issue ยท 20 comments
I'm currently trying to work out how to call .focus()
on an element.
const yo = require('yo-yo')
function textarea (value) {
const el = yo`<textarea>${value}</textarea>`
el.focus()
return el
}
const foo = textarea('foo')
document.body.appendChild(foo)
yo.update(foo, textarea('bar'))
There are a couple problems with the above:
- updating is asynchronous; the
textarea
function doesn't know when its return value is added to the page - element references are inconsistent;
el
represents a new element, not what's in the page
Any ideas how to achieve this? I've tried a few things to no avail.
Unfortunately elements don't natively have an onload
and onunload
events. I use the on-load library atm, which uses a MutationObserver to detect when the element is loaded/unloaded on the body
. But would eventually like to figure out a more robust lifecycle implementation.
For element references, if you need the actual element in the dom and lost the reference to it, you can always use document.querySelector()
to find it.
The "target" element foo
will always hold the reference to the actual element. So another strategy if an element needs to update itself, let that element control the update, rather than a parent. But it really depends on what you're building on how high up the yo.update
s should occur.
@shama ooh, on-load
looks heaps good.
I think virtual-dom
's widgets got the events down to a minimum:
- init: when an event is first rendered
- update: when an event receives new properties
- destroy: when an event is removed from the dom
I think between on-load
and functions receiving data, this model is already in place for bel
. Perhaps yo-yo
could integrate on-load
, but I don't think there's would be a need for more than these events. Thoughts?
@yoshuawuyts I had an implementation that looked like:
var el = yo`<div onload=${function () {}} onunload=${function () {}}>
add me
</div>`
To be consistent with existing events and those events are the ones I wish elements already had natively.
But opted to keep the elements pure to make implementing yo-yoify
easier (and one of the reasons dom-css
was removed too). I'd be curious to see how it would affect yo-yoify
if on-load
was integrated.
I wanted to look into how web components shim lifecycle support too. Might be a good model to borrow from too.
Oh and on-load
doesn't support older browsers but we could implement a polling backup to the MutationObserver... but I wanted to dive into more solutions to ensure the solution is the best option we currently have to implement it.
Looks like webcomponents are using MutationObservers as well for their lifecycles too: https://github.com/webcomponents/webcomponentsjs/blob/master/src/CustomElements/observe.js
Yeah, I think on-load
pretty much covers all our bases; perhaps improving the syntax on the yo
level would be neat. I quite like the idea of using onload=${function}
, it looks heaps neat.
As far as webcomponent events are concerned, I believe they expose the following:
.createdCallback() // after element was created
.attachedCallback() // after element was attached to DOM
.detachedCallback() // after element was detached from dom
.attributeChangedCallback(attr, old, nw) // on element attribute change
.attached()
/ .detached()
seem similar to what on-load
implements now. I believe .created()
is like an init
function; I reckon it shouldn't be that hard to emulate if we keep track of state a bit (perhaps extend on-load
? Or should we leave it up to the consumer to implement?) I don't think .attributeChangedCallback()
would be needed.
morphdom does an OK job of keeping track of lifecycle. One approach I tried was adding onBeforeNodeDiscarded
and onBeforeMorphEl
handlers that emitted custom events on elements. That only partially solved my issue though. on-load seems like a pretty good approach. What would an ideal solution be?
My pull request for more detailed lifecycle handling in morphdom has just been released: patrick-steele-idem/morphdom#46
I'm considering creating a module that wraps the morphdom function to give you lifecycle hooks. This could be implemented as custom events on elements, or even something callback-based. Example:
const yo = require('yo')
const hooks = require('morphdom-hooks')(yo.update)
function textarea (value) {
const el = yo`<textarea>${value}</textarea>`
hooks.onAdded(el, function (el) {
el.focus()
})
// hooks.onUpdated
// hooks.onDiscarded
return el
}
const foo = textarea('foo')
document.body.appendChild(foo)
yo.update(foo, textarea('bar'))
What this wouldn't have is a way to handle the initial insertion to the document - events would only occur when the DOM is morphed.
The simplest I could come up with: https://github.com/callum/morphdom-hooks
Thoughts @shama? @yoshuawuyts?
yo.transform.js
function has(object, path) {
return object != null && hasOwnProperty.call(object, path);
}
var morphdomHooks = ["onBeforeNodeAdded",
"onNodeAdded",
"onBeforeElUpdated",
"onElUpdated",
"onBeforeNodeDiscarded",
"onNodeDiscarded",
"onBeforeElChildrenUpdated"];
module.exports = yo.transform = function(el, newEl, opts) {
if(typeof opts !== 'object')
opts = {};
for (var hook = 0; hook < morphdomHooks.length; hook++)
if(has(el, morphdomHooks[hook]))
opts[morphdomHooks[hook]] = el[morphdomHooks[hook]]
return this.update(el, newEl, opts);
}
smaller version (without 'has'):
var morphdomHooks = ["onBeforeNodeAdded",
"onNodeAdded",
"onBeforeElUpdated",
"onElUpdated",
"onBeforeNodeDiscarded",
"onNodeDiscarded",
"onBeforeElChildrenUpdated"];
module.exports = yo.transform = function(el, newEl, opts) {
if(typeof opts !== 'object') opts = {};
for (var hook = 0; hook < morphdomHooks.length; hook++)
opts[morphdomHooks[hook]] = el[morphdomHooks[hook]]
return this.update(el, newEl, opts);
}
Usage:
function Button (content) {
return yo`<button>${content}</button>`
}
var button = Button("My Button")
button.onElUpdated = function (node) {
console.log('updated!');
}
yo.transform(button, Button("My New Button"))
document.body.appendChild(button)
A more modular approach, thoughts? It may be slightly slower due to the testing and binding of the element every time it's updated. But I would assume it's minimal processing. If you want to bypass this, you can just use yo.update
. But yo.transform
would be used if you have morphdom hooks.
As well the morphdomHooks can be decreased reducing functionality, but increasing processing speed.
**Note, doesn't seem to work on requirebin, but I think that's to do with morphdom. Works locally.
@SilentCicero is that different to https://github.com/callum/morphdom-hooks? It's made to be used with yo-yo:
var yo = require('yo-yo')
var hooks = require('morphdom-hooks')
var update = hooks(yo.update)
update(nodeA, nodeB, {})
For anyone experimenting with MutationObserver
:
https://developer.mozilla.org/en/docs/Web/API/MutationObserver
pretty interesting.
window.listeners = [];
var doc = window.document,
MutationObserver = window.MutationObserver || window.WebKitMutationObserver,
observer;
function ready(selector, fn){
// Store the selector and callback to be monitored
listeners.push({
selector: selector,
fn: fn
});
if(!observer){
// Watch for changes in the document
observer = new MutationObserver(check);
observer.observe(doc.documentElement, {
childList: true,
subtree: true
});
}
console.log(listeners);
// Check if the element is currently in the DOM
check();
}
function check(){
// Check the DOM for elements matching a stored selector
for(var i = 0, len = window.listeners.length, listener, elements; i < len; i++){
listener = window.listeners[i];
// Query for elements matching the specified selector
elements = doc.querySelectorAll(listener.selector);
for(var j = 0, jLen = elements.length, element; j < jLen; j++){
element = elements[j];
// Make sure the callback isn't invoked with the
// same element more than once
if(!element.ready){
element.ready = true;
// Invoke the callback with the element
listener.fn.call(element, element);
}
}
}
}
Just left a note of some trouble I'm having with the on-load
package, and yoyo: shama/on-load#1
For focus specifically, you can set a boolean html property autofocus
when you trigger an update.
@substack see my latest life cycle package: https://github.com/SilentCicero/throw-down
I tried out morphdom-hooks
but found that only the parent nodes got the add
and discard
events.
Also on the initial render the events weren't called.
I ended up with https://gist.github.com/JamesKyburz/f0d8536c6ad7f144989984655dadf226
@JamesKyburz awaiting merge patrick-steele-idem/morphdom#57
@callum Great thanks!
on-load
seems to be working brilliantly https://github.com/shama/on-load/issues - looking like a winner. The question now is: should this be part of yo-yo
or bel
in some way, or would this be a separate thing?
Ah choojs/nanohtml#32 is out; this means it'll be part of bel
which means that once it lands and we update the version of bel
here this issue will be resolved โจ