So looking at the likes of Crossbar and Autobahn, it's easy to say "yeah, looks great, but it's quite complicated, and it's going to take a while to integrate into what we have, or indeed re-write what we have ...". Well, what if you could roll this level of technology into your current application, pretty much without changing anything?

If this were possible, not only does it make the use of next generation technology more realistic, but it also presents the option of producing products that can be rolled out and made available to others, who would be able to make use of it on the same basis, i.e. just add to what's already there.

So .. enter "the Widget".

Widgets are a great way of wrapping up little bits of UI based functionality into manageable chunks that can be re-used by others, but often Widgets carry their own baggage in terms of dependency and environmental requirements. For example, does the Widget support my framework, does it need other modules, does it require a complicated configuration, can I make it work "with" my code, etc.

But (!) what if you could produce a simple Widget that would literally wrap an entire application?

What I've tried to do here is demonstrate how to write a reactive Vue.js application based on AutobahnJS, and present it as a generic Javascript widget with a single .CSS import and a single .JS import, that can be instantiated via a custom HTML tag. So for example, my index.html looks like this;

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
    <link href=css/app.css rel=stylesheet>
    <title>demo-widget</title>
  </head>
  <body>
    <vue-widget
      url='wss://(my_domain)/ws'
      user='my_user'
      pass='my_password'
      realm='my_realm'>
    </vue-widget>
    <script src=js/app.js />
  </body>
</html>

The idea here is that you can include app.css and app.js in any web page or application, then instantiate one or more vue-widget tags at will with your own parameters. In this instance my demo includes AutobahnJS, Vue.js, Vuex, Bulma and JsPanels, to provide a real-time / reactive presence indicator in a floating Window.

The initial presentation of the Widget is via a radial menu that appears as a "Plus" sign in the bottom right of the screen, this could obviously be any kind of button placed anywhere on the screen, but in this instance clicking on the "Plus" presents three further options.

  • The 'Home' icon just demonstrates a Bulma toaster message
  • The 'Coffee' icon shows an "about box"
  • The 'Binoculars' icon will popup the reactive Autobahn driven Widget

So the active Widget is essentially an Autobahn connection to your server of choice, which will grab a list of active sessions and store them in a Vuex store. It will also subscribe to "on_leave" and "on_join", which will update the store as sessions are deleted and created.

So to test this, just access the demo page from multiple tabs, expand the menu and hit the Binoculars. On selecting this icon, the widget will make an Autobahn connection to the server which should update this display on any / all other tabs in which it's running.

So, how hard is this?

Well realistically, not nearly as difficult as you might imagine, below I'll cover how this is done, but I'm going to assume a degree of familiarity with modern JS based frameworks and in principle what "Vue" is.

First you'll need to create a "Vue" application, so assuming you have the Vue CLI (v3) installed (there's plenty of documentation on how to do this) you should be able to do;

vue create demo-widget

It will ask you a bunch of boilerplate questions, typically you will want "Vuex" and you won't want "router" (as a Widget, you don't really want 'router' messing with your URL as this might affect your host application) Then you'll need a few other libraries;

npm i --save vue-custom-element
npm i --save document-register-element
npm i --save webpack

Next you need to change your main.js to tell it to create a Widget rather than an application, so, make a copy of your main.js, then paste this in;

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store/index'
import vueCustomElement from 'vue-custom-element'
import 'document-register-element/build/document-register-element'
Vue.use(vueCustomElement)
App.store = store
App.router = router
Vue.customElement('vue-widget', App)

Next, in your index.html, replace your main "app" div with your new custom widget (<vue-widget></vue-widget> or whatever your rename your tag to be) and you should be good to go. To check everything works, do;

npm serve run

This will activate a local webserver and show you it's URL, if you access this URL, it should display the default "Vue" template application. (if it doesn't, time to back-track as something's gone wrong!)

To build the target files for distribution you can then do;

npm run build

This will produce the desired files in the "dist" folder. Now this may produce more than one file, so you will need to make a slight modification to the webpack configuration by adding vue.config.js to your project root folder, this will ensure that only one .CSS and one .JS file are produced.

const webpack = require('webpack')
module.exports = {
  filenameHashing: false,  
  configureWebpack: {
    plugins: [
      new webpack.optimize.LimitChunkCountPlugin({
        maxChunks: 1
      })
    ]
  },
  chainWebpack:
    config => {
      config.optimization.delete('splitChunks')
  }
}

If you run the build again,  you should see one css and one js file in dist, which can then be used as per the index.html file at the top of this page. (or embedded within an existing page or application via a similar method)

All you need to do now is write something useful to go "in" the widget.

My demonstration application is very simple, but does demonstrate how to integrate complex third party libraries and make them do something practical, it's not designed to be a template of "how" to write a Widget or "how" to code. Any moderately well behaved Vue.js application should work so long as it appreciates it's working 'with' an external page or application, the logic and implementation are up to you!

The repository for my demo application can be found here;

https://gitlab.com/oddjobz/demowidget

A bit more Tech

So how big are Widgets like this? Well I guess it depends on how much code you write, but my demo (which includes a bunch of substantial libraries including, but not limited to, Vue, Vuex, Autobahn, Bulma, FontAwesome etc) looks like this when built;

File                Size                        Gzipped
dist/js/app.js      785.80 KiB                  231.24 KiB
dist/css/app.css    278.17 KiB                  34.79 KiB

CSS Integration

This is likely to be a little different for each project, but relatively smooth interaction can be achieved. If you take a look at Menu.vue you will find a "scss" style section near the bottom which controls how the "Bulma" framework is tuned to deal with other frameworks. i.e. we define a number of global styles and variables, then scope the majority of the Bulma framework within a couple of defined classes. If the CSS that Bulma (or the framework of your choice) needs to be "global" and some of this conflicts with your host page / application, then you will have an issue .. but such is the life of non-namespaced CSS ...

Demonstration

You should see a large "Plus" icon in the bottom right of this screen, this is implemented by using the code-injection feature on Ghost (i.e. the bogging software in use here) to inject app.css into the page header, then app.js and <vue-widget/> into the footer. (i.e. no code required) Note that each click on the Binoculars opens a new Autobahn connection, these are not closed by design in order to let you see how connections can accumulate and are displayed. If you minimise the connection table, then restore or maximise, this does not open a new connection.

Click Here to open another instance of the page / another connection. (the connection won't be made until you click on the binoculars on the new page)