Use Google Analytics via Google Tag Manager with Vaadin

Hi guys,
does anyone of you already managed to integrate Google Analytics via Google Tag Manager into your Vaadin App? I’ve managed to track the page view via Analytics, but I have no ideas how to track a specifc event like button click.
Extract from the Google development guidelines:

As an example, to set an event when a user clicks a button, you might modify the button's link to call the push() API as follows:
<a href="#" name="button1" onclick="dataLayer.push({'event': 'button1-click'});" >Button 1</a>

How can I archieve something like this within the Vaadin Java code? I’m known about the Vaadin Google Analytics Plugin, but from my understanding this is not suitable for the Google Tag Manager usecase, or?

Many thanks in advance!

Cheers
Fabian

I’m also looking for the same answer. It works with google analytics but what to do in case I want to use google tag manager?

Have you checked this add-on https://vaadin.com/directory/component/googleanalyticstracker ?

Sorry for the late response. I was able to handle it by my own with the following approach (assuming you are using Spring). With it, you are able to track page visits and any specific click action performed on your page:

  1. Create a custom BootstrapListener which will append the needed JavaScript to your Vaadin page:
public class GoogleTagManagerListener implements BootstrapListener {

    private final String tagManagerId;

    private static final String HEAD = "head";

    GoogleTagManagerListener(String tagManagerId) {
        this.tagManagerId = tagManagerId;
    }

    @Override
    public void modifyBootstrapFragment(BootstrapFragmentResponse bootstrapFragmentResponse) {
        bootstrapFragmentResponse
                .getFragmentNodes()
                .add(getBodyIFrame());
    }

    @Override
    public void modifyBootstrapPage(BootstrapPageResponse bootstrapPageResponse) {
        bootstrapPageResponse
                .getDocument()
                .getElementsByTag(HEAD)
                .get(0)
                .appendChild(getDataLayer())
                .appendChild(getHeaderScript(bootstrapPageResponse.getDocument()));
    }

    private Node getHeaderScript(Document document) {
        @Language("JavaScript")
        String js = "(function (w, d, s, l, i) {\n" +
                "    w[l]
 = w[l]
 || [];\n" +
                "    w[l]
.push({\n" +
                "        'gtm.start':\n" +
                "            new Date().getTime(), event: 'gtm.js'\n" +
                "    });\n" +
                "    var f = d.getElementsByTagName(s)[0]
,\n" +
                "        j = d.createElement(s), dl = l != 'dataLayer' ? '&l=' + l : '';\n" +
                "    j.async = true;\n" +
                "    j.src =\n" +
                "        'https://www.googletagmanager.com/gtm.js?id=' + i + dl;\n" +
                "    f.parentNode.insertBefore(j, f);\n" +
                "})(window, document, 'script', 'dataLayer', " + "'" + tagManagerId + "');";

        return document.createElement("script")
                .append(js);
    }

    private Node getDataLayer() {
        return new Element(Tag.valueOf("script"), "")
                .append("var dataLayer = dataLayer || []");
    }

    private Node getBodyIFrame() {
        return new Element(Tag.valueOf("noscript"), "")
                .append("<iframe src=\"https://www.googletagmanager.com/ns.html?id=" + tagManagerId + "\"\n" +
                        "        height=\"0\" width=\"0\" style=\"display:none;visibility:hidden\"></iframe>");
    }
}
  1. Extend from SpringVaadinServlet and override the servletInitialized method in which you add your custom BootstrapListener:

    ...
    @Override
    protected void servletInitialized() throws ServletException {
        super.servletInitialized();
        getService().addSessionInitListener(this::addBootStrapListenerOnSessionInit);
    }
    
    private void addBootStrapListenerOnSessionInit(SessionInitEvent sessionInitEvent) {
        sessionInitEvent.getSession().addBootstrapListener(new GoogleTagManagerListener(tagManagerId));
    }
    
  2. Create the following JavaScriptExtensions to be able to fire the events to Google Tag Manager. I’ve mainly reused the code from the already existing Vaadin Google Analytics Plugin:

public class GoogleTagManagerState extends JavaScriptExtensionState {

    public boolean allowAnchor = true;
    public String trackerId;
    public String pageId = "none";
}

@JavaScript({"vaadin://tag_extension.js"})
public class GoogleTagManager extends AbstractJavaScriptExtension implements ViewChangeListener {

    private static final String TRACK_EVENT = "trackEvent";
    private static final String TRACK_PAGE_VIEW = "trackPageView";
    private static final String SET_PAGE_ID = "setPageId";

    /**
     * Instantiate new Google Tag Manager by id.
     *
     * @param trackerId The tracking id from Google Tag Manager. Something like
     *                  'GTM-123456'.
     */
    public GoogleTagManager(String trackerId) {
        setTrackerId(trackerId);
    }

    /**
     * Instantiate new Google Tag Manager by id and pageId.
     *
     * @param trackerId The tracking id from Google Analytics. Something like
     *                  'GTM-123456'.
     * @param pageId    The name of the pageId which can be used to push pageView events. Something like 'app/home'
     */
    public GoogleTagManager(String trackerId, String pageId) {
        this(trackerId);
        setPageId(pageId);
    }


    /**
     * Get the page id associated with this tracker.
     *
     * @return The page id.
     */
    public String getPageId() {
        return getState().pageId;
    }

    /**
     * Sets the domain name you are tracking
     *
     * @param pageId The page id.
     */
    public void setPageId(String pageId) {
        getState().pageId = pageId;
        callFunction(SET_PAGE_ID, pageId);

    }

    @Override
    protected GoogleTagManagerState getState() {
        return (GoogleTagManagerState) super.getState();
    }


    /**
     * Sets the Google Tag Manager Id.
     *
     * @param trackerId The tracking id like 'GTM-123456'
     */
    public void setTrackerId(String trackerId) {
        getState().trackerId = trackerId;
    }

    /**
     * Gets the Google Tag Manager Id.
     *
     * @return Tracking id like 'GTM-123456'.
     */
    public String getTrackerId() {
        return getState().trackerId;
    }

    /**
     * This method sets the # sign as the query string delimiter in campaign
     * tracking.
     * <p>
     * https://developers.google.com/analytics/devguides/collection/gajs/methods/
     *
     * @param allowAnchor Are anchors allowed in URIs. This is the default.
     */
    public void setAllowAnchor(boolean allowAnchor) {
        getState().allowAnchor = allowAnchor;
    }

    /**
     * This method sets the # sign as the query string delimiter in campaign
     * tracking.
     * <p>
     * https://developers.google.com/analytics/devguides/collection/gajs/methods/
     *
     * @return true if anchors are allowed in the URIs
     */
    public boolean isAllowAnchor() {
        return getState().allowAnchor;
    }


    /**
     * Track a single page view. This effectively invokes the 'trackPageview' in
     * ga.js file.
     *
     * @param pageId The page id. Use a scheme like '/topic/page' or
     *               '/view/action'.
     */
    public void trackPageview(String pageId) {
        callFunction(TRACK_PAGE_VIEW, pageId);
    }

    /**
     * Track an event. See the Google Tag Manager documentation for more information.
     *
     * @param eventCategory Typically the object that was interacted with (e.g. 'Video')
     * @param eventAction   The type of interaction (e.g. 'play')
     * @see <a href="https://developers.google.com/tag-manager/devguide#events">Google Tag Manager documentation</a>
     */
    public void trackEvent(String eventCategory, String eventAction) {
        callFunction(TRACK_EVENT, eventCategory, eventAction);
    }

    /**
     * Track an event. See the Google Tag Manager documentation for more information.
     *
     * @param eventCategory Typically the object that was interacted with (e.g. 'Video')
     * @param eventAction   The type of interaction (e.g. 'play')
     * @param eventLabel    Useful for categorizing events (e.g. 'Fall Campaign'). Optional.
     * @param eventValue    A numeric value associated with the event (e.g. 42). Optional.
     * @see <a href="https://developers.google.com/tag-manager/devguide#events">Google Tag Manager documentation</a>
     */
    public void trackEvent(String eventCategory, String eventAction, String eventLabel, int eventValue) {
        callFunction(TRACK_EVENT, eventCategory, eventAction, eventLabel, eventValue);
    }


    /**
     * Track an event. See the Google Tag Manager documentation for more information.
     *
     * @param eventCategory Typically the object that was interacted with (e.g. 'Video')
     * @param eventAction   The type of interaction (e.g. 'play')
     * @param eventLabel    Useful for categorizing events (e.g. 'Fall Campaign'). Optional.
     * @see <a href="https://developers.google.com/tag-manager/devguide#events">Google Tag Manager documentation</a>
     */
    public void trackEvent(String eventCategory, String eventAction, String eventLabel) {
        callFunction(TRACK_EVENT, eventCategory, eventAction, eventLabel);
    }

    /**
     * Attach this Tag Manager component to a UI to enable tracking
     *
     * @param target The UI to track
     */
    public void extend(UI target) {
        super.extend(target);
    }

    @Override
    public boolean beforeViewChange(ViewChangeEvent event) {
        return true;
    }

    @Override
    public void afterViewChange(ViewChangeEvent event) {
        trackPageview(event.getViewName());
    }
}
  1. Create an instance of the GoogleTagManager with your custom tagId in your SpringView :
GoogleTagManager gtm = new GoogleTagManager(tagManagerId);
  1. To track something (e.g select something in a ComboBox, do the following)
....
ComboBox<String> comboBox = new ComboBox<>()
comboBox.addValueChangeListener(this::timeRangeChange);
....

    private void timeRangeChange(HasValue.ValueChangeEvent<String> event) {
        String value = event.getValue();
        gtm.trackEvent("yourEventCategory", "yourEventAction", "yourEventLabel");
    }

Additionally to that, you need to put the following JavaScript inside your /webapp/VAADIN folder:

window.com_foo_bar_GoogleTagManager = function () {
    this.setPageId = function (pageId) {
        dataLayer = [{
            pageName: pageId
        }]
    };
    this.trackEvent = function (category, action, label, value) {
        console.debug("trackingEvent: {" +
            "eventCategory: " + category + ", \n" +
            "eventAction: " + action + ", \n" +
            "eventLabel: " + label + ", \n" +
            "eventValue: " + value + "\n" +
            "}");
        console.debug("dataLayer: " + dataLayer);
        dataLayer.push({
            event: 'event',
            eventCategory: category,
            eventAction: action,
            eventLabel: label,
            eventValue: value
        })
    };

    this.trackPageView = function (pageId) {
        console.debug("trackPageView: " + pageId);
        console.debug(dataLayer);
        dataLayer.push({
            pageName: pageId
        })
    };
};

Hi Fabian,

very interesting. Have you make it working with Vaadin 12.x ?

very interesting. Have you make it working with Vaadin 12.x ?

New version (4.0.0) of the add-on for Vaadin 10+ was uploaded to Directory just few days ago.

Hi Louis-Michel,

No, I’ve never tried it with Vaadin > 8. I guess this approach is simply not working with it, as most of those things I’ve done there were needed because of the GWT basement of Vaadin 8.

But I thought starting with Vaadin 10, you can simply write the client side in JavaScript. There, you should be able to use Google Tag Manager simply as it is intended by Google.

Hi Fabian,
So the GTM header/body scripts will only be added to the views/pages where GoogleTagManager is instantiated? I am currently working in Vaadin 8 and I have been asked to add Facebook Pixel tracker via Google tag Manager. In Vaadin 8, will I need to add every trigger event location manually via Java code? Rather than simply adding GTM header/body scripts in say - a jsp and be done?
Thanks,
Leo