-
Notifications
You must be signed in to change notification settings - Fork 45
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Callback / event on DOM insertion #80
Comments
Nah this is perfect. :-) If you want a forum, the elixirforums (of all places) is generally where it is talked about most though, otherwise here. :-)
Heh, I have the same thing in one of my apps!
I actually parse it based on event callbacks and json-path branching, though adding it to node would be good (can you PR those in?).
Yeah this would be the point of the custom node that I've not implemented yet, for now just wait a render tick by subscribing to a I should leave this open until I get custom nodes added in, I really need to get around to doing that VDom overhaul... (EDIT: You can always listen to DOM added events from javascript too) |
Hmm... it would be really easy to make an attribute that enqueues an event only when it is added (and optionally another on removal), this would be perfect for listening to specific element add/remove events... I should add that to the todo too... |
Thank you for the quick reply @OvermindDL1 👍 I'll try out the animation frame tick idea as a temporary solution. Being able to register for a custom event e.g. "mount" would follow the current logic well: event "mount"
(fun evt ->
match Js.Undefined.toOption evt##target with
| Some target -> _auto_expand target; None
| None -> None) I might add the additional fields to Web.Node.t and make a PR. |
More things in the exported types is always nice, but yeah such an event or attribute would definitely be best. I wish the "load" event on the DOM worked on every element, it would be perfect if so. |
Might be related.. |
Hey @alfredfriedrich, Yes, sorry for the slight psudo-code above. The definition of event is not an external, since all "interactions" with the actual DOM goes through the virtual DOM, registration of event handlers have to go through the virtual DOM as well. I don't know much about the how the virtual DOM is implemented, so I am not of much help in that regard. However, this is taken from tea_html.ml, as an example of how to register for input events through the VDOM: let onInputOpt ?(key="") msg =
onCB "input" key
(fun ev ->
match Js.Undefined.toOption ev##target with
| None -> None
| Some target -> match Js.Undefined.toOption target##value with
| None -> None
| Some value -> msg value
)
let onInput ?(key="") msg = onInputOpt ~key:key (fun ev -> Some (msg ev)) You would use it to register in the following example: type effect =
| MyEffect of string
[@@bs.deriving {accessors}]
let draw model =
...
input' [ onInput myEffect ] [] By service workers do you mean this? (I am not that seasoned in browser app programming, so this is the first time I hear about it) It looks like it comes with a lot of custom API that you would need to wrap using externals, so the above code snippets might not be that relevant. As an example of how to wrap external APIs try to have a look at web_xmlhttprequest.ml, it helped me a lot with some of the work I've been doing lately. |
no worries :)
This is where I hit a wall, because
Yes, particulary
This is what I came up so far: type notification
type serviceworker = <
(* properties *)
state : string;
scriptURL : string;
(* methods *)
onstatechange : (serviceworker_event -> unit [@bs.meth]) [@bs.set];
> Js.t
and serviceworker_event = <
target : serviceworker Js.Nullable.t [@bs.get]
> Js.t
and serviceworkerregistration = <
(* methods *)
unregister : unit -> bool Js.Promise.t [@bs.meth];
(* properties *)
scope : string;
installing : serviceworker Js.Nullable.t;
waiting : serviceworker Js.Nullable.t;
active : serviceworker Js.Nullable.t;
pushManager : pushmanager;
> Js.t
and pushmanager = <
(* methods *)
subscribe : subscribeoptions -> pushsubscription Js.Promise.t [@bs.meth]
> Js.t
and pushsubscription = <
endpoint : string;
expirationTime : string option;
> Js.t
and serviceworkercontainer = <
(* properties *)
controller : serviceworker Js.Nullable.t;
ready : serviceworkerregistration Js.Promise.t;
register : string -> serviceworkerregistration Js.Promise.t [@bs.meth];
> Js.t
external container : serviceworkercontainer = "serviceWorker" [@@bs.val] [@@bs.scope "navigator"]
Yep, reading it again and again :-) I feel like there is an answer for me in there, too. |
never mind, got it somehow working: let onStateChange swOpt =
match Js.Nullable.toOption swOpt with
| Some s ->
Cmd.call (fun callbacks ->
let enqRes = fun ev ->
match Js.Undefined.toOption ev##target with
| None -> ()
| Some target ->
let targetAsServiceworker = targetToServiceworker target in
let msg = ServiceWorkerChanged targetAsServiceworker in
(* let open Vdom in *)
!callbacks.enqueue msg
in
s##onstatechange #= enqRes
)
| None -> Cmd.none
in
let cmds =
[reg##installing; reg##waiting; reg##active]
|> List.map onStateChange
in (model, Cmd.batch cmds) |
Might need an external for that, I've not set up service workers yet (though if you come up with a great API then definitely PR it in! :-) ).
What would be the javascript code of what you are trying to accomplish?
Lot's of ways to do that, from pushing a message manually to the main app itself to handling things in a Cmd or Subscription. :-)
This is exactly right! Though for DOM events the event external should be what is used regardless, but you can use it in a ton of different ways!
Similar for me, I've heard of service workers and know how they are used in PWA's, but I've never used them yet... ^.^;
What would be the javascript code for using them? I'm thinking that a Subscription interface is what will suit them based on what little I know about them, but if you can show the JS for them and how to interact with them then I could come up with a better API for it. :-)
Ahh, it looks like a global object?
Oh hey cool! Don't suppose you want to PR that code? If you leave the PR open to commits from the main repo people then I can clean it up and get it merged in to main as a combination set of subscriptions and commands. :-) |
The serviceworker itself is currently in JS: self.addEventListener('install', function(event) {
console.log('service worker is installing.')
});
self.addEventListener('activate', function(event) {
console.log('service worker is activating.')
});
self.addEventListener('push', function(event) {
if (event.data) {
console.log('This push event has data: ', event.data.text());
console.log(event)
} else {
console.log('This push event has no data.');
}
}); I plan to try to write it in BS later.
Yes, e.g. to register a service worker: if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('/sw.js').then(function(registration) {
// Registration was successful
console.log('ServiceWorker registration successful with scope: ', registration.scope);
}, function(err) {
// registration failed :(
console.log('ServiceWorker registration failed: ', err);
});
});
} and later I can use the registration to show push notifications: serviceWorkerRegistration.showNotification(title, options); To play around, I wanted to register an event listener on the serviceWorkerRegistration.onupdatefound = function (event) {
model.registration = event.target.registration
}
or
myActiveServiceworker.onstatechange = function (event) {
$('#swindicator').text = event.target.state
}
Currently, I'm rewriting it once more :-) Will try to create a PR if it looks good. |
Hmm, so it's just a set of global event listeners it looks like? Yeah this would fit the subscription pattern absolutely perfectly then. :-) I'm guessing the
And the registration seems to just return a promise then? In that case it can be a Cmd, though perhaps combining the Cmd and promise handling into a single call would be easier for the user (and remove the need for another message head)...
Just a simple Cmd on that one too. :-)
These could be done via
Awesome! But yeah just follow the patterns above, I'd make a single subscription that subscribes to a user-specified event on the serviceWorker that takes a function that takes the event argument and return a |
I did not see any examples where they used
Definitely, I use an external that uses Because the serviceworker / registration / container object "seem to appear somewhere in the DOM Tree" ( On a side note, I tried to implement a global "mount/unmount" listener, rigged up a working type mutationRecord = <
_type : string;
target : Web_node.t;
addedNodes : Web_node.t list;
removedNodes : Web_node.t list;
> Js.t
type mutationObserver = <
observe : Web_node.t -> mutationObserverInit -> unit [@bs.meth];
diconnect : unit -> unit [@bs.meth];
> Js.t
external mutationObserver : (mutationRecord Js.Array.t -> unit) -> mutationObserver = "MutationObserver" [@@bs.new]
let observerOptions = [%bs.obj {
childList = true;
attributes = true;
characterData = true;
subtree = true;
attributeOldValue = true;
characterDataOldValue = true;
(* attributeFilter = []; *)
}]
let global tagger =
let open Vdom in
let enableCall callbacks_base =
let callbacks = ref callbacks_base in
let handler = (fun (recs : mutationRecord Js.Array.t) ->
(* let () = Js.log2 "In Global Observer" recs in *)
match !observed with
| None -> ()
| Some vdom ->
let node = patchVNodesOnElems_CreateElement callbacks vdom in
let filterForNode =
List.filter (fun elem ->
(* this is okay for the bs compiler, but in javascript world, elem.target is a living dom node and the node above is just a pumped up Vdom.t *)
elem##_type == "childList" && elem##target == node
) in
let foundNode = filterForNode (Array.to_list recs) in
let () = Js.log2 "foundNode" foundNode in
()
(* callbacks.enqueue (tagger recs) *)
) in
let m = mutationObserver handler in
let () = m##observe Web_node.document_node observerOptions in
fun () -> m##diconnect ()
in Tea_sub.registration "mutationobserver" enableCall
let register node =
let () = observed := Some node in
()
let unregister =
let () = observed := None in
() |
Just making sure so the same calls can be used. :-)
All good, navigator is not really in the DOM tree anyway. :-)
I thought I read somewhere that mutation observers ended up not getting added to the spec and thus can't be relied on? I need to look in to that again, might be thinking about something else... |
This might turn out to be either a question or a feature request, sorry if opening an issue is not the right place to ask this (btw is there a project related forum/chat somewhere?).
I am trying to achieve auto-resizing of textareas. Resizing on value change is working fine by using something like the following:
I am using an external to hack around certain missing fields on Web.Node.t such as scrollHeight and others (tbh I prefer it this way since auto resizing of textareas is a hack regardless):
This works great, but I also need to have _auto_expand be called when the element is inserted into the DOM. E.g. in React you would implement componentDidMount.
I have been trying to find examples in Elm where you get access to a DOM element on insertion/mount time, but have not found any.
So, the question / feature request is; is it possible to get access to an element at the moment after it is inserted into the document?
The text was updated successfully, but these errors were encountered: