I am building AngularJS applications which have common header with links to each of the application:
App1
App2
Each application is running on its own subdomain and when user clicks a link on the header - page redirects to that application.
I have to track user actions with the links, e.g. onClick events with Omniture (but the problem applies to Google Analytics as well). When I add an onClick event that calls a function to send event to Omniture, e.g.:
App1
trackLink() is a function of an AngularJS service, brief implementation:
trackLink: function (eVar8Code) {
s = this.getSVariable(s);
s.eVar8 = eVar8Code;
s.prop28 = s.eVar8;
this.sendOmnitureMessage(s, send, false);
return s;
},
the function executes asynchronously and returns right away. Then standard link's behaviour kicks in: page is redirected to the URL defined in "href" attribute. New page is loaded very quickly (around 70 ms) but AJAX request to Omniture has not been executed: it's all async.
I believe that using events for the links is incorrect approach, one should rather use Query parameters, e.g.:
App1
but it's hard to convince some.
What is a good practise to track events on links?
Change your function to include a short timeout (probably you'd let it return false to suppress default link behaviour, too, and redirect via the location object).
Google Analytics has hit callbacks which are executed after the call to Google was sent, you might want to look if Adobe Analytics has something similar (as this can be used for redirects after the tracking call has been made).
If event tracking and query parameters are interchangeable depends on your use case (they certainly measure different things). However event tracking is a well accepted way for link tracking.
As #Eike Pierstorff suggested - I used capabilities of Adobe Analytics native library to set a delay (200ms) which give the call to Adobe Analytics much better chances to succeed:
in HTML:
App1
in AngularJS service:
sendOmnitureMessageWithDelay: function (s, element, eVar8Code) {
var s = s_gi(s_account); // jshint ignore:line
s.useForcedLinkTracking = true;
s.forcedLinkTrackingTimeout = 200; // Max number of milliseconds to wait for tracking to finish
s.linkTrackVars = 'eVar8,prop28';
s.eVar8 = eVar8Code;
s.prop28 = eVar8Code;
var target = element;
if (!target) {
target = true;
}
s.tl(target, 'o', s.eVar8, null, 'navigate');
this.cleanOmnitureVars();
}
Here, element - is HTML element about.
It works pretty well in 99% of the cases but has issues on the slow and old devices where page loads before call to Adobe has been made. It appears that there is no good solution to this problem and there cannot be guarantee that events would always be recorded in Adobe Analytics (or Google Analytics).
Related
I'm creating a Google Chrome extension that adds some extra functionality to Gmail by injecting a script that I've written. By 'injected script' please see this definition.
To do this, my content script matches on ["https://mail.google.com/mail/*"].
However, the problem with this is that this is the same URL for when Gmail is loading and has finished loading. As a result, my script appears to sometimes to get injected too early, while Gmail is still loading, leading to reference errors.
To try to circumvent this problem, I'm only running my script on load by doing:
window.addEventListener("load", runInjectedScript);
And in my content script (which does the injecting), it only injects when this conditional is true: if (window.self === window.top).
Yet, these measures do not seem to guarantee that my script will always get injected at the right time, once Gmail has finished loading and the inbox appears. How can I ensure that this happens? Are there any techniques that I haven't implemented?
To ensure injecting scripts when "inbox appears", take a look at MutationObserver, which provides a way to react to changes in the DOM, in particular for observing the input being inserted
You could potentially run a setInterval checking to see if the data you're intercepting in Gmail is available yet.
var gmailLoaded = setInterval(function () {
if (typeof GMAIL !== 'undefined') {
runInjectedScript();
clearInterval(gmailLoaded);
}
}, 100);
You'll need to replace GMAIL with whatever you're trying to reference from Gmail. You could also use the same approach above checking to see if the loading state is active, however that may add additional overhead.
I inspected the gmail page and once the inbox loads they add style="display: none" to the #loading element.
You could do some polling for that to change and then bootstrap your app.
function onInboxLoaded(cb) {
var pollInterval = setInterval(function() {
var elem = document.getElementById('loading');
if (elem && elem.style.display === 'none') {
clearInterval(pollInterval);
cb();
}
}, 100);
}
onInboxLoaded(function(){
// bootstrap app
})
Hey guys just testing our pages out using the grunt-phantomcss plugin (it's essentially a wrapper for PhantomJS & CasperJS).
We have some stuff on our sites that comes in dynamically (random profile images for users and random advertisements) sooo technically the page looks different each time we load it, meaning the build fails. We would like to be able to jump in and using good ol' DOM API techniques and 'grey out'/make opaque these images so that Casper/Phantom doesn't see them and passes the build.
We've already looked at pageSettings.loadImages = false and although that technically works, it also takes out every image meaning that even our non-ad, non-profile images get filtered out.
Here's a very basic sample test script (doesn't work):
casper.start( 'http://our.url.here.com' )
.then(function(){
this.evaluate(function(){
var profs = document.querySelectorAll('.profile');
profs.forEach(function( val, i ){
val.style.opacity = 0;
});
return;
});
phantomcss.screenshot( '.profiles-box', 'profiles' );
});
Would love to know how other people have solved this because I am sure this isn't a strange use-case (as so many people have dynamic ads on their sites).
Your script might actually work. The problem is that profs is a NodeList. It doesn't have a forEach function. Use this:
var profs = document.querySelectorAll('.profile');
Array.prototype.forEach.call(profs, function( val, i ){
val.style.opacity = 0;
});
It is always a good idea to register to page.error and remote.message to catch those errors.
Another idea would be to employ the resource.requested event handler to abort all the resources that you don't want loaded. It uses the underlying onResourceRequested PhantomJS function.
casper.on("resource.requested", function(requestData, networkRequest){
if (requestData.url.indexOf("mydomain") === -1) {
// abort all resources that are not on my domain
networkRequest.abort();
}
});
If your page handles unloaded resources well, then this should be a viable option.
I am trying to extract content from web pages using my firefox/chrome/safari extension. Capturing works fine but when I capture full web pages, it takes a long time and UI gets blocked. I want to move the capture/DOM parsing code to a different thread (Web Worker). But web workers do not have access to the DOM. Is there a way I can work around this?
I am using the following code to inject the script into the web page:
function executeScript(script, messageKey, callback) {
var wm = Components.classes["#mozilla.org/appshell/window-mediator;1"].getService(Components.interfaces.nsIWindowMediator);
var mainWindow = wm.getMostRecentWindow("navigator:browser");
mainWindow.gBrowser.selectedBrowser.messageManager.loadFrameScript(script, true);
mainWindow.gBrowser.selectedBrowser.messageManager.addMessageListener(messageKey, callback);
}
executeScript("chrome://extension/content/contentscript.js", "onSelectionReceived", onSelection);
All the DOM processing is happening inside this script 'contentscript.js'
If the work you are trying to perform needs to interact with the DOM and it happens to take a long time, and you can't refactor it to not need to interact with the DOM, then there is a way without using WebWorkers.
(Because as you discovered, WebWorkers do not have access to the DOM)
Consider using Array Processing. The basic idea is to split up the work you need to do into different chunks, and after reach chunk of work is completed, periodically give back control to the DOM (UI Thread) using the Timer.
Here is a basic example of Array Processing:
function saveDocument(id){
var tasks = [openDocument,writeText,closeDocument,updateUI]
setTimeout(function(){
//execute the next task
var task = tasks.shift();
task(id);
//determine if there's more
if (tasks.length > 0) {
setTimeout(arguments.calee, 25);
}
}, 25);
}
I'm tracking the Download button click on a website featuring a project of mine with this code:
function trackDownload(link) {
try {
_gaq.push(['_trackEvent', 'Downloads', 'Click', 'Setup executable']);
setTimeout('document.location = "' + link.href + '"', 100);
} catch (err) {}
return false;
}
And the button is something as:
Download
So, when a user clicks it, an event is pushed to Analytics and then the user is redirected to the file.
This is applicable also on external link tracking, no differences.
And now my question. Can I be sure that the Analytics event is "processed" before the user is redirect? If not, that redirection cause the event to be lost? Currently events are being tracked, but I cannot be sure that all of them are.
I read I can also try something a little bit different, pushing the redirection function into Analytics queue:
_gaq.push(function() { document.location = link.href; });
But it's not clear if this works or if it's just equivalent to the previous one. In fact, here it's said that "calls to _gaq.push [...] executes commands as they are pushed".
You are correct in that you can push functions onto the analytics queue. Since functions or tracking events are executed/evaluated in the order that you pushed them on to the array, you should be able to do this:
function trackDownload(link) {
_gaq.push(['_trackEvent', 'Downloads', 'Click', 'Setup executable']);
_gaq.push(function() { document.location = link.href });
return false;
}
Note that the try/catch isn't necessary since push() isn't documented to throw anything (and I'd recommend removing it since empty catch blocks can mask other problems).
You ask:
But it's not clear if this works or if it's just equivalent to the previous one.
In your first example (push, setTimeout), the event will be lost if Analytics hasn't finished loading when you do the redirect (because at that time, _gaq is just an array). In the version with the push(function..., then the event will be recorded before the redirect regardless of whether Analytics has finished loading at the time the user hits the download button. For this reason, I would recommend using push(function....
Be warned that the push(function... version will wait for analytics to finish loading before the redirect happens (which sounds like what you want anyway), but you may want to add a way to handle the case where analytics doesn't load.
I am looking for a quick way to grab some data off of one Web page and throw it into another. I don't have access to the query string in the URL of the second page, so passing the data that way is not an option. Right now, I am using a Greasemonkey user script in tandem with a JS bookmarklet trigger: javascript:doIt();
// ==UserScript==
// #include public_site
// #include internal_site
// ==/UserScript==
if (document.location.host.match(internal_site)) {
var datum1 = GM_getValue("d1");
var datum2 = GM_getValue("d2");
}
unsafeWindow.doIt = function() {
if(document.location.host.match(public_site)) {
var d1 = innerHTML of page element 1;
var d2 = innerHTML of page element 2;
//Next two lines use setTimeout to bypass GM_setValue restriction
window.setTimeout(function() {GM_setValue("d1", d1);}, 0);
window.setTimeout(function() {GM_setValue("d2", d2);}, 0);
}
else if(document.location.host.match(internal_site)) {
document.getElementById("field1").value = datum1;
document.getElementById("field2").value = datum2;
}
}
While I am open to another method, I would prefer to stay with this basic model if possible, as this is just a small fraction of the code in doIt() which is used on several other pages, mostly to automate date-based form fills; people really like their "magic button."
The above code works, but there's an interruption to the workflow: In order for the user to know which page on the public site to grab data from, the internal page has to be opened first. Then, once the GM cookie is set from the public page, the internal page has to be reloaded to get the proper information into the internal page variables. I'm wondering if there's any way to GM_getValue() at bookmarklet-clicktime to prevent the need for a refresh. Thanks!
Can you move the bookmarklet to a button or link -- that Greasemonkey will add to the page(s)?
Then you could set click-event handlers to fire GM_getValue().
It looks like the current method is exploiting a "security hole" -- one that may be closed in the future. You might consider doing everything in a Firefox extension, instead.
Possibly useful link: http://articles.sitepoint.com/article/ten-tips-firefox-extensions/1