Skip to content
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

Using concur-replica for components #34

Open
LukaHorvat opened this issue Mar 31, 2020 · 10 comments
Open

Using concur-replica for components #34

LukaHorvat opened this issue Mar 31, 2020 · 10 comments

Comments

@LukaHorvat
Copy link

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?

@pkamenarsky
Copy link
Owner

pkamenarsky commented Apr 8, 2020

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.

@LukaHorvat
Copy link
Author

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.

@pkamenarsky
Copy link
Owner

Note to self, investigate custom components.

@YuMingLiao
Copy link

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]]


@pkamenarsky
Copy link
Owner

pkamenarsky commented May 11, 2020

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.

@YuMingLiao
Copy link

@pkamenarsky , Oops. Thanks for spotting that. Now attributedChangedCallback is called before every connectedCallback, but not before disconnectedCallback, as expected.

@pkamenarsky
Copy link
Owner

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.

@YuMingLiao
Copy link

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.

@YuMingLiao
Copy link

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.

@pkamenarsky
Copy link
Owner

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants