Open LukaHorvat opened 4 years ago
Yeah, I've thought about this as well, here are some experiments regarding this in the Replica repo. The basic idea is to piggyback on the evend dispatch -> server -> VDOM patch -> client loop, so that components send custom events to the server (e.g. a ZoomIn
event) and then the server sends an updated custom patch to the component (e.g. "update yourself with these location pins").
However, I suspect it would be now possible to do that reusing the new JS FFI functionality, without having to patch Replica.
EDIT:
then have smaller concur components inside the JS code editor
That's a cool idea! Shouldn't be too hard in principle, but needs some thought.
I'd love it if you gave it a shot. I took a look at the JS code but it seemed just a bit too involved to do without knowing the details.
Note to self, investigate custom components.
Hi, I have tried custom components with this code and found something.
Only connectedCallback (when custom element show without previous same custom element) and disconnectedCallback (when custom element is not included in new doms.) works. I can't trigger attributeChangedCallback and adoptedCallback by concur-replica syntax.
{-# LANGUAGE OverloadedStrings, QuasiQuotes #-}
import Concur.Core
import Concur.Replica hiding (i)
import Replica.VDOM (defaultIndex)
import Prelude hiding (div, id)
import Data.Text
import Network.WebSockets.Connection (defaultConnectionOptions)
import Network.Wai as Wai
import Network.Wai.Middleware.Static as Static
import Data.String.Interpolate
customElement :: Widget HTML a
customElement = do
_ <- orr [el "custom-element" [textProp "port" "1"] [], () <$ button [onClick] [text "click me"]] -- connectedCallback is called when the element appears first time. getAttribute gets 1.
_ <- orr [el "custom-element" [textProp "port" "2"] [], () <$ button [onClick] [text "click me"]] -- nothing is called.
_ <- orr [el "undefined-name" [textProp "port" "3"] [], () <$ button [onClick] [text "click me"]] -- disconnectedCallback is called. getAttribute gets 2.
el "custom-element" [textProp "port" "4"] [] -- connectedCallback is called. getAttributes gets 4.
defineCustomElement :: Text
defineCustomElement = [i|
customElements.define('custom-element',
class extends HTMLElement {
constructor() { super(); }
connectedCallback() { this.updateElement(); console.log('connected');}
attributeChangedCallback() { this.updateElement(); console.log('attributeChanged');}
disconnectedCallback() { this.updateElement(); console.log('disconnected');}
adoptedCallback() { this.updateElement(); console.log('adopted');}
static get observedattributes() { return ['port']; }
updateElement()
{
const port = this.getAttribute('port');
alert(port);
}
}
);
|]
main :: IO ()
main = do
run 8080
(defaultIndex "custom element testing" [])
defaultConnectionOptions
static
$ \_ -> div [] [customElement,script [] [text defineCustomElement]]
Ah, I think you misspelled observedattributes
(it should be observedAttributes
). Also, if my understanding is correct,adoptedCallback
will never be called - replica
only inserts or deletes elements, it doesn't move them.
@pkamenarsky , Oops. Thanks for spotting that. Now attributedChangedCallback is called before every connectedCallback, but not before disconnectedCallback, as expected.
I suspect this is because you haven't registered undefined-name
as a custom component. In your example above, you should get these callbacks in this order:
connectedCallback
, attributeChangedCallback
, disconnectedCallback
, connectedCallback
.
Actually, after I registered the second element, it becomes:
(before clicking any button) attributeChanged connected
(first click) attributeChanged
(second click) disconnected second attributeChanged second connected
(third click) second disconnected attributeChanged connected
it seems it will always call attributeChangedCallback before it calls connectCallback, even in the first show when there is no any button clicking.
I don't know if it is right. But it makes sense to me. I can live with that.
And if I don't register the second element, I get: (click 0) attributeChanged connected
(click 1) attributeChanged
(click 2) disconnected
(click 3) attributeChanged connected
I will always get one more attributeChangedCallback before it connects. Other than that, I get the order you specified.
Yeah, that seems about right - see https://github.com/pkamenarsky/replica/blob/master/js/client.ts#L370. When inserting a new node we first attach its children, then set the attributes and as a last step add it to the DOM.
It's somewhat strange that custom components will call the attributeChanged
callback even before being added to the DOM, but I guess that's what the spec says.
What would be the easiest way to do this?
Say I'm writing an app in Haskell but a part of the app calls for a code editor. I'm not going to reimplement that in concur-replica so I'd like to just include that JS component where from the concur perspective it's just a widget that finishes once the user submits what's written. What I'd also like is a way to then have smaller concur components inside the JS code editor. For example, autocomplete dropdowns or things like that. So a Haskell wrapper for JS components and a JS wrapper for Haskell components.
Do you think this is feasible?