Phoenix WebComponents

We’ve been using WebComponents a lot lately at our place of work and sadly using the phoenix socket library threaded through everything is not very reusable, so ended up making a webcomponent of the phoenix socket itself. So we can just do something like this in html:

        <phoenix-socket
          endpoint="/socket"
          param-token="<%= Guardian.Plug.current_token(@conn) %>"
          connected="{{socketConnected}}"
          socket="{{socket}}"
          connect
          ></phoenix-socket>
          EndPoint: "[[socket.endPoint]]"<br />
          IsConnected: "[[socketConnected]]"<br />

And this, in this case, outputs this to the screen:

EndPoint: "/socket/websocket"
IsConnected: "true"

Of course changing any of the properties like endpoint or parameters or disabling ‘connect’ or so will cause it to disconnect and/or reconnect with new parameters, but for a socket that is a rare case anyway.

We can continue to thread to join a topic like:

    <phoenix-channel
      socket="{{socket}}"
      channel="{{channel}}"
      topic-pre="Messenger:"
      topic="system"
      msg-joined="{{msgJoined}}"
      connected="{{topicSystemConnected}}"
      ></phoenix-channel>
    System Topic Joined:  [[topicSystemConnected]] <br />
    Your nick:  [[msgJoined.nick]]

Which outputs this (since the server "Messenger:system" topic returns a structure that contains your nick):

System Topic Joined: true
Your nick: Gabriel Robertson

The {{socket}} here is the same variable that is from the <phoenix-socket />, you can pass in the normal Phoenix library socket object as well if you are using an existing library elsewhere and want to re-use the socket, or you can take this socket and give it to normal javascript code elsewhere. And as expected if you add a sleep or something to the socket join then the ‘true’ above will be ‘false’ until the join finally goes through. It automatically handles new sockets, no sockets, sockets losing and reconnecting its connection, etc… Also handles changing properties will disconnect and reconnect to a new given topic with whatever parameters are specified as expected. You can get various topic information here (like with the socket, the channel has a lot more options that are not demonstrated here, still need to program in more as well).

To handle messages you can use javascript to listen as normal, or continue using html by doing:

<phoenix-msghandler
  channel="{{channel}}"
  key="msg:recv"
  msg="{{receivedMsg}}"
  ></phoenix-msghandler>

And similar for presence as well, takes the channel output from the above <phoenix-channel /> as input, or feel free to pass in your own channel object via javascript. Usual property stuff. Etc…

Do many other people use webcomponents here and might find these useful?

7 Likes

I’ve made a testing SPA that is server driven using webcomponents. The links at the top are normal ‘a’ links (middle-clickable and all), just instead of routing they are matched out and sent to the server via a <phoenix-channel-msg> element. The server sends a map of “importURLs” and “elementName” and optionally “cmds”, the SPA will import those url’s if not already imported then it will create the given element and replace the on-page ‘page’ element with (using css effect to slide out and in, just because I could ^.^) once it fully loaded. There is no flash of unstyled content, everything is fast and easily cachable and mix phoenix.digestable, all communication happens over a phoenix websocket/longpolling, tested in Chrome, Firefox, Edge (the below animation is edge, it is actually a lot smoother than the animation shows), IE11, and Chrome on my android phone:

The Root link is just “/spa”, the Root with slash link is “/spa/” (I was trying to test all cases), Messenger is “/spa/messenger”, and Blah is “/spa/blah”. Blah does not exist on the server so it sends back a thing to display the error module (which I’ve not implemented yet so it falls back to blank and logs a message in the console right now), but messenger displays another module that sets up another phoenix channel connection over the same socket, and the root’s just display the ‘home’ page, which is just some text right now. I have some descriptive text showing the state of some of the bindings.

The useful bit of the html is just this:

    <app-location route="{{fullRoute}}" url-space-regex="[[basePath]]"></app-location>
    <app-route id="baseRoute"
      route="{{fullRoute}}"
      pattern="[[basePath]]"
      tail="{{baseRoute}}"
      active="{{baseRouteActive}}"
      ></app-route>
    <app-route id="route"
      route="{{baseRoute}}"
      pattern="/:name"
      data="{{moduleRouteData}}"
      tail="{{moduleRoute}}"
      active="{{moduleRouteActive}}"
      ></app-route>

    <phoenix-socket id="socket"
      endpoint="/socket"
      param-token="[[paramToken]]"
      connected="{{socketConnected}}"
      socket="{{socket}}"
      connect="true"
      ></phoenix-socket>

    <phoenix-channel
      socket="{{socket}}"
      channel="{{controlChannel}}"
      topic="spa"
      msg-joined="{{controlChannelMsgJoined}}"
      connected="{{controlChannelConnected}}"
      ></phoenix-channel>
    <phoenix-channel-msg
      channel="{{controlChannel}}"
      event="view"
      msg-send="{{moduleRouteData.name}}"
      msg-latest="{{mainViewData}}"
      ></phoenix-channel-msg>

    <app-drawer-layout id="layout" responsive-width="1280px">
      <app-drawer swipe-open>
        <app-header condenses reveals shadow effects="waterfall">
          <app-toolbar style="pointer-events:all;">
            <a main-title href="/" class="site-title">/* Snip */</a>
          </app-toolbar>
        </app-header>
        <div style="height: 100%; overflow: auto;">Testing a drawer</div>
      </app-drawer>
      <app-header-layout>
        <app-header condenses reveals shadow effects="waterfall">
          <app-toolbar style="pointer-events:all;">
            <paper-icon-button icon="menu" drawer-toggle></paper-icon-button>
            <a main-title href="/" class="site-title">/* Snip */</a>
          </app-toolbar>
        </app-header>
        <main role="main">

          <a href="[[basePath]]">Root</a><br />
          <a href="[[basePath]]/">Root with slash</a><br />
          <a href="[[basePath]]/messenger">Messenger</a><br />
          <a href="[[basePath]]/blah">Blah</a><br />

          Token: "[[paramToken]]"<br />
          BasePath: "[[basePath]]"<br />
          Module Name: "[[moduleRouteData.name]]" -> [[_stringify(moduleRouteData)]]<br />
          Module Path: "[[moduleRoute.path]]"<br />
          Socket: "[[socket]]"<br />
          EndPoint: "[[socket.endPoint]]"<br />
          IsConnected: [[socketConnected]] -> [[controlChannelConnected]] -> [[_stringify(controlChannelMsgJoined)]]<br />
          MainViewData: [[_stringify(mainViewData)]]<br />
          ModuleRouteActive:  [[moduleRouteActive]]<br />
          <neon-animated-pages
            id="moduleContainer"
            attr-for-selected="module"
            selected=""
            on-neon-animation-finish="_onNeonAnimationFinish"
            entry-animation="slide-from-right-animation"
            exit-animation="slide-left-animation"
            animate-initial-selection></neon-animated-pages>
        </main>
      </app-header-layout>
    </app-drawer-layout>

And Phoenix is currently routing all “/spa” and “/spa/*path” requests to that same template, the router on the page uses the route to contact the server to determine what to display. I have a few modules though I’m just testing some of the test links in the animation (and removed the work branding and such).

3 Likes

Very cool. I’ve been learning React Native for a small mobile project, but a big-shot friend in San Francisco is pushing me to get into Polymer and WebComponents.

Would be very interested to see this up on GitHub someday.

Those are very different with React Native you are actually using native widgets. React Native is really fun to develop in compared to fully native too.

1 Like

Can you share a public repo? This is very interesting.

I plan to pull out the phoenix socket webcomponents out once I’ve finished well testing them. I seemingly keep finding random little bugs of my own doing every time I stress them, though not in over a week now so they might be good. Still some new features I want to add though, but those are pretty optional, I could release it now, hmm…

2 Likes