We have a custom Google Analytics plugin that will be sending commands to track custom pageviews and log events. Here's a simplified repro of our scenario:
// Version 1: only works with analytics.js, not with GTM
function GaPlugin(tracker, config) {
// Simplified for SO, we actually use the config argument
// to track more interesting pages and events...
ga(tracker.get('name') + ".send", "pageview", "/virtual/page/view");
}
var ga = window[window["GoogleAnalyticsObject"] || "ga"];
if (typeof ga == "function") {
ga("provide", "myAnalytics", GaPlugin);
ga("require", "myAnalytics", { /* omitted for simplicity */ });
}
This works fine if analytics.js is included directly on the page. However, we're now including universal analytics on the page using the Google Tag Manager. Thus we ran into the dreaded error...
Command ignored. Unknown target: undefined
...as seen in the Google Analytics debugging Chrome plugin when the require command is being executed.
This question and this blogpost imply the need to set a tracker name (in GTM, using: Edit Tag => More Settings => Advanced Configuration => Set Tracker Name checkbox) but the tooltip says "Use of named trackers is highly discouraged in GTM". So instead I changed things to this:
// Version 2: crashes with GTM because ga is loaded after this code
function GaPlugin(tracker, config) {
// Simplified for SO, we actually use the config argument
// to track more interesting pages and events...
ga(tracker.get('name') + ".send", "pageview", "/virtual/page/view");
}
var ga = window[window["GoogleAnalyticsObject"] || "ga"];
if (typeof ga == "function") {
ga("provide", "myAnalytics", GaPlugin);
ga(ga.getAll()[0].get("name") + ".require", "myAnalytics", { });
}
But now I'm met with an error because ga is undefined when the above executes, because GTM will load universal analytics asynchronously, where I want to run my code to bootstrap the plugin right away. This also means I cannot place a callback on the ga command queue, because again: it doesn't exist yet.
In essence, the order of things (I think) is now:
The GTM snippet starts loading (async).
My own javascript code runs.
GTM will start loading analytics (async).
Analytics is loaded and ready to be used.
The only workaround I could think of was this:
// Version 3: ugly workaround...
function GaPlugin(tracker, config) {
// Simplified for SO, we actually use the config argument
// to track more interesting pages and events...
ga(tracker.get('name') + ".send", "pageview", "/virtual/page/view");
}
var interval = setInterval(function() {
var ga = window[window["GoogleAnalyticsObject"] || "ga"];
if (typeof ga == "function" && typeof ga.getAll == "function") {
ga("provide", "myAnalytics", GaPlugin);
ga(ga.getAll()[0].get("name") + ".require", "myAnalytics", { });
clearInterval(interval);
}
}, 250);
Isn't there a better way to do this? The GTM documentation nor the GA Plugins documentation seems to have any info on this.
As a footnote, I just realized it might also work if I mimic the tracking snippet by creating ga as a command queue myself. But that also feels like a workaround, not a solution...
Not satisfying, but let me share my workaround as an answer nonetheless, as it is in fact the solution I'm using right now. It builds on the final footnote: mimicking what the Google Analytics bootstrapping snippet does.
I'm setting up the ga object, or at least its q property, myself:
function createGoogleAnalyticsQueueIfNeeded() {
// As a workaround for: http://stackoverflow.com/questions/40587544
// We mimick: https://developers.google.com/analytics/devguides/collection/analyticsjs/tracking-snippet-reference
var gaKey = window["GoogleAnalyticsObject"] || "ga";
var ga = window[gaKey] || function () {
(window[gaKey]["q"] = window[gaKey]["q"] || []).push(arguments);
};
window[gaKey] = ga;
return ga;
}
After calling the above, you can place a command on that queue which will be executed whenever GTM has finished loading GA (which also includes an optimization to run the plugin for all trackers):
var ga = createGoogleAnalyticsQueueIfNeeded();
ga(function() {
ga("provide", "myAnalytics", GaPlugin);
ga.getAll().forEach(function(t) {
ga(t.get("name") + ".require", "myAnalytics", { });
});
});
Et voila, the provide and .require calls now run whenever GA (loaded via GTM) hollers at the callback.
Related
I'm just getting started with Electron, with prior experience with node-webkit (nw.js).
In nw.js, I was able to create iframes and then access the DOM of said iframe in order to grab things like the title, favicon, &c. When I picked up Electron a few days ago to port my nw.js app to it, I saw advice to use webviews instead of iframes, simply because they were better. Now, the functionality I mentioned above was relatively easy to do in nw.js, but I don't know how to do it in Electron (and examples are slim to none). Can anyone help?
Also, I have back/forward buttons for my webview (and I intend on having more than one). I saw in the documentation that I could call functions for doing so on a webview, but nothing I have tried worked either (and, I haven't found examples of them being used in the wild).
I dunno who voted to close my question, but I'm glad it didn't go through. Other people have this question elsewhere online too. I also explained what I wanted to achieve, but w/e.
I ended up using ipc-message. The documentation could use more examples/explanations for the layperson, but hey, I figured it out. My code is here and here, but I will also post examples below should my code disappear for whatever reason.
This code is in aries.js, and this file is included in the main renderer page, which is index.html.
var ipc = require("ipc");
var webview = document.getElementsByClassName("tabs-pane active")[0];
webview.addEventListener("ipc-message", function (e) {
if (e.channel === "window-data") {
// console.log(e.args[0]);
$(".tab.active .tab-favicon").attr("src", e.args[0].favicon);
$(".tab.active .tab-title").html(e.args[0].title);
$("#url-bar").val(e.args[0].url);
$("#aries-titlebar h1").html("Aries | " + e.args[0].title);
}
// TODO
// Make this better...cancel out setTimeout?
var timer;
if (e.channel === "mouseover-href") {
// console.log(e.args[0]);
$(".linker").html(e.args[0]).stop().addClass("active");
clearTimeout(timer);
timer = setTimeout(function () {
$(".linker").stop().removeClass("active");
}, 1500);
}
});
This next bit of code is in browser.js, and this file gets injected into my <webview>.
var ipc = require("ipc");
document.addEventListener("mouseover", function (e) {
var hoveredEl = e.target;
if (hoveredEl.tagName !== "A") {
return;
}
ipc.sendToHost("mouseover-href", hoveredEl.href);
});
document.addEventListener("DOMContentLoaded", function () {
var data = {
"title": document.title,
"url": window.location.href,
// need to make my own version, can't rely on Google forever
// maybe have this URL fetcher hosted on hikar.io?
"favicon": "https://www.google.com/s2/favicons?domain=" + window.location.href
};
ipc.sendToHost("window-data", data);
});
I haven't found a reliable way to inject jQuery into <webview>s, and I probably shouldn't because the page I would be injecting might already have it (in case you're wondering why my main code is jQuery, but there's also regular JavaScript).
Besides guest to host IPC calls as NetOperatorWibby, it is also very useful to go from host to guest. The only way to do this at present is to use the <webview>.executeJavaScript(code, userGesture). This api is a bit crude but it works.
If you are working with a remote guest, like "extending" a third party web page, you can also utilize webview preload attribute which executes your custom script before any other scripts are run on the page. Just note that the preload api, for security reasons, will nuke any functions that are created in the root namespace of your custom JS file when your custom script finishes, however this custodial process will not nuke any objects you declare in the root. So if you want your custom functions to persist, bundle them into a singleton object and your custom APIs will persist after the page fully loads.
[update] Here is a simple example that I just finished writing: Electron-Webview-Host-to-Guest-RPC-Sample
This relates to previous answer (I am not allowed to comment): Important info regarding ipc module for users of Electron 1.x:
The ipc module was split into two separate modules:
ipcMain for the main process
ipcRenderer for the renderer process
So, the above examples need to be corrected, instead of
// Outdated - doesn't work in 1.x
var ipc = require("ipc");
use:
// In main process.
var ipcMain = require('electron').ipcMain
And:
// In renderer process.
var ipcRenderer = require('electron').ipcRenderer
See: http://electron.atom.io/blog/2015/11/17/electron-api-changes section on 'Splitting the ipc module'
I am performing an audit using a custom web crawler of mine and was trying to garner the accountID for the legacy implementations of Google Analytics, but I cannot seem to get any of the JavaScript functions in _gaq to return the accountId in use. Does anybody know how to do this? All the documentation I've read really only mentions how to set variables, not how to get variables out once set for the purpose of auditing an implementation.
Thanks in advance
UPDATE
Thanks everyone! I wish I could check off all of your responses as good answers.
After a bit of testing, I have come up with the following function that handles pretty much every use case.
function getAccount() {
try {
if (_gaq) {
for (i = 0; i < _gaq.length; i++) {
if (_gaq[i][0] == "_setAccount") {
return _gaq[i][1]
}
}
}
if (_gat) {
if (_gat.fb) {
return _gat.fb
}
}
if (ga) {
return ga.getAll()[0].a.data.values[':trackingId']
}
} catch (e) { }
return ""
}
Joshua, this isn't a standard feature / get function that would be available.
You can however manually access ga object created by the tracking library.
If you for example open console for this webpage and type in:
ga.getAll()[0].a.data.values[':trackingId']
You will receive UA-5620270-1 which is probably the main Analytics Account for Stack Overflow. Similar approach should work in your case as well - and also any other attribute that's accessible:
Screen: http://fii.cz/sbdqevk
If you are prepared to accept a less than elegant solution you could use
function getAccount(_gaq) {
for (i = 0; i < _gaq.length; i++) {
if (_gaq[i][0] == "_setAccount") {
return _gaq[i][1];
}
}
}
_gaq is an array of arrays, the function simply loops through until it finds a subarray where the first element is _setAccount and returns the second element, which is the account number (_gaq.push(['_setAccount', 'UA-XXXXX-X']);).
As you are talking of legacy implementations, there are even older versions of the code so you might need more checks. The oldest version I could find on an active page was:
<script type="text/javascript">
_uacct = "UA-XXXXXXXX-X";
urchinTracker();
</script>
It's quite easy to get the Account Id from there. There is also the synchronous version of the code (sorry, can't find an example right now).
And for the current version look at Petrs answer.
According to documentation first you get all the trackers set on page
// returns an array with all the trackers
const trackers = ga.getAll();
And then, for each tracker, you can obtain the tracking id for each one getting the property
// returns the 'trackingId' propperty
const trackingId = tracker.get('trackingId'));
If you want to shorten it up, you could do
const trackingIds = ga.getAll().map(tracker => tracker.get('trackingId'));
Warning:
Remember to run this always after the ga ready.
Don't — use ga object methods outside a readyCallback as the methods
may not be available yet.
var trackers = ga.getAll();
Do — use ga object methods inside a
readyCallback as they're guaranteed to be available.
ga(function() {
var trackers = ga.getAll();
});
I just did it using an alternative method, as follows:
var _gaq = _gaq || [];
_gaq.push(function() {
var trackers, i;
trackers = _gat._getTrackers();
for (i = trackers.length - 1; i >= 0; i--) {
var account = trackers[i]._getAccount();
console.log("tracker account", account);
}
});
I'am just playing for learning reasons with some google code.
All world is using the following google-code for analytics.
If i look at this line:
i[r]=i[r]||function(){ (i[r].q=i[r].q||[]).push(arguments) }
it must be wrong for me.
i[r] is representing i['ga']
If I do not give the argument 'ga' to the call I will get an error in the next line. That's means to me that the ||function(.. will never be called.
(function(i,s,o,g,r,a,m){ // r is the 'ga' argument
i['GoogleAnalyticsObject']=r;
i[r]=i[r]||function(){ (i[r].q=i[r].q||[]).push(arguments) }, //for what is this line?
i[r].l=1*new Date();
a=s.createElement(o),
m=s.getElementsByTagName(o)[0];
a.async=1;
a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
I can't believe that this is an issue by google. Is there something i do not know?
For me, it helps to "unminify" things a little, so if you substitute the parameters values in, this
i[r]=i[r] || function(){
(i[r].q=i[r].q||[]).push(arguments)
},
becomes this
window.ga = window.ga || function(){
(window.ga.q = window.ga.q || []).push(arguments)
},
And if you replace all the minification tricks, you get this:
// check whether the Analytics object is defined
if (!('ga' in window)){
// Analytics object not defined, so define it
window.ga = function(){
// Add tasks to the queue
window.ga.q.push(arguments);
};
Hope this helps.
I've got a jQuery plugin I made which will nicely format dates on elements with certain attributes on them. Overall it works, but I'm having some problems with Modernizr, but ONLY in IE and ONLY after a form postback.
Here's a snippet from the plugin where it uses the fantastic MomentJS library. Basically the first time the plugin is called, it will download the needed file then run the code to parse dates. If it is called any time afterwards and the library has already been downloaded, it can just go ahead and run the date parsing code.
function parseDates() {
var $items = this;
if (typeof moment !== "undefined") {
//If we are calling this at a time after page load, just run the function
setupDisplayDate();
} else {
//If the files have not been included yet, download them & call the function
//Load in a date library!
Modernizr.load({
load: SCRIPTS_PATH + "moment.min.js",
callback: setupDisplayDate
});
}
function setupDisplayDate() {
console.log("setupDisplayDate called! Moment is " + typeof moment);
$items.each(function () {
var $thisItem = $(this);
var formatter = $thisItem.data("date-format") || "MMMM Do, YYYY";
var ticks = parseInt($thisItem.data("date-ticks"), 10);
var formattedDate = moment(ticks).format(formatter);
$thisItem.text(formattedDate);
});
}
}
When I do this in IE9 only after a page postback, I get an error within the setupDisplayDate function saying that moment is not defined. What am I doing wrong?
I did find that if I do a timeout of 500ms it will work, but I shouldn't have to do that. I thought the whole point of the Modernizr.load feature was to download the code, and then make it available. It seems to download it and the fire my callback before it is available to use.
EDIT
Here's [a blog post about how IE9 will not properly dynamically added scripts: http://www.guypo.com/technical/ies-premature-execution-problem/
Any way around this?
Another Edit
It seems like the issues is actually regarding multiple calls to the load function and then the various callbacks firing out of order. There's an open issue on GitHub about it and this reproducible test case.
I have file called common.js and it's included in each page of my site using <script />.
It will grow fast as my sites functionality will grow (I hope; I imagine). :)
Lets example I have a jQuery event:
$('#that').click(function() {
one_of_many_functions($(this));
}
For the moment, I have that one_of_many_functions() in common.js.
Is it somehow possible that JavaScript automatically loads file one_of_many_functions.js when such function is called, but it doesn't exist? Like auto-loader. :)
The second option I see is to do something like:
$('#that').click(function() {
include('one_of_many_functions');
one_of_many_functions($(this));
}
That not so automatically, but still - includes wanted file.
Is any of this possible? Thanks in an advice! :)
It is not possible to directly auto-load external javascripts on demand. It is, however, possible to implement a dynamic inclusion mechanism similar to the second route you mentioned.
There are some challenges though. When you "include" a new external script, you aren't going to be able to immediately use the included functionality, you'll have to wait until the script loads. This means that you'll have to fragment your code somewhat, which means that you'll have to make some decisions about what should just be included in the core vs. what can be included on demand.
You'll need to set up a central object that keeps track of which assets are already loaded. Here's a quick mockup of that:
var assets = {
assets: {},
include: function (asset_name, callback) {
if (typeof callback != 'function')
callback = function () { return false; };
if (typeof this.assets[asset_name] != 'undefined' )
return callback();
var html_doc = document.getElementsByTagName('head')[0];
var st = document.createElement('script');
st.setAttribute('language', 'javascript');
st.setAttribute('type', 'text/javascript');
st.setAttribute('src', asset_name);
st.onload = function () { assets._script_loaded(asset_name, callback); };
html_doc.appendChild(st);
},
_script_loaded: function (asset_name, callback) {
this.assets[asset_name] = true;
callback();
}
};
assets.inlude('myfile.js', function () {
/* do stuff that depends on myfile.js */
});
Sure it's possible -- but this can become painful to manage. In order to implement something like this, you're going to have to maintain an index of functions and their corresponding source file. As your project grows, this can be troublesome for a few reasons -- the 2 that stick out in my mind are:
A) You have the added responsibility of maintaining your index object/lookup mechanism so that your scripts know where to look when the function you're calling cannot be found.
B) This is one more thing that can go wrong when debugging your growing project.
I'm sure that someone else will mention this by the time I'm finished writing this, but your time would probably be better spent figuring out how to combine all of your code into a single .js file. The benefits to doing so are well-documented.
I have created something close to that a year ago. In fact, I have found this thread by search if that is something new on the field. You can see what I have created here: https://github.com/thiagomata/CanvasBox/blob/master/src/main/New.js
My project are, almost 100% OOP. So, I used this fact to focus my solution. I create this "Class" with the name "New" what is used to, first load and after instance the objects.
Here a example of someone using it:
var objSquare = New.Square(); // Square is loaded and after that instance is created
objSquare.x = objBox.width / 2;
objSquare.y = objBox.height / 2;
var objSomeExample = New.Stuff("some parameters can be sent too");
In this version I am not using some json with all js file position. The mapping is hardcore as you can see here:
New.prototype.arrMap = {
CanvasBox: "" + window.MAIN_PATH + "CanvasBox",
CanvasBoxBehavior: "" + window.MAIN_PATH + "CanvasBoxBehavior",
CanvasBoxButton: "" + window.MAIN_PATH + "CanvasBoxButton",
// (...)
};
But make this more automatic, using gulp or grunt is something what I am thinking to do, and it is not that hard.
This solution was created to be used into the project. So, the code may need some changes to be able to be used into any project. But may be a start.
Hope this helps.
As I said before, this still is a working progress. But I have created a more independent module what use gulp to keep it updated.
All the magic que be found in this links:
https://github.com/thiagomata/CanvasBox/blob/master/src/coffee/main/Instance.coffee
https://github.com/thiagomata/CanvasBox/blob/master/src/node/scripts.js
https://github.com/thiagomata/CanvasBox/blob/master/gulpfile.js
A special look should be in this lines of the Instance.coffee
###
# Create an instance of the object passing the argument
###
instaceObject = (->
ClassElement = (args) ->
window[args["0"]].apply this, args["1"]
->
ClassElement:: = (window[arguments["0"]])::
objElement = new ClassElement(arguments)
return objElement
)()
This lines allows me to initialize a instance of some object after load its file. As is used in the create method:
create:()->
#load()
return instaceObject(#packageName, arguments)