Wesley Bakker

Interesting things I encounter doing my job...

Sponsors

News

Wesley Bakker
motion10
Rivium Quadrant 151
2909 LC Capelle aan den IJssel
Region of Rotterdam
The Netherlands
Phone: +31 10 2351035

(feel free to chat with me)
 

Add to Technorati Favorites

March 2010 - Posts

Google Analytics and jQuery, happy together

Google Analytics is great out of the box already, but you can do much more than just registering your page loads. Especially with all these “Web 2.0” sites it can be convenient to not register page loads, but events! In this blog post I’ll show you how you can use jQuery in combination with Google Analytics to get a great insight on what actually happens on your website while you’re not looking!

Tracking Events

With Google Analytics you can track custom events if you like. This gives you the power to register f.e.:

  • Who’s been hovering over my “Buy” button?
  • Who’s been clicking the “Download” link?
  • Who’s been clicking the jQuery.UI.Tab?
  • Who’s been clicking the advertisements?
  • Who’s been clicking on which external links?

The Google Analytics method to register these events is:

pageTracker._trackEvent(category, eventType, optional_label, optional_value) 

optional_label is useful for filtering and optional_value is somewhat special. If you pass an integer to the value parameter, Google analytics will aggregate the value for you. So if you have an ad campaign, you can keep track of how many people clicked the advertisements AND you can see right away how much money you’ve earned with that.

There’s a nice explanation on the _trackEvent method over here: http://code.google.com/apis/analytics/docs/tracking/eventTrackerGuide.html

jQuery Plugin

It’s quit cumbersome however to attach an event handler to all these items on your page. So I’ve created a jQuery plugin to make life easier for you and in it’s simplest form you can use it like this:

$("selector").trackEvent(optional_options);

What this does is that it will call _trackEvent if someone clicks the selected elements once per element. It will use the element nodeName as category variable and the href, value, id or text (first non empty in that order) as label variable. For the value variable it uses 1.

So if my html looks like this:

<script type="text/javascript">
$(function(){
  $("a").trackEvent();
});
</script>

<a href="http://www.virtuelekassa.net">
     ASP.Net iDeal library</a>
<a href="http://www.virtuelekassa.net/downloads/library.zip">
     Download Library</a>

Than if someone clicks the first hyperlink, the event gets tracked like this:

pageTracker._trackEvent("A",
                        "click",
                        "http://www.virtuelekassa.net",
                         1);

If someone clicks the second link, it will be tracked like this:

pageTracker._trackEvent("A",
                        "click",
                        "http://www.virtuelekassa.net/downloads/library.zip",
                         1);

These events will be tracked only once per element click per page load.

External links

This of course can be very helpful in registering how many people navigate away from your web site through external links. So I’ve added an extra filter expression to select all external links on the page. You can use it like this:

$(function(){
    $("a:external").trackEvent();
});

With this in place, all hyperlinks on your web site that point to external web sites get the event tracking behavior.

Flexible

Although the defaults are fine for me, they might not be for you, so the plugin is flexible enough to adjust it to your likings. The optional options variable looks like this:

var settings = {
    eventType : string,
    once      : bool,
    category  : string or function,
    action    : string or function,
    label     : string or function,
    value     : int or function,
};
Example 1

Lets say you would like to track every click on your jQuery.UI.Tab, with:

  • “Tab” as the category value
  • The text of the tab as label value

Your jQuery load function would look somewhat like this:

var eventTrackingOptions = {
    once    : false,
    category: "Tab",
    label   : function(event){ return $(this).text(); }
};

$(function(){
    $("#tabContainer li").trackEvent(eventTrackingOptions)
});
Example 2

Lets say you have a nice picture of your boy- or girlfriend on your page and would like to track every mouseenter AND click on this lovely picture.

Your jQuery load function would look somewhat like this:

var eventTrackingOptions = {
    eventType: "mouseenter click",
    once     : false,
    category : "Predators",
    label    : "My sweet hearth"
};

$(function(){
    $("img.myLoverPic").trackEvent(eventTrackingOptions)
});
Example 3

Lets say you have some advertisements on your page, and you get payed 2 cents on each click. You can now keep track yourself on how much much money you are making like this.

<script type="text/javascript">
var eventTrackingOptions = {
    category: "Advertisements",
    label   : function(event){ return $(this).attr("rel"); },
    value   : 0.02
};

$(function(){
    $(".advertisementLink").trackEvent(eventTrackingOptions)
});
</script>

<!-- some html -->
<ul>
    <li>
        <a class="advertisementLink"
           rel="CompanyOne"
           href="http://companyone.com">Buy here!</a></li>
    <li>
        <a class="advertisementLink"
           rel="CompanyTwo"
           href="http://companytwo.com">Or here!</a></li>
</ul>

The Source

I guess you’re all bored by now and curious about the source… so here it is:

 /** 
  * Version 1.0
  * March 27, 2010
  *
  * Copyright (c) 2010 Wesley Bakker
  * Licensed under the GPL licenses.
  * http://www.gnu.org/licenses/gpl.txt
  **/
(function($) {
    var methods = {
        getOptionValue: function(value, elem, event) {
            if ($.isFunction(value)) {
                value = value.call(elem, event);
            }

            return value;
        },
        getCategory: function() {
            return this.nodeName;
        },
        getAction: function(event) {
            return event.type;
        },
        getLabel: function() {
            var self = $(this);
            if (self.is("a")) {
                return self.attr("href");
            }
            else if (self.is("input")) {
                return self.val();
            }
            else if (self.attr("id")) {
                return self.attr("id");
            }
            else {
                return self.text();
            }
        }
    };

    $.expr[':'].external = function(elem) {
        return (elem.host && elem.host !== location.host) === true;
    };

    $.fn.trackEvent = function(options) {
        var settings = {
            eventType : "click",
            once      : true,
            category  : methods.getCategory,
            action    : methods.getAction,
            label     : methods.getLabel,
            value     : 1
        };

        if (options) $.extend(settings, options);

        this.each(function(i) {
            var eventHandler = function(event) {
                var category = methods.getOptionValue(settings.category, this, event);
                var action   = methods.getOptionValue(settings.action  , this, event);
                var label    = methods.getOptionValue(settings.label   , this, event);
                var value    = methods.getOptionValue(settings.value   , this, event);

                //alert(category + "||" + action + "||" + label + "||" + value);
                pageTracker._trackEvent(category, action, label, value);
            };

            if (settings.once) {
                $(this).one(settings.eventType, eventHandler);
            }
            else {
                $(this).bind(settings.eventType, eventHandler);
            }
        });

        return this;
    };
})(jQuery);

Conclusion

With this straight forward jQuery plugin it’s a piece of cake to keep track of what’s happening on your page while you’re not there.

Cheers,

Wes

Get rid of the style="border-width:0px" from the Image tag.

I do have pet projects in which I try to get every single nitty gritty detail right. And then it bothers me that by default the ASP.NET Image control adds a style="border-width:0px" to the rendered image tag even though I never asked for it. Not even does it add the style attribe without asking, it doesn't offer a way to get rid of it! You can get rid of it though!

ControlAdapters

Fortunately ASP.NET does come with control adapters, so we can do something about it. Lets first create a new .browser file in the App_Browser directory to tie up our control adapter to the rendering of the Image control. The contents should like lik this:

<?xml version="1.0" encoding="utf-8" ?>
<browsers>
  <browser refID="Default">
    <controlAdapters>
      <adapter
        controlType="System.Web.UI.WebControls.Image"
        adapterType="WMB.VirtueleKassa.WebControls.ImageAdapter" />
    </controlAdapters>
  </browser>
</browsers>

Next thing to do is to create the WMB.VirtueleKassa.WebControls.ImageAdapter. Create a new class file in the App_Code directory. The code for the ImageAdapter should look like this:

!! have a look at the comment of RichardD! He proposed a better solution !!

using System;
using System.IO;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.Adapters;

namespace WMB.VirtueleKassa.WebControls { 
    public class ImageAdapter : WebControlAdapter {
        public ImageAdapter() {
        }

        protected override void RenderBeginTag(HtmlTextWriter writer) {
            Image img = this.Control as Image;

            if (img.BorderWidth.IsEmpty) {
                string origTag = string.Empty;

                using (StringWriter sw = new StringWriter())
                using (HtmlTextWriter hw = new HtmlTextWriter(sw)) {
                    base.RenderBeginTag(hw);
                    hw.Flush();
                    hw.Close();

                    origTag = sw.ToString();
                }

                string newTag = origTag.Replace("border-width:0px;", "");
                newTag = newTag.Replace(" style=\"\"", "");
                writer.Write(newTag);
            }
            else {
                base.RenderBeginTag(writer);
            }
        }

        protected override void RenderEndTag(HtmlTextWriter writer) {
        }

        protected override void RenderContents(HtmlTextWriter writer) {
        }
    }
}

And thats it! If you do apply a width yourself, the tag will be saved, and so will any other style tags. Only if, after the deletion of the border-width:0px; the style attribute remains empty, the complete style attribute will be removed as well. Have a look at the source of this page to see the result.

Regards,

Wesley

Posted: Mar 22 2010, 11:23 AM by webbes | with 8 comment(s)
Filed under:
Remove page flicker in IE8

In this pet project of mine I have two large background images. Unfortunately this means that IE will display a nasty page flicker on each request. I used to get rid of this with the a metatag that looks like this:

    <!--[if IE]>
        <meta http-equiv="Page-Exit" content="blendTrans(Duration=0.0)" />
    <![endif]-->

For some reason however, this doesn't work in IE8 with cached pages. Use this tag instead and it all works fine again.

    <!--[if IE]>
        <meta http-equiv="Page-Exit" content="Alpha(opacity=100)" />
    <![endif]-->

Cheers,

Wes

Asynchronous DataContext

Triggered by a blog post of Scott Hanselman I was wondering if I could create a generic extension method to asynchronously retrieve objects from a DataContext. And well I could. I ended up with two classes.

The ExecuteAsyncState:

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Data.Linq;
   4:  using System.Data.SqlClient;
   5:  using System.Threading;
   6:   
   7:  namespace AsyncDataAccess {
   8:      public class ExecuteAsyncState<T> {
   9:          public ExecuteAsyncState(DataContext dataContext, SqlCommand sqlCommand, Action<IEnumerable<T>> onReady) {
  10:              this.DataContext = dataContext;
  11:              this.SqlCommand = sqlCommand;
  12:              this.OnReady = onReady;
  13:              this.WaitHandle = new AutoResetEvent(false);
  14:          }
  15:   
  16:          public DataContext DataContext { get; private set; }
  17:          public SqlCommand SqlCommand { get; private set; }
  18:          public Action<IEnumerable<T>> OnReady { get; private set; }
  19:          public AutoResetEvent WaitHandle { get; private set; }
  20:      }
  21:  }

And the DataContextUtility:

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Data.Linq;
   4:  using System.Data.SqlClient;
   5:  using System.Linq;
   6:   
   7:  namespace AsyncDataAccess {
   8:      public static class DataContextUtility {
   9:          public static IAsyncResult ExecuteAsync<T>(this DataContext dataContext, IQueryable<T> query, Action<IEnumerable<T>> onReady) {
  10:              SqlCommand sqlCommand = dataContext.GetCommand(query) as SqlCommand;
  11:   
  12:              ExecuteAsyncState<T> asynchState = new ExecuteAsyncState<T>(dataContext, sqlCommand, onReady);
  13:   
  14:              AsyncCallback callback = new AsyncCallback(EndExecuteAsync<T>);
  15:              return sqlCommand.BeginExecuteReader(callback, asynchState);
  16:          }
  17:   
  18:          private static void EndExecuteAsync<T>(IAsyncResult result) {
  19:              ExecuteAsyncState<T> asynchState = result.AsyncState as ExecuteAsyncState<T>;
  20:   
  21:              DataContext dataContext = asynchState.DataContext;
  22:              SqlCommand sqlCommand = asynchState.SqlCommand;
  23:              Action<IEnumerable<T>> onReady = asynchState.OnReady;
  24:   
  25:              SqlDataReader reader = sqlCommand.EndExecuteReader(result);
  26:              var resultData = from item in dataContext.Translate<T>(reader)
  27:                               select item;
  28:   
  29:              try {
  30:                  onReady.Invoke(resultData);
  31:              }
  32:              finally {
  33:                  reader.Close();
  34:                  asynchState.WaitHandle.Set();
  35:              }
  36:          }
  37:      }
  38:  }

These two classes enable you to retrieve your object asynchronously. Like so:

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Linq;
   4:  using System.Text;
   5:  using System.Threading;
   6:   
   7:  namespace AsyncDataAccess {
   8:      class Program {
   9:          static void Main(string[] args) {
  10:              using (SampleDbContextDataContext context = new SampleDbContextDataContext()) {
  11:                  context.Connection.Open();
  12:                  
  13:                  var customerQuery = from Customer c in context.Customers
  14:                                      select c;
  15:   
  16:                  IAsyncResult customerResult =
  17:                      context.ExecuteAsync<Customer>(customerQuery, (customers) => {
  18:                                                                         foreach (var c in customers) {
  19:                                                                             Console.WriteLine(c.ToString());
  20:                                                                         }
  21:                                                                     });
  22:   
  23:                  var productQuery = from Product p in context.Products
  24:                                     select p;
  25:   
  26:                  IAsyncResult productResult =
  27:                      context.ExecuteAsync<Product>(productQuery, (products) => {
  28:                                                                       foreach (var p in products) {
  29:                                                                           Console.WriteLine(p.ToString());
  30:                                                                       }
  31:                                                                   });
  32:   
  33:                  Console.WriteLine("Before the queries are returned:");
  34:   
  35:                  ExecuteAsyncState<Customer> customerState = customerResult.AsyncState as ExecuteAsyncState<Customer>;
  36:                  ExecuteAsyncState<Product> productState = productResult.AsyncState as ExecuteAsyncState<Product>;
  37:   
  38:                  WaitHandle[] waitHandles = new[] { customerState.WaitHandle, productState.WaitHandle };
  39:                  WaitHandle.WaitAll(waitHandles);
  40:   
  41:                  Console.WriteLine("After the queries are returned:");
  42:   
  43:                  Console.ReadLine();
  44:              }
  45:   
  46:          }
  47:      }
  48:  }

And that results in a screen like this:

image

Where you’ll see it prints my name first, then a product name, then the rest of the customer names to finish of with the rest of the product names.

Conclusion

It was very easy to get this result and you could of course add some more methods that simply return a single object instead of an IEnumerable<T>.

The code is attached so you can try it yourself.

Cheers and have fun,

Wesley

More Posts