Poor man's realtime

Streaming to browsers has got a lot easier with the advent of APIs like WebSockets and Server-sent Events. However the server-side aspect is often trickier to setup--many servers and runtimes are not tuned for long-lived requests.

There's a much simpler approach to 'realtime' updates which can be applicable in some situations; polling the page's endpoint and updating the body's HTML. I put realtime in quotes because this is not strictly realtime. However it's a good enough technique for a lot of cases. Indeed we're using it to update a progress bar and list of results in Mailmatch.

All we need to do is set up an interval to re-fetch the page's HTML every five seconds, parse the response HTML looking for a specific element, and then replace the page's corresponding element with the updated one from the server.

reloader.coffee

$ = jQuery

class @Reloader  
  constructor: (@selector = '.wrapper', @path, @interval = 5000) ->
    @start()

  start: =>
    @timer = setInterval(@fetch, @interval)

  stop: =>
    clearInterval(@timer)

  fetch: =>
    $.get(@path).done(@render)

  render: (data) =>
    $body = $(@parseBody(data))

    if @selector is 'body'
      $el = $body
    else
      $el = @findAll($body, @selector)

    $(@selector).replaceWith($el)

  parseHTML: (html) =>
    $.parseHTML(html, document, false)

  parseBody: (data) =>
    @parseHTML(data.match(/<body[^>]*>([\s\S.]*)<\/body>/i)[0])

  findAll: ($elems, selector) ->
    $elems.filter(selector).add($elems.find(selector))

Notice we're having to parse out the <body> tag using a regex--document fragments don't like parsing out doctypes. Also notice that by default, @path is undefined -- jQuery's Ajax request will just fetch the page's current path and query parameters.

To use the library, simply instantiate it on document ready, passing in a selector to the element that you want to constantly update.

$ -> new Reloader('.el-wrapper')

This technique is better than full page reload since it's a lot quicker - it doesn't entail fetching all the styles and scripts in the header. It also has the advantage of localizing updates to a specific element.