Following situation:
On a single page application, an event : ArticleClick is fired when we open a specific article on that page. I consider an article read when 10 seconds have passed after the ArticleClick event.
So far so good, this can be accomplished using the Timer functionality in GTM.
Only problem is that, when the event has fired and after 9 seconds i open another event, this will cause a second timer to start and no matter what i do as long as the website is open (because it's single page) there will always be a read event. Is there a simple way to stop the first timer when another article (and another timer) is opened before the 10 secs has reached?
Solved by using a datalayer variable to store the state of the timer (running or not).
Mind that the true or falses here are strings instead of booleans, this because the default value in GTM for the DL-variable needs to be set to false to make this work,doing that in the GTM-interface automatically makes the value a string.
<script>
(function() {
var timerNumber = 1;
var limit = 1;
// timer
var fireTimer = function() {
window.dataLayer.push({
'event' : 'ArticleRead',
'custom.timer.running' : 'false'
});
timerNumber += 1;
if (limit < timerNumber) {
window.clearInterval(timerId);
}
};
if ({{custom.timer.running}} == 'false') {
window.dataLayer.push({ 'custom.timer.running' : 'true'});
var timerId = window.setInterval(fireTimer, 10000); }
})();
</script>
So now when we trigger the timer a second time and the timer hasn't reached it's 10000 milsecs it does not continue and it starts again.
Related
Is there a way to invoke the value event handler only on value change in Firebase? If I just add the value event handler without any special code, it will be called once on reading the initial value and then on any value change. I want to avoid invoking the event handler on reading the initial value.
One of the use cases where I need this functionality is that I am working on an app where user can add tasks for some other users. The other users can either accept or reject a task. There is a server which is monitoring these tasks on Firebase and it sends a push notification to the creator of the task whenever a user accepts or rejects a task i.e. whenever there is a change in the status of task.
I have tried two solutions.
Solution 1:
Have a map and add an entry to the map when the value event handler is called if it is not already present and if the the entry is present, then do the work required on value change. This solution works but I need this in several different cases and I have to create this map in all such cases and I didn't find it to be a convenient solution.
Solution 2:
Add the value event handler like this
ref.on('value', (snapshot) => {
console.log('Initial call ', this.initialCall);
if(!this.initialCall) {
// Do work
} else {
this.initialCall = false;
}
}, {initialCall: true}) // {initialCall: true} is the context which is provided as this in the event handler
Here my thinking was that I can check if the event handler is called for the initial value by checking if this.initialCall is set to true. If it is, then set the this.initialCall to false and then in the subsequent invocations, this.initialCall should be false and I can do the work required for a value change. Using this solution what I observed was that the this.initialCall was undefined the first time the handler is called and then it was set to true and then it was true for all value change event handler invocations for all the tasks, not just the one for which it was set to true.
With regards to solution 2, I am not a JS expert and it is possible that due to some gap in my JS knowledge, I am doing something wrong due to which it doesn't work as I expected.
Please let me know if you have an idea/solution which can be used to call the value event handler only on value change.
A value event in Firebase is invoked immediately with the current value and then whenever the value changes. If you only care abut when the value changes, you can simply ignore the initial event:
var isInitialValue = true;
ref.on('value', function(snapshot) {
if (isInitialValue) {
isInitialValue = false;
}
else {
// TODO: handle subsequent changes
}
});
I'm using angulartics with google analytics and intercom.
In my current case i have to track the same event multiple times in a loop,but angulartics seems to throw the events away if they occure to fast successively.
When using the debugger, each event is tracked, also if i call the eventTrack with some delay for each event.
But without debugger and delay the event is only tracked once.
Am i missing anything or is this exactly what is recommended, delaying the track-call?
Code-Example:
// looping a list of things you want to track for each element
for (var i = 0; i < length; i++){
// call the event tracks in parallel,
// fired async with a delay of 1000 ms between each
setTimeout(function delayed() {
$analytics.eventTrack(action, trackingEvent);
}, 1000 * i);
}
I have the following scenario:
Client side has a button clicking it will execute Meteor.call method on the server-side which will call API and fetch products, During this time I wan't to disable this button + block this method from executing again basically nothing stops you from clicking the button 100x times and server will keep on executing same method again and again.
Few ideas I had in my mind: Use sessions to disable button (Problem: can still using the console Meteor.call and abuse it)
I also looked at Meteor.apply in the docs with wait:true didn't seems to stop from method execution. I honestly not sure how this kind of thing is handled with no hacks.
Client-side:
'click .button-products': function(e){
Meteor.call('getActiveProducts', function(error, results){
if (error)
return Alerts.add(error.reason, 'danger', {autoHide: 5000});
if (results.success)
return Alerts.add('Finished Importing Products Successfully', 'success', {autoHide: 5000});
})
}
Server-side
Meteor.methods({
getActiveProducts: function(){
var user = Meteor.user();
var api = api.forUser(user);
importProducts = function(items){
nextPage = items.pagination.next_page;
items.results.forEach(function(product){
var sameproduct = apiProducts.findOne({listing_id: product.listing_id});
if (sameproduct) {
return;
}
var productExtend = _.extend(product, {userId: Meteor.userId()});
apiProducts.insert(productExtend);
});
};
var products = api.ProductsActive('GET', {includes: 'Images', limit: 1});
importProducts(products);
while (nextPage !== null) {
products = api.ProductsActive('GET', {includes: 'Images', page: nextPage, limit: 1});
importProducts(products);
}
return {success: true};
}
});
From the Meteor docs:
On the server, methods from a given client run one at a time. The N+1th invocation from a client won't start until the Nth invocation returns. However, you can change this by calling this.unblock. This will allow the N+1th invocation to start running in a new fiber.
What this means is that subsequent calls to the method won't actually know that they were made while the first call was still running, because the first call will have already finished running. But you could do something like this:
Meteor.methods({
getActiveProducts: function() {
var currentUser = Meteor.users.findOne(this.userId);
if (currentUser && !currentUser.gettingProducts) {
Meteor.users.update(this.userId, {$set: {gettingProducts: true}});
// let the other calls run, but now they won't get past the if block
this.unblock();
// do your actual method stuff here
Meteor.users.update(this.userId, {$set: {gettingProducts: false}});
}
}
});
Now subsequent calls may run while the first is still running, but they won't run anything inside the if block. Theoretically, if the user sends enough calls, the first call could finish before all of the others have started. But this should at least significantly limit the number of etsy calls that can be initiated by a user. You could adapt this technique to be more robust, such as storing the last time a successful call was initiated and making sure X seconds have passed, or storing the number of times the method has been called in the last hour and limiting that number, etc.
A package I wrote a while back might come in handy for you. Essentially it exposes the Session api on the server side (hence the name), meaning you can do something like ServerSession.set('doingSomethingImportant', true) within the call, and then check this session's value in subsequent calls. The session can only be set on the server, and expires upon connection close (so they could spam calls, but only as fast as they can refresh the page).
In the event of error, you can just reset the session. There shouldn't be any issues related to unexpected errors either because the session will just expire upon connection close. Let me know what you think :)
Dear all,
Template.tmp_detail_campaign_code_batch.events({
'click .ancProdCodePagination': function (e) {
Meteor.subscribe('ItemPage', Number*10,10)
}
});
Its definition:
Meteor.publish('ItemPage', function(skipItem, takeItem){
return Item.find({},{
skip : skipItem,
limit : takeItem
}); }
When I click .ancProdCodePagination, the amount of subscribed Items keep increasing by 10. For pagination, I'd like to make the amount stay at 10, but with different Items for each click.
What should I do?
You just need to stop the previous subscription first, which will involve storing the handle that it returns somewhere:
var itemSub;
Template.tmp_detail_campaign_code_batch.events({
'click .ancProdCodePagination': function (e) {
if (itemSub)
itemSub.stop();
itemSub = Meteor.subscribe('ItemPage', Number*10,10);
}
});
From the docs, the stop method does this:
Cancel the subscription. This will typically result in the server directing the client to remove the subscription's data from the client's cache.
I have a website which is using Google Analytics newer asynchronous tracking method (_gaq). The problem I've run into is that I want to institute some specific link tracking and am worried that I will be creating a race condition.
Basically, it's a news website so it has headlines which link to stories all over the place. A headline for a story might appear in 3 different places on a page, and appear on hundreds of other pages. Thus, in order to understand how our audience is interacting with the site we have to track how each specific headline block is used, and not just the destination. Because of those two stipulations tracking individual pages, nor tracking referred pages won't be enough, we have to track individual links.
So if I have a link.
Here
Because _gaq.push() is an asynchronous call, isn't it possible that the page change will occur prior to Google's completion of the click tracking? If so is there a way to prevent that, or do I have a misunderstanding about the way that Google Analytics Async functions (http://code.google.com/apis/analytics/docs/tracking/asyncUsageGuide.html).
You're right. If the browser leaves the page before it sends the GA tracking beacon (gif hit) for the event, the event will not be recorded. This is not new to the async code however, because the process of sending the tracking beacon is asynchronous; the old code worked the same way in that respect. If tracking is really that important, you could do something like this:
function track(link) {
if (!_gat) return true;
_gaq.push(['_trackEvent', 'stuff']);
setTimeout(function() {location.href=link.href'}, 200);
return false;
}
...
This will stop the browser from going to the next page when the link is clicked if GA has been loaded already (it's probably best to not make the user wait that long). Then it sends the event and waits 200 milliseconds to send the user to the href of the link they clicked on. This increases the likelihood that the event will be recorded. You can increase the likelihood even more by making the timeout longer, but that also may be hurting user-experience in the process. It's a balance you'll have to experiment with.
I've got this problem too, and am determined to find a real solution.
What about pushing the function into the queue?
// Log a pageview to GA for a conversion
_gaq.push(['_trackPageview', url]);
// Push the redirect to make sure it happens AFTER we track the pageview
_gaq.push(function() { document.location = url; });
From Google's documentation for universal analytics (new version since most other answers for this question). You can now easily specify a callback.
var trackOutboundLink = function(url) {
ga('send', 'event', 'outbound', 'click', url, {'hitCallback':
function () {
document.location = url;
}
});
}
For clarity I'd recommend using this syntax, which makes it clearer which properties you're sending and easier to add more :
ga('send', 'event', {
'eventCategory': 'Homepage',
'eventAction': 'Video Play',
'eventLabel': label,
'eventValue': null,
'hitCallback': function()
{
// redirect here
},
'transport': 'beacon',
'nonInteraction': (interactive || true ? 0 : 1)
});
[Here's a complete list of parameters for all possible ga calls.]
In addition I've added the transport parameter set to beacon (not actually needed because it's automatically set if appropriate):
This specifies the transport mechanism with which hits will be sent.
The options are 'beacon', 'xhr', or 'image'. By default, analytics.js
will try to figure out the best method based on the hit size and
browser capabilities. If you specify 'beacon' and the user's browser
does not support the navigator.sendBeacon method, it will fall back
to 'image' or 'xhr' depending on hit size.
So when using navigator.beacon the navigation won't interrupt the tracking . Unfortunately Microsoft's support for beacon is non existent so you should still put the redirect in a callback.
In event handler you should setup hit callback:
_gaq.push(['_set', 'hitCallback', function(){
document.location = ...
}]);
send you data
_gaq.push(['_trackEvent'
and stop event event processing
e.preventDefault();
e.stopPropagation();
I'm trying out a new approach where we build the URL for utm.gif ourselves, and request it, then only once we've received the response (the gif) we send the user on their way:
Usage:
trackPageview(url, function() { document.location = url; });
Code (CrumbleCookie from: http://www.dannytalk.com/read-google-analytics-cookie-script/)
/**
* Use this to log a pageview to google and make sure it gets through
* See: http://www.google.com/support/forum/p/Google%20Analytics/thread?tid=5f11a529100f1d47&hl=en
*/
function trackPageview(url, fn) {
var utmaCookie = crumbleCookie('__utma');
var utmzCookie = crumbleCookie('__utmz');
var cookies = '__utma=' + utmaCookie + ';__utmz=' + utmzCookie;
var requestId = '' + (Math.floor((9999999999-999999999)*Math.random()) + 1000000000);
var hId = '' + (Math.floor((9999999999-999999999)*Math.random()) + 1000000000);
var utmUrl = 'http://www.google-analytics.com/__utm.gif';
utmUrl += '?utmwv=4.8.9';
utmUrl += '&utmn=' + requestId;
utmUrl += '&utmhn=' + encodeURIComponent(window.location.hostname);
utmUrl += '&utmhid=' + hId;
utmUrl += '&utmr=-';
utmUrl += '&utmp=' + encodeURIComponent(url);
utmUrl += '&utmac=' + encodeURIComponent(_gaProfileId);
utmUrl += '&utmcc=' + encodeURIComponent(cookies);
var image = new Image();
image.onload = function() { fn(); };
image.src = utmUrl;
}
/**
* #author: Danny Ng (http://www.dannytalk.com/read-google-analytics-cookie-script/)
* #modified: 19/08/10
* #notes: Free to use and distribute without altering this comment. Would appreciate a link back :)
*/
// Strip leading and trailing white-space
String.prototype.trim = function() { return this.replace(/^\s*|\s*$/g, ''); }
// Check if string is empty
String.prototype.empty = function() {
if (this.length == 0)
return true;
else if (this.length > 0)
return /^\s*$/.test(this);
}
// Breaks cookie into an object of keypair cookie values
function crumbleCookie(c)
{
var cookie_array = document.cookie.split(';');
var keyvaluepair = {};
for (var cookie = 0; cookie < cookie_array.length; cookie++)
{
var key = cookie_array[cookie].substring(0, cookie_array[cookie].indexOf('=')).trim();
var value = cookie_array[cookie].substring(cookie_array[cookie].indexOf('=')+1, cookie_array[cookie].length).trim();
keyvaluepair[key] = value;
}
if (c)
return keyvaluepair[c] ? keyvaluepair[c] : null;
return keyvaluepair;
}
Using onmousedown instead of onclick may also help. It doesn't eliminate the race condition, but it gives GA a head start. There's also the concern of someone clicking on a link and dragging away before letting go of the mouse button, but that's probably a negligible case.