I would be nice to be able to use the text from another DOM element in the action, category, or label.

Currently

The code used looks like this.

 	array(
 	  'event' => 'mousedown',
 	  'selector' => '.horizontal-tabs ul.horizontal-tabs-list li a',
 	  'category' => 'Product Page Interaction',
 	  'action' => 'focus',
 	  'label' => '!currentPage', 
 	  'value' => 0,
 	  'noninteraction' => TRUE,
 	),

Maybe

We could change it to something like this:

array(
  'event' => 'mousedown',
  'selector' => '.horizontal-tabs ul.horizontal-tabs-list li a',
  'category' => 'Product Page Interaction',
  'action' => array(
    'selector' => 'h1#page-title',
    'return' => '!text'
  ),
  'label' => '!currentPage', 
  'value' => 0,
  'noninteraction' => TRUE,
 ),

Comments

dbassendine’s picture

I think this would be a great addition to improve the flexibility of the API.

In a simple example, I have a search form with a text field and a submit button. In order to track the search terms used, I would need to trigger the event when the form is submitted, but grab the value from the form field.

The API structure suggested above looks sensible to me. If I'm able to add this feature during some pending client work, would you be interested in rolling it into the module?

Thanks, David

frob’s picture

feel free to post a patch.

patches are very welcome.
:)

dbassendine’s picture

Hey frob,

Thanks for getting back so quickly :)

I've just been taking a look through the module. The hook settings array looks like it's passed directly to the JS Drupal.settings, so it doesn't look too hard to add a second selector and pass an "action" JS object to pull the !text or !href selectors from:

  1. Load the "action" object (js/google_analytics_et.js, Drupal.behaviors.googleAnalyticsET)
  2. Pass the object to "trackEvent"
  3. Pull the !text and !href from the action object (rather than the event one)
  4. Update ....api.php

I realised the change in structure of the hook array would break existing implementations, though, because the location of "action" would change. We could just add another array key called "selector-data" to point to the object to pull all of the tokens from - but it would be more restrictive in that all the tokens would need to come from one object (rather than being able to set different objects for category, action, label, but that's getting quite complicated anyway). This would look like:

array(
  'event' => 'mousedown',
  'selector' => '.horizontal-tabs ul.horizontal-tabs-list li a',
  'selector-data' => 'h1#page-title',
  'category' => 'Product Page Interaction',
  'action' => '!text',
  'label' => '!currentPage', 
  'value' => 0,
  'noninteraction' => TRUE,
 ),

What do you think?

Thanks, David

frob’s picture

I like where your going. I will say that backwards compatibility is very important. At least till v2 (which is very far away).

How about something like this.

array(
  'event' => 'mousedown',
  'selector' => '.horizontal-tabs ul.horizontal-tabs-list li a',
  'selector-data' => array(
    '[token]' => '[selector:h1#page-title]',
  ),
  'category' => 'Product Page Interaction',
  'action' => '!text',
  'label' => '!currentPage', 
  'value' => 0,
  'noninteraction' => TRUE,
 ),

It will require a bit more but in the end this will be far more flexible. Ever sense I wrote the drush integration I have wanted to change the way the tokens are written. There is actually a bug in using the "BANG!" ! for a token passing in bash. Switching to the drupal standard (but keeping the old style around for backwards-compatibility).

Also by doing this we can allow for any number of custom tokens built on DOM. Maybe we should pick another wrapper for the tokens though. CSS uses [] for selectors. For that matter the : is used too. So we cannot switch to drupal standard tokens, unless we wrap the selectors in another wrapper.

Maybe we should have the tokens in drupal's standard token format and have the other section labeled in the example '[selector:h1#page-title]' we could just change that to 'selector : h1#page-title'. Or maybe just use json.

Whats your opinion?

dbassendine’s picture

I like the idea of tokens, and I think you could set the selector and method for each token. Setting the method allows some flexibility for elements where .text() isn't appropriate. For example, on some form fields you'd need to use .val() instead.

Tokens could be (optionally) defined and used instead of the existing !text, !currentPage etc. values where necessary. I don't think the token wrapper matters too much so long as it doesn't cause havoc in Javascript. Afaik, the tokens would be replaced before they ever reach CSS, no? I'll have a play around and see what works.

To build on your suggestion above, you could do something like this:

array(
  'event' => 'mousedown',
  'selector' => '.horizontal-tabs ul.horizontal-tabs-list li a',
  'category' => 'Product Page Interaction',
  'action' => '[title]',
  'label' => '[dropdown]', 
  'value' => 0,
  'noninteraction' => TRUE,
  'tokens' => array(
    '[title]' => array(
      'selector' => 'h1#page-title',
      'method' => 'text',
    ),
    '[dropdown]' => array(
      'selector' => 'form select#dropdown',
      'method' => 'val',
    ),
  ),
 ),

I've put the tokens at the end of the array to indicate they're optional. The idea is you could define tokens if you wanted, but if not everything should just look and work the same way as before.

What do you think?

Thanks, David

frob’s picture

Yes, I like that very much. It also allows for greater flexibility. If the implementation worked in a way that it didn't need to know that the methode existed when until the event fired, then this could o things that we don't necessarily intend. Which is good.

What I mean is that someone could add a lib that adds a methode that the api doesn't know about -- the api will just check to see if it exists and fire it if it does; if it doesn't exist then maybe send a message to the console.

I should probably move all the debug alerts to the console.

dbassendine’s picture

Version: 7.x-1.x-dev » 6.x-1.x-dev
StatusFileSize
new4.37 KB

Here's a patch for this. It's currently against 6.x-1.x-dev, unfortunately, since I'm building against a 6.x build for this work - but hopefully it can be transferred relatively quickly. It's tested and working well for me. Some notes:

  • Using %token notation for the custom tokens - brackets cause issues, and this seems consistent with the !token notation for the standard tokens - see below for an example
  • Existing token detection for standard tokens was a little unwieldy when I came to extend it, so I've reworked it in what I hope it clearer code with less duplication
  • Existing filter_xss couldn't handle parsing the tokens sub-arrays, so I've updated this in a way that's specific to tokens - we could use a more generic recursive array function here but I haven't had the time to develop this and it doesn't seem necessary right now
  • No api.php or example module updates yet

An example I'm using, to track a search form submission:

   array(
      'event' => 'submit',
      'selector' => '#header-group form#search-theme-form',
      'category' => 'ISS',
      'action' => '%searchterm',
      'label' => '!currentPage',
      'value' => 0,
      'noninteraction' => TRUE,
      'tokens' => array(
        '%searchterm' => array(
          'selector' => '#header-group form#search-theme-form input#edit-search-theme-form-1',
          'method' => 'val',
        ),
      ),
    ),

Let me know how this looks for you - it would be great to get this tested!

Thanks, David

jelo’s picture

Component: Code » API

This is AWESOME. Exactly what I needed. David, I actually have the same use case, i.e. tracking a search form submission. I applied the patch and it works very well. I don't know much about jquery so I am not sure how to help test it, with the exception of saying that it works in my D6 site.

My other use case is related to Views. Views allows us to expose filters and I would like to properly track selections by users. With the original module code I managed to get it working this way:

  • Views page with 6 exposed filters
  • Most of the filters are drop downs
  • Triggering on event "change" allowed tracking selection within a single filter

However, this had several limitations. Using "change" does not mean that a user actually submitted the form. It would track any accidental change to any element and not only the selection that was made at submit time. And it did not allow grouping of filters. While I was able to track the filters individually, I was not able to identify if a single user might have applied 4 of the 6 filters at the same time (at submit).

How would you implement that with your patch? I used:

   array(
      'event' => 'submit',
      'selector' => '#views-exposed-form-Programs-page-2',
      'category' => 'Programs',
      'action' => '%filteritem',
      'label' => 'Filter: Faculty',
      'value' => 0,
      'noninteraction' => TRUE,
      'tokens' => array(
        '%filteritem' => array(
          'selector' => 'select#edit-fid option:selected',
          'method' => 'html',
        ),
      ),
    ),
   array(
      'event' => 'submit',
      'selector' => '#views-exposed-form-Programs-page-2',
      'category' => 'Programs',
      'action' => '%filteritem',
      'label' => 'Filter: Subject',
      'value' => 0,
      'noninteraction' => TRUE,
      'tokens' => array(
        '%filteritem' => array(
          'selector' => 'select#edit-sid option:selected',
          'method' => 'html',
        ),
      ),
    ),

This setup results in 2 events (# of filters) when the form is submitted. Should it not be 1 event (# of submits) with multiple actions (# of filters applied) attached to it? I need to think about how/if GA can track this...

Other observations:

  • Value vs. label: in your example you used action to store the search term. For consistency, should that not rather be the label (https://developers.google.com/analytics/devguides/collection/gajs/eventT...)? Obviously, anybody can change this to whatever they need, but if this example goes into the example module, maybe one or the other way has advantages/disadvantages?
  • Removing the catch all option? Views allows to show a select "ANY" option. In my example this gets recorded on each submit rather than only when an actual selection has been made. The same applies to a text field (option "contains" in views to allow a keyword search) which gets recorded even it is empty and one of the other filters was applied. Can I exclude that from happening?

Please let me know what I can do to move this forward. I would love to see this committed to 6 and 7 versions as I have sites on both versions for which this feature would be very helpful!

Cheers, J.

jelo’s picture

Re: value vs. label

The count in GA is based on label. If you use the current page as label, GA would show you how often people have searched from that page. If you use the search term as label, GA would show you how often users have searched for the same term on any page... In the regular event tracking, label is always the most specific/granular option, e.g.

  • Downloads: Actions = PDF, DOC, XLS etc. and Label = link to file
  • Outbound links: Action = Click and Label = target URL
  • Mails: Action = Click and Label = email address

New issue:
If views "reset filter" button is enabled, it triggers a submit and the filters are counted twice (actually with the same values as you are resetting them from) which messes up the data.

frob’s picture

Why is the reset filter button an issue? Just target the submit button only.

jelo’s picture

Right now the clear button in views is implemented as
<input type="submit" name="op" id="edit-reset" value="Clear" class="form-submit">

I guess you are suggestion to not use event "submit", but "click" on the button itself? I will try that tomorrow.

jelo’s picture

Title: Allow Tokens from other DOM ellements » Allow Tokens from other DOM elements

Thanks for the hint to target the button only. That was an easy solution and works well ;-)

I adjusted the array to be

      'event' => 'click',
      'selector' => '#views-exposed-form-Programs-page-2 input#edit-submit-Programs',

in case someone else is reading this thread.

Frob, may I ask what the next steps are to get this patch committed? I would be happy to try to help...

jelo’s picture

Issue summary: View changes

reformatted

ultimike’s picture

Issue summary: View changes

I'm pretty sure that the _google_analytics_et_sanitize_event_trackers() as re-written in the patch above does not adequately protect against XSS. I went ahead and updated it for Drupal 7 as follows below:

/**
 * Sanitizes event tracking data that is being put into javascript.
 */
function _google_analytics_et_sanitize_event_trackers($selectors) {
  $sanitized_selectors = array();

  if (is_array($selectors)) {
    foreach ($selectors as $key => $selector) {
      if ($key !== 'debug') {
        foreach ($selector as $selector_key => $s) {
          // Normal parameters
          if (!is_array($s)) {
            $s = filter_xss($s);
            $selector[$selector_key] = $s;
          }
          else {
            // Tokens subarray
            foreach($s as $token_key => $token) {
              $selector[$selector_key][$token_key]['selector'] = filter_xss($selector[$selector_key][$token_key]['selector']);
              $selector[$selector_key][$token_key]['method'] = filter_xss($selector[$selector_key][$token_key]['method']);
            }
          }
        }
      }
      $sanitized_selectors[$key] = $selector;
    }
  }
  return $sanitized_selectors;
}

Also, if anyone is interested, I incorporated this change into my latest patch for the Google Analytics Event Tracking UI module #1994320-11: User Interface submodule.

Thanks,
-mike

lee20’s picture

I needed this functionality for D7, so here is a patch for D7 that resembles the patch in #7

joelpittet’s picture

Version: 6.x-1.x-dev » 7.x-1.x-dev
Status: Active » Needs review
StatusFileSize
new4.88 KB

Took the changes from #13 which look correct because the sanitization wasn't actually happening in HEAD.

Re-rolled #14 with the changes from #13 + a couple coding standards fixes in the introduced patch code.

There may need some more work here but give this a try.

joelpittet’s picture

StatusFileSize
new7.79 KB
new6.08 KB

There was a leftover $ in there and it's not wrapped in a closure so just put in jQuery.

Added tokens to the debug output and fixed a Namespace issue with {string:value} with string as the key.

jelo’s picture

Status: Needs review » Reviewed & tested by the community

The latest patch actually makes the tokens resolve correctly. This is exactly what I needed. Works like a charm...

The last submitted patch, 7: google_analytics_et-tokens-1778250-0.patch, failed testing.

The last submitted patch, 14: google_analytics_et-d7-tokens-1778250-1.patch, failed testing.