Nullable objects using JavaScript proxies

 Reading time ~3 minutes

Heads up: this article is over a year old. Some information might be out of date, as I don't always update older articles.

Introduction

JavaScript proxies are an ES6 (aka ES2015) feature that lets you wrap an existing object and intercept any access to its attributes or methods, even if they do not exist.

var proxy = new Proxy({ name: "John Doe" }, {
    get: function(target, property) {
        if (property in target) {
            return target[property];
        } else {
            return "Nope!";
        }
    }
});

console.log(proxy.age);         // "Nope!"
console.log(proxy.name);        // "John Doe"
console.log(proxy.title);       // "Nope!"

As you can see the proxy intercepts (technically “traps”) the operation intended for the target object and allows you to perform your own behavior.

The syntax of a Proxy is really easy:

var p = new Proxy(target, handler);
  • target is the Object that gets wrapped by the proxy
  • handler is the placeholder object which contains traps

Notice: A complete list of traps is available on the MDN Page.

So how can we use Proxies to our own advantage?

Context

At work we are building a huge Single Page Application and we needed to integrate the Matomo (formerly Piwik) analytics platform for tracking our users.

In a standard website it is enough to include the JavaScript tracker for tracking page visits, but in a SPA it is necessary to explicitly call the required methods in order to track when the user is changing page or performing an additional action, for example:

Piwik.getTracker().trackPageView(document.title);
// or
Piwik.getTracker().trackLink("http://...");

In a SPA built with VueRouter it is possible to perform the trackPageView automatically, just by attaching the method to the afterEach hook

router.afterEach((to, from) => {
    document.title = to.meta.title;

    Piwik.getTracker().trackPageView(document.title);
});

The Problem

It is obvious that we don’t want to track anything with Piwik when the application is in development or in debug mode. However if we don’t include the Piwik tracker when developing the application we will get the error Uncaught ReferenceError: Piwik is not defined. Neither it is feasible to scatter conditionals all across the application, just to check if Piwik is defined in window before calling methods on it, as it is used in many different parts of the application.

if (Piwik) Piwik.getTracker().trackPageView(document.title);

The Solution

So the first idea that came to my mind was to replace the Piwik object with a null object, an entity that could receive any method call or any attribute access without breaking the application. A Proxy is the perfect fit for this purpose.

The following is the code that we came up to. If the application is in production the regular Piwik tracker is injected in the page, otherwise the Piwik object is replaced with a Proxy that traps every method call (logging it to the console) and returns the same instance of the Proxy, so that it is possible to chain methods on it.

@if(app()->environment("production") and ! config()->get("app.debug"))

    <!-- Matomo -->
    <script type="text/javascript">
        var _paq = _paq || [];
        _paq.push(['trackPageView']);
        _paq.push(['enableLinkTracking']);
        (function() {
            var u="//domain.com/piwik/";
            _paq.push(['setTrackerUrl', u+'piwik.php']);
            _paq.push(['setSiteId', '<site id>']);
            var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
            g.type='text/javascript'; g.async=true; g.defer=true; g.src=u+'piwik.js'; s.parentNode.insertBefore(g,s);
        })();
    </script>

@else

    <script>
        Piwik = new Proxy({}, {
            get: function(target, name) {
                return function (...args) {
                    console.log("[Piwik]: " + name + JSON.stringify(args));

                    return Piwik;
                };
            }
        });
    </script>

@endif

The result is wonderful. We get the benefit to check for free what get’s tracked by Piwik without moving away by the application:

[Piwik]: getTracker[]
[Piwik]: trackPageView["My awesome app"]

Warninng: You should be aware that Proxies are not available in IE11. In our case this is acceptable because they are used only in development and not in production.


References:

  1. http://2ality.com/2017/11/proxy-method-calls.html
  2. http://www.zsoltnagy.eu/es6-proxies-in-practice/
  3. https://gidi.io/2016/02/07/using-es6-s-proxy-for-safe-object-property-access.html
comments powered by Disqus

Handling breadcrumbs with VueX in a VueJS Single Page Application

Introduction

At work we are building a complex Single Page Application with VueJS, VueX and VueRouter. As soon as it started …