I'm creating an event data via GTM, and typically if it's sent to GA the timezone will follow the GA timezone but if I sent it to a different endpoint how do I capture the timestamp for the GTM event? Would I have to populate it in the dataLayer?
You may create a custom variable using javascript. That custom variable will return the current timestamp (beware that you may need to deal with browsers timezones, if that is important to you).
Go to Variables and add a new using the type Custom JavaScript:
Then the code could be along this lines (grabbed from here), depending on the format you want to return:
function() {
try {
var timestamp = new Date();
var time = timestamp.toString().split(' ');
return time[3]+ " " +time[1]+ " " +time[2]+ " "+time[4];
} catch(e) {
return "unknown";
}
}
After that, a variable with the name given (in this case I used 'Timestamp') will become available as {{Timestamp}} You then can plug it in the event or whatever tag you are creating. You may even use it as {{Timestamp}} inside a Javascript tag. The variable will return a timestamp in the format: 2020 May 30 11:41:44
Related
How do I remove URL params from getting pushed to GA4 through GTM? I have tried many solutions but none seem to be working.
Which key in "Fields to Set" do I need to use so GTM replaces the url query param from all dimensions like page_path, page_location, path_referrer?
This article has been my life saver when dealing with URL params in GA4, but please use my experience and avoid the mistake of applying the script directly to page_location.
page_location is what I call a technical dimension that GA4 uses to sort referring websites according to its internal rules and do any other GA4 things. Remove URL params from page_location using GTM, and you'll stop seeing all channels, reliant on UTMs—so paid search, display, paid social, email etc (provided you use UTMs, of course). Don't forget: in this case, you remove the URL params in GTM before they get in GA, so if GTM strips params out, GA doesn't see them.
To illustrate my mistake, this is how my GA4 configuration tag in GTM looked like initially:
Bad idea. Don't touch page_location.
The best approach is to just create your own dimension which you would use to store 'clean' URLs, say, page_URI. The reason: you stop relying on GA built-in dimensions that (potentially) are prone to change and you create something of your own that you will have control over and can add to any event as a dimension.
Below is my version of the script in GTM, deployed as a Custom Javascript Variable:
function() {
var params = ['hash', 'email', 'utm_source', 'utm_medium', 'utm_campaign', 'utm_content', 'utm_term', 'gclid', 'fbclid', 'mc_cid', 'mc_eid', 'msclkid']; //Add URL params to be excluded from page URI
var a = document.createElement('a');
var param,
qps,
iop,
ioe,
i;
a.href = {{Page URL}};
if (a.search) {
qps = '&' + a.search.replace('?', '') + '&';
for (i = 0; i < params.length; i++) {
param = params[i];
iop = qps.indexOf('&' + param + '=');
if(iop > -1) {
ioe = qps.indexOf('&', iop + 1);
qps = qps.slice(0, iop) + qps.slice(ioe, qps.length);
}
}
a.search = qps.slice(1, qps.length - 1);
}
return a.href;
}
Two things to mention about the code:
List all params you want to strip out in the array params;
a.href = {{Page URL}} - the code makes use of GTM's built-in variable Page URL (hence double curly brackets) that captures the full URL (without hostname, though). If you feel fancy, you can replace it with plain JS.
So the code above now populates the GTM field/GA4 dimension page_URI in the main configuration tag and any other tags, where I think having a clean URI is useful:
I do realize that this approach uses up one GA4 dimension, but it's a price I'm willing to pay to have a clean URL in the absence of a better solution.
In the GA4 tag in GTM try to set page_location as Field to Set and a Custom JavaScript Variable as value with this definition:
function(){
return document.location.hostname + document.location.pathname;
}
i.e. (note: App+Web is old name of GA4):
You can also use the following JavaScript in the custom JavaScript variable instead of the custom JavaScript mentioned above.
In this custom JavaScript instead of creating a new anchor element, we simply are taking the full page URL and then using the JavaScript's in-built URL() method to convert it to a proper URL that can be programmatically managed and then manage it according to the need.
I'm sharing my script below:
Step 1
Create a custom JavaScript variable inside your GTM and add the following JavaScript code into it.
function() {
// Set the array with the list of query string you would like to remove being shown up in Google Analytics 4
var excuded_query_params = [
'add',
'the',
'query',
'strings',
'you',
'would',
'like',
'to',
'be',
'removed',
'from',
'GA4',
'analytics',
'report'
]
// Get the full Page URL from GTM in-build variables
var page_url_string = {{Page URL}}
// Convert the received URL from string format to URL format
var page_url = new URL( page_url_string )
var page_url_copy = new URL( page_url_string )
// Loop through the query parameters in the URL and if there is any query param which is in the excluded list,
// remove that from the full URL
page_url_copy.searchParams.forEach( function(param_value, param_name) {
if( excuded_query_params.includes( param_name ) ) {
page_url.searchParams.delete( param_name )
}
} )
// Return the final URL
return page_url.toString()
}
Please Note: as we are going to replace the value of page_location a default GA4 variable's data - it is highly recommended that you do not remove the utm_ query parameters from the URL as GA4 reports use that data internally and that may lead to report breaking. So, it's best that you do not remove query parameters like utm_souyrce, utm_campaign etc.
Step 2
Inside your GA4 Configuration Tag, click on Fields to Set and add a new field with the Field Name set as page_location and value set as this custom JavaScript variable.
Step 3
Now it's time to preview inside GTM and deeply.
I have tried to get the client ID with custom javascript but it cannot return the value. Below is the code is tried. Would like to seek help from all experts. Thanks.
function () {
return function () {
try {
var trackers = ga.getAll();
trackers.forEach(function(tracker) {
var cid = tracker.get('clientId');
tracker.set('dimension1', cid);
});
} catch (e) {}
}
}
It cannot return a normal client ID
Your custom variable returns a function, not a value (since the function is never actually executed).
A better way to get the clientId for each current tracker is to use a custom task in Google Analytics (tasks are basically individual steps in the tracker lifecycle, from checking if a client id exists to assemble the payload to actually sending the data). A task is a Javascript function that is added to the GA tag via the "set fields" configuration. Tasks have access to the tracker data model and can add, remove or modify values from the payload.
The only task you can use via GTM is the customTask, which as the name suggests, adds custom capabilities to the tracker.
If you create a custom javascript variable called e.g. "getClientId" with the following code:
function() {
// Modify customDimensionIndex to match the index number you want to send the data to
var customDimensionIndex = 5;
return function(model) {
model.set('dimension' + customDimensionIndex, model.get('clientId'));
}
}
then go to your GA settings tag, and in the "set field" configuration set the field name "customTask" with the variable as value, the clientId will be extracted from the data model and added to the payload as custom dimension.
Better than my explanation is Simo Ahavas GTM tip for setting the client id via custom tasks.
I have a Google Forms to get my delivery order from customer and on the forms I have Date field.
The response of the forms will be filled automatically to order document per response.
I use this scripts:
function autoFill(e) {
var timestamp = e.values[0];
var nama = e.values[1];
var tglBuat = e.values[10];
var file = DriveApp.getFileById(MY_TEMPLATE_FILE_ID);
var folder = DriveApp.getFolderById(OUTPUT_FOLDER_ID);
var copy = file.makeCopy(nama+"_"+timestamp, folder);
var doc = DocumentApp.openById(copy.getId());
var body = doc.getBody();
body.replaceText('#NamaLengkap#', tglBuat);
body.replaceText('#TanggalDibuat#', tglBuat);
doc.saveAndClose();
}
The flow is simple like this:
I prepared Template file for the Order Document paper
Customer will fill the forms
Form result will be kept in certain Google Spreadsheet
The script above on the (3), will be triggered everytime (2) submitted
Voila, I have Order Document filled with customer order details
My template file are something like this:
Customer Name: #NamaLengkap#
Order Date: #TanggalDibuat#
My problem is here in date format, I want the output on my template file using this format "26 August 2020", but the google form only give this format "08/26/2020".
How do I changes it?
I read some article about changing the email format before filling the form, but i don't think this is good solution. Because customer wont care at all.
Solution
You just need to take your date and convert it to a Date String Javascript object as shown below:
function myFunction() {
var shortDate = new Date("03/25/2015");
var longDateFormat = shortDate.toDateString();
Logger.log(longDateFormat);
}
I hope this has helped you. Let me know if you need anything else or if you did not understood something. :)
I have a Data Layer that is giving me information like this from Drupal
dataLayer = [{
"entityType":"node",
"entityBundle":"article",
"entityTaxonomy":
{"funnel_path":{"2":"Find a Park"},
"byline":{"4":"Name1","5":"Name2"}},"drupalLanguage":"en",
"userUid":"1"}
];
</script>
I can easily use GTM's Data Layer variable to pull in entityBundle. How do I set it to pull in the information in byline? I tried entityTaxonomy.byline, but that give me an array. I can set to do entityTaxonomy.byline.4 to get Name1, but that would be silly since the editors would be regularly adding things.
I am planning to add the byline, ultimately, into Custom Dimension 2 in Google Analytics.
I am looking to have the data that goes to Custom Dimension 2 to be Name1, Name2 . Sometimes this will be just one value. Sometimes it can be up to 20 values.
What do I need to do in GTM to get it to register that information?
entityTaxonomy.byline actually gives you an object. You would need to do a bit of processing to get an array that you can join into a string. One possible way would be
temp = [];
Object.keys(test.entityTaxonomy.byline).map(function(key, index) {
temp.push(test.entityTaxonomy.byline[key]);
});
bylines = temp.join(",")
(I'm sure that could be done much more concise). In GTM you would need to create a variable that contains the objects with the bylines, then you could do the processing in a custom javascript variable (which is by definition an anonymous function with a return value)
function() {
var byLineObject = {{bylines}} // created as datalayer var beforehand
temp = [];
Object.keys(byLineObject).map(function(key, index) {
temp.push(byLineObject[key]);
});
return temp.join(",")
}
I thought it'd be easy but, yeah... it wasn't. I already posted a question that went in the same direction, but formulated another question.
What I want to do
I have the collection songs, that has a time attribute (the playing-time of the song). This attribute should be handled different in the form-validation and the backend-validation!
! I'd like to do it with what autoform (and simple-schema / collection2) offers me. If that's possible...
in the form the time should be entered and validated as a string that fits the regex /^\d{1,2}:?[0-5][0-9]$/ (so either format "mm:ss" or mmss).
in the database it should be stored as a Number
What I tried to do
1. The "formToDoc-way"
This is my javascript
// schema for collection
var schema = {
time: {
label: "Time (MM:SS)",
type: Number // !!!
},
// ...
};
SongsSchema = new SimpleSchema(schema);
Songs.attachSchema(SongsSchema);
// schema for form validation
schema.time.type = String // changing from Number to String!
schema.time.regEx = /^\d{1,2}:?[0-5][0-9]$/;
SongsSchemaForm = new SimpleSchema(schema);
And this is my template:
{{>quickForm
id="..."
type="insert"
collection="Songs"
schema="SongsSchemaForm"
}}
My desired workflow would be:
time is validated as a String using the schema
time is being converted to seconds (Number)
time is validated as a Number in the backend
song is stored
And the way back.
I first tried to use the hook formToDoc and converted the string into seconds (Number).
The Problem:
I found out, that the form validation via the given schema (for the form) takes place AFTER the conversion in `formToDoc, so it is a Number already and validation as a String fails.
That is why I looked for another hook that fires after the form is validated. That's why I tried...
2. The "before.insert-way"
I used the hook before.insert and the way to the database worked!
AutoForm.hooks({
formCreateSong: {
before: {
insert: function (doc) {
// converting the doc.time to Number (seconds)
// ...
return doc;
}
},
docToForm: function (doc) {
// convert the doc.time (Number) back to a string (MM:SS)
// ...
return doc;
}
}
});
The Problem:
When I implemented an update-form, the docToForm was not called so in the update-form was the numerical value (in seconds).
Questions:
How can I do the way back from the database to the form, so the conversion from seconds to a string MM:SS?
Is there a better way how to cope with this usecase (different data types in the form-validation and backend-validation)?
I am looking for a "meteor autoform" way of solving this.
Thank you alot for reading and hopefully a good answer ;-)
I feel like the time should really be formatted inside the view and not inside the model. So here's the Schema for time I'd use:
...
function convertTimeToSeconds (timeString) {
var timeSplit = timeString.split(':')
return (parseInt(timeSplit[0]) * 60 + parseInt(timeSplit[1]))
}
time: {
type: Number,
autoValue: function () {
if(!/^\d{1,2}:?[0-5][0-9]$/.test(this.value)) return false
return convertTimeToSeconds(this.value)
}
}
...
This has a small disadvantage of course. You can't use the quickForm-helper anymore, but will have to use autoForm.
To then display the value I'd simply find the songs and then write a helper:
Template.registerHelper('formateTime', function (seconds) {
var secondsMod = seconds % 60
return [(seconds - secondsMod) / 60, secondsMod].join(':')
})
In your template:
{{ formatTime time }}
The easy answer is don't validate the string, validate the number that the string is converted into.
With simpleschema, all you do is create a custom validation. That custom validation is going to grab the string, turn it into a number, and then validate that number.
Then, when you pull it from the database, you'll have to take that number & convert it into a string. Now, simpleschema doesn't do this natively, but it's easy enough to do in your form.
Now, if you wanted to get fancy, here's what I'd recommend:
Add new schema fields:
SimpleSchema.extendOptions({
userValue: Match.Optional(Function),
dbValue: Match.Optional(Function),
});
Then, add a function to your time field (stored as Date field):
userValue: function () {
return moment(this.value).format('mm:ss');
},
dbValue: function () {
return timeToNumber(this.value);
}
Then, make a function that converts a timeString to a number (quick and dirty example, you'll have to add error checking):
function timeToNumber(str) {
str.replace(':',''); //remove colon
var mins = +str.substr(0,2);
var secs = +str.substr(2,2);
return mins * 60 + secs;
}
Then, for real-time validation you can use schema.namedContext().validateOne. To update the db, just send timeToNumber(input.value).