Jacob Lowe

Published ● 4 min read

Harness the back button.


Sometimes the product has some interesting ideas, and they are not always in line with how a platform works. My team at Agent Ace got tasked to have a multi-step page that has a single URL. Simple right? Yeah, pretty simple in today’s single-page web. One thing tho, it needs to also go back a step when the back button is clicked. There is a couple of ways to “fix” this: query strings or hash URLs are the most common. Technically it is the same URL but there is just different data appended to it. There is a least one other solution that is pretty sweet.

Flashback to my prior job, Hone Inc, where our product was mostly in an iframe. I notice that the iframe was actually writing to the parent windows history. So when the back button was clicked on the browser the content in the iframe would navigate back. This was not completely unexpected for me, but it is certainly interesting browser behavior. With this knowledge of browsers history, I decided to experiment.

The experiment

My experiment was to be able to make an iframe that I could pass data to, via the query string, and it would echo that data out.

<!DOCTYPE html>
<title>Echo Iframe</title>
<meta charset="utf8" />
<script>
  var keyValues = (location.search || '').split('?').pop().split('&'),
    query = {},
    keyValue

  // echoing back the query data to the parent window
  function sendMessage(message) {
    var target = location.protocol + '//' + location.host
    if (parent) {
      parent.postMessage('iframe:' + JSON.stringify(message), target)
    }
  }

  // parsing the query
  for (var i = 0; i < keyValues.length; i += 1) {
    keyValue = keyValues[i].split('=')
    query[keyValue[0]] = keyValue[1]
  }

  sendMessage(query) // send it
</script>

So after putting this HTML inside of an iframe, I had a good baseline for testing my first experiment. Now I just had to make the parent listen to the iframe’s messages.

<!DOCTYPE html>
<title>Echo Iframe</title>
<meta charset="utf8" />
<!-- assumes echo is in same directory as parent.html -->
<iframe id="echo" src="./echo.html?state=1"></iframe>
<script>
  window.iframe = document.getElementById('echo')
  window.addEventListener('message', function (e) {
    if (e && typeof e.data === 'string' && e.data.match(/^iframe:/)) {
      var data = e.data.split('iframe:').join('')
      console.log(JSON.parse(data))
    }
  })
</script>

This was all I needed for the parent window for this experiment. The next step was to dynamically change the iframe’s src URL and see if the data was correct. I did this by grabbing the iframe in my console and changing the src to the same URL with a different query string. So I served up both of these pages with a static server and tried it out.

Success!

demo of it working

This was pretty basic post message stuff and also allowed me to then click the back button and see that the iframe was reloading and spitting out the previous query stings data. This was great to see because it meant I could write to the parent’s history by navigating an iframe. Then once the back button is clicked, on the browser, the iframe would reload and echo out the previous state to the parent window.

You can get the experiment code here!

Hooking it into the app

Hooking this into our app was tricky we have normal navigation and iframe navigation going on. We ended up placing the code in a Router since it’s routing an iframe, and then pushing data to a view based on the state. There is so many ways to do this, and your app/framework will already have a way it wants to render views based on a route. The basics of it are to listen to the state change, apply that state to the view.

Pitfalls

This is great to preserve the URL or your app by allowing native navigation functionality, but that is not always optimal. We were building steps in a funnel that did not need the ability to share or save the current state so we could get away with using this method. If you do want the ability to copy the URL and share it. Do not use this method, it’s not good for a lot of situations.

This seemed to work cross-browser, but there was one place that it was an issue. Which was Chrome for IOS, it seems to not be fixable with the current state of browsers on IOS. Bug ticket. I would suspect this would be similar on other 3rd party browsers for IOS as well.

Discuss it on Twitter Edit on Github