Dynamically change the <script> tag src - javascript

Dynamically change the src tag
I am integrating Google analytics into my Angular app. It requires to add a google tracking id in the script-src and later. It needs to load at first before anything else.
While it works fine for one env, I wanted to have different Ids for different enviorment.
I am trying something like:
<script>
var googleTrackingId;
switch (window.location.hostname) {
case 'mycompany.com':
googleTrackingId = 'UA-123123-3'; // production
break;
case 'staging.mycompany.com':
googleTrackingId = 'UA-123123-2'; // staging
break;
default:
googleTrackingId = 'UA-123123-1'; // localhost
}
</script>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=${googleTrackingId}"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', googleTrackingId);
</script>
But the src tag is not changing.

You can find the desired tracking ID based on hostname first, then create the <script> tag.
<script>
var googleTrackingId;
switch (window.location.hostname) {
case 'mycompany.com':
googleTrackingId = 'UA-123123-3'; // production
break;
case 'staging.mycompany.com':
googleTrackingId = 'UA-123123-2'; // staging
break;
default:
googleTrackingId = 'UA-123123-1'; // localhost
}
document.write( '<scr' + 'ipt async src="https://www.googletagmanager.com/gtag/js?id=' + googleTrackingId + '"></scr' + 'ipt>' );
</script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', googleTrackingId);
</script>

Using Javascript, you can get the <script> element by ID, and append whatever you want to the element's src attribute.
Edit: You can alter the script element immediately when it is loaded, using the script's onload event handler:
var analyticsScript = document.getElementById('analytics');
analyticsScript.onload = function() {
analyticsScript.src += '?id=3wrwerf45r3t36y645y4';
console.log(analyticsScript.src);
}
<script src='https://some_source' id="analytics"></script>
Why your approach does not work
In your HTML code, you are trying to use template literals here:
<script async src="https://www.googletagmanager.com/gtag/js?id=${googleTrackingId}"></script>
However, template literals work in Javascript, not in HTML (at least not to my knowledge). Also, template literals are wrapped inside backticks (`), not double quotes (") !

Related

Cookie Consent Banner [duplicate]

I am using React/Nextjs for my website and the react-cookie-consent library. It creates a pop up where the user can agree to the cookie policy. If agreed, a cookie is set: CookieConsent with value "true".
Additionally, I want to use Google Analytics to track users if they agree (click Accept on the popup).
But it doesnt work: The Google Analytic cookies _ga and G_ENABLED_IDPS are set before the user clicks on the pop up.
Funny thing is, I only realized that on the Microsoft Edge Browser. In Chrome, these cookies are not set before the user gives consent.
This is my current code in _document.js:
<Head>
{/* Global Site Tag (gtag.js) - Google Analytics */}
<script
async
src={`https://www.googletagmanager.com/gtag/js?id=${GA_TRACKING_ID}`}
/>
<script
dangerouslySetInnerHTML={{
__html: `
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
`}}
/>
<script type="text/javascript" src="/static/blockReactDevTools.js" />
<link href="https://fonts.googleapis.com/css?family=Cabin&display=swap" rel="stylesheet" />
</Head>
I played around using some hints from the internet, and came up with this version which also doesn't work:
<Head>
{/* Global Site Tag (gtag.js) - Google Analytics */}
<script
async
src={`https://www.googletagmanager.com/gtag/js?id=${GA_TRACKING_ID}`}
/>
<script
dangerouslySetInnerHTML={{
__html: `
var gtagId = '${GA_TRACKING_ID}';
window['ga-disable-' + gtagId] = true;
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
function getCookie(cname) {
var name = cname + "=";
var ca = document.cookie.split(';');
for(var i = 0; i < ca.length; i++) {
var c = ca[i];
while (c.charAt(0) == ' ') {
c = c.substring(1);
}
if (c.indexOf(name) == 0) {
return c.substring(name.length, c.length);
}
}
return "";
}
window.addEventListener("load", function() {
var isCookieConsentTrue = getCookie("CookieConsent") == 'true';
if(isCookieConsentTrue){
window['ga-disable-' + gtagId] = false;
alert("Cookie Consent given!");
} else {
alert("Cookie Consent NOT given!");
window['ga-disable-' + gtagId] = true;
}
});
`}}
/>
<script type="text/javascript" src="/static/blockReactDevTools.js" />
<link href="https://fonts.googleapis.com/css?family=Cabin&display=swap" rel="stylesheet" />
</Head>
I don't know if this is a nextjs specific issue, or something plain stupid general.
Can anyone guide me to a working solution?
EDIT: I tried the "Universal Analytics" approach of the suggestion. Suggested Solution make my helper functions to log events and pageviews fail (see below). Do I need also the gtag manager?
Use gtag's consent config options. Currently, the following can be put before any data measurement commands (config or event) are run in the header:
gtag('consent', 'default', {
'ad_storage': 'denied',
'analytics_storage': 'denied'
});
Then run this once the user has approved or if a consent cookie is present:
gtag('consent', 'update', {
'ad_storage': 'granted',
'analytics_storage': 'granted'
});
If you use Facebook Pixel, it works in a similar way. E.g. fbq('consent', 'revoke'); then fbq('consent', 'grant').
The way you were doing it is Opt-out. The GA cookie is always set, as soon as the client requests the gtag.js. This however doesn't comply with GDPR. What you should look into is Opt-in, so that no GA cookie is set without consenting.
The general idea is to async load the gtag.js once the client has consented. For full functionality of gtag functions you have to load the gtag.js on every page-load if the client already consented. Best practice to do this is with a cookieconsent cookie set on consent.
There's a widely used js library for this, which generates a popup and sets the consent-cookie for you.
Reference:
https://www.osano.com/cookieconsent/documentation/javascript-api/
You can generate code for the layout of your cookie banner by clicking Start Coding here:
https://www.osano.com/cookieconsent/download/
https://github.com/osano/cookieconsent/blob/dev/examples/example-7-javascript-api.html
Following code has to be implemented on every page in the <head> section:
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/cookieconsent2/3.1.1/cookieconsent.min.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/cookieconsent2/3.1.1/cookieconsent.min.js" data-cfasync="false"></script>
<script>
var popup;
window.addEventListener('load', function(){
window.cookieconsent.initialise({
//set revokeBtn if you don't want to see a tiny pullup bar overlapping your website
//if revokeBtn is set, make sure to include a link to cookie settings in your footer
//you can open your banner again with: popup.open();
//revokeBtn: "<div class='cc-revoke'></div>",
type: "opt-in",
theme: "classic",
palette: {
popup: {
background: "#000",
text: "#fff"
},
button: {
background: "#fd0",
text: "#000"
}
},
onInitialise: function(status) {
// request gtag.js on page-load when the client already consented
if(status == cookieconsent.status.allow) setCookies();
},
onStatusChange: function(status) {
// resquest gtag cookies on a new consent
if (this.hasConsented()) setCookies();
else deleteCookies(this.options.cookie.name)
},
/* enable this to hide the cookie banner outside GDPR regions
law: {
regionalLaw: false,
},
location: true,
},
*/
function (p) {
popup = p;
})
});
//it is absolutely crucial to define gtag in the global scope
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '${GA_TRACKING_ID}', {'anonymize_ip': true});
function setCookies() {
var s = document.createElement('script');
s.type = "text/javascript"
s.async = "true";
s.src = "https://www.googletagmanager.com/gtag/js?id=${GA_TRACKING_ID}";
var x = document.getElementsByTagName('script')[0];
x.parentNode.insertBefore(s, x);
// you can add facebook-pixel and other cookies here
};
function deleteCookies(cookieconsent_name) {
var keep = [cookieconsent_name, "DYNSRV"];
document.cookie.split(';').forEach(function(c) {
c = c.split('=')[0].trim();
if (!~keep.indexOf(c))
document.cookie = c + '=;' + 'expires=Thu, 01 Jan 1970 00:00:00 UTC;path=/';
});
};
</script>
Note:
Make sure that the gtag.js is loaded on every page-load once the consent-cookie was set to allow. Use event_callback to see if a gtag event was sent. You can use the gtag function without checking for the consent-cookie. If gtag.js is not present it just adds elements to the window.dataLayer without any functionality. To avoid errors, the function gtag() has to be declared in global scope and before use.
// cookie-check is not necessary when gtag is in global scope
//if(popup.hasConsented()) {
gtag('event', 'sign_up', {
'method': 'Google',
'event_callback': function(){alert('event was sent');}
});
//}
You don't have to send an extra pageview event, unless you want to manually specify the path. setCookies() already sends the current document path along with the config

How can I load script tags from a string?

An external source is providing the content of the script tags written as HTML in a string.
I need to add these script tags into the head.
If I do it like this, all script tags are added to the DOM in head tag, however none of the sources are actually being loaded (devtools network tab).
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
</head>
<body>
<script>
let sLoadThis = '<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/underscore#latest/underscore-umd-min.js">'
sLoadThis = sLoadThis + "<" + "/script>"
let oScript = $(sLoadThis).get(0);
document.head.appendChild(oScript);
</script>
</body>
</html>
If I sort of use the jQuery as an interpreter to then insert the script tag with vanilla JS, it works:
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
</head>
<body>
<script>
let sLoadThis = '<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/underscore#latest/underscore-umd-min.js">'
sLoadThis = sLoadThis + "<" + "/script>"
let oScript = $(sLoadThis).get(0);
var vanillaScript = document.createElement('script')
vanillaScript .type = 'text/javascript'
vanillaScript .src = oScript.src
document.head.appendChild(vanillaScript )
</script>
</body>
</html>
I would like to understand why it doesn't work in my first example.
The second example runs because you 're using a dom node. That 's how appendChild() works. The jQuery get() method works with existing dom elements. The only thing you 've got is a string, that does not exist in your dom yet. Try the following.
let script = $.parseHtml(sLoadThis);
console.log(script.get(0)); // should be a script dom node element
document.head.appendChild(script.get(0));
In vanilla JavaScript its even easier and much more faster than jQuery because you don 't have to use a dependency.
const fragment = document.createRange().createContextualFragment(htmlStr);
document.head.appendChild(fragment);
As far as I know there are 4 ways to create a DOM element from a string:
OPTION 1: innerHTML
OPTION 2: insertAdjacentHTML
OPTION 3: DOMParser().parseFromString
OPTION 4: createRange().createContextualFragment
HTML5 specifies that a <script> tag inserted with one of the 3 first options should not execute because it could became a security risk. See this
So, in the example, although all script tags are added to the DOM in the <head> section, only the option number 4 will be executed.
const str1 = "<script>alert('option 1 executed')" + "<" + "/script>"
const str2 = "<script>alert('option 2 executed')" + "<" + "/script>"
const str3 = "<script>alert('option 3 executed')" + "<" + "/script>"
const str4 = "<script>alert('option 4 executed')" + "<" + "/script>"
// OPTION 1
const placeholder1 = document.createElement('div')
placeholder1.innerHTML = str1
const node1 = placeholder1.firstChild
document.head.appendChild(node1)
// OPTION 2
const placeholder2 = document.createElement('div')
placeholder2.insertAdjacentHTML('afterbegin', str2)
const node2 = placeholder2.firstChild
document.head.appendChild(node2)
// OPTION 3
const node3 = new DOMParser().parseFromString(str3, 'text/html').head.firstElementChild
document.head.appendChild(node3)
// OPTION 4
const node4 = document.createRange().createContextualFragment(str4)
document.head.appendChild(node4)

Is that possible to put Template7 code in a separate file rather than in html

I am using a framework called Framework7.
In my index.html, I have some Template7 code, like this format
<script type="text/template7" id="commentsTemplate">
{{#each this}}
<div> test this template 7 code </div>
</script>
However, I want to have this part of code into an another separated file (Just like I can have many other *.js files in, say, a static folder and refer to the file by "static/*.js).
I have tried to use a typical way to import js
<script type="text/template7" id="storiesTemplate" src="js/template.js"></script>
But it doesn't work, there is also no demo/sample code in the documentation.
Any help is appreciated!
You can do it. The idea behind is to include a HTML file in a HTML file. I can tell at least 3 ways that this can happen, but personally I fully validated only the third.
First there is a jQuery next sample is taken from this thread
a.html:
<html>
<head>
<script src="jquery.js"></script>
<script>
$(function(){
$("#includedContent").load("b.html");
});
</script>
</head>
<body>
<div id="includedContent"></div>
</body>
</html>
b.html:
<p> This is my include file </p>
Another solution, I found here and doesn't require jQuery but still it's not tested: there is a small function
My solution is a pure HTML5 and is probably not supported in the old browsers, but I don't care for them.
Add in the head of your html, link to your html with template
<link rel="import" href="html/templates/Hello.html">
Add your template code in Hello.html. Than use this utility function:
loadTemplate: function(templateName)
{
var link = document.querySelector('link[rel="import"][href="html/templates/' + templateName + '.html"]');
var content = link.import;
var script = content.querySelector('script').innerHTML || content.querySelector('script').innerText;
return script;
}
Finally, call the function where you need it:
var tpl = mobileUtils.loadTemplate('hello');
this.templates.compiledTpl = Template7.compile(tpl);
Now you have compiled template ready to be used.
=======UPDATE
After building my project for ios I found out that link import is not supported from all browsers yet and I failed to make it work on iphone. So I tried method number 2. It works but as you might see it makes get requests, which I didn't like. jquery load seems to have the same deficiency.
So I came out with method number 4.
<iframe id="iFrameId" src="html/templates/template1.html" style="display:none"></iframe>
and now my loadTemplate function is
loadTemplate: function(iframeId, id)
{
var iFrame = document.getElementById(iframeId);
if ( !iFrame || !iFrame.contentDocument ) {
console.log('missing iframe or iframe can not be retrieved ' + iframeId);
return "";
}
var el = iFrame.contentDocument.getElementById(id);
if ( !el ) {
console.log('iframe element can not be located ' + id );
return "";
}
return el.innerText || el.innerHTML;
}
How about lazy loading and inserting through the prescriptions?
(function (Template7) {
"use strict";
window.templater = new function(){
var cache = {};
var self = this;
this.load = function(url)
{
return new Promise(function(resolve,reject)
{
if(cache[url]){
resolve(cache[url]);
return true;
}
if(url in Template7.templates){
resolve(Template7.templates[url]);
return true;
}
var xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.onload = function() {
if(this.status == 200 && this.response.search('<!DOCTYPE html>') == -1){
cache[url] = Template7.compile(this.response);
resolve(cache[url]);
}else{
reject(`Template ${url} not found`);
}
};
xhr.send();
})
}
this.render = function(url, data)
{
return self.load(url)
.then(function(tpl){
return tpl(data) ;
});
}
this.getCache = function()
{
return cache;
}
}
})(Template7);
Using :
templater.render('tpl.html').then((res)=>{ //res string })
Or :
templater.load('tpl.html').then( tpl => { Dom7('.selector').html( tpl(data) ) } )
It is possible to define your templates in .js-files. The template just needs to be a string.
Refer to this [JSFiddle] (https://jsfiddle.net/timverwaal/hxetm9rc/) and note the difference between 'template1' and 'template2'
var template1 = $$('#template').html();
var template2 = '<p>Hello, my name is still {{firstName}} {{lastName}}</p>'
template1 just extracts the content of the <script> and puts it in a string.
template2 directly defines the string

Google prettify plugin on IE browser not working

I am using Google Prettify to generate a JavaScript document preview.
The plugin works in Chrome (if I refresh the page 2 to 3 times), but not in IE9 and IE11.
Due to dynamic rendering, I can't set onload="prettyPrint();" on my body tag, so I am using
<script>window.onload = prettyPrint </script>
I also tried this, with the same result
<script>
$(document).ready(function () {
prettyPrint()
});
</script>
I tried to debug run_prettify.js but it says undefined
var prettyPrint;
Here is the documentation
calling js file in end of body.
<script src="${url.context}/res/components/preview/code-prettify-master/prettify.js?autoload=true&lang=css'" />
<script src="${url.context}/res/components/preview/code-prettify-master/run_prettify.js" />
<script src="${url.context}/res/components/preview/code-prettify-master/lang-css.js" />
this is the code, where i am generating <pre> tags
var resultDiv = YAHOO.util.Dom.getElementsByClassName("previewer CodePrettifyMaster")[0];
var preDiv = document.createElement('pre');
preDiv.id = this.wp.id + '_prettify';
preDiv.className = "prettyprint lang-scm linenums";
var ganttContentDiv = document.createElement('div');
ganttContentDiv.id = 'contentContainer';
ganttContentDiv.appendChild(preDiv);
resultDiv.appendChild(ganttContentDiv);

Creating a global analytics javascript variable and using those values in Google Analytics / Remarketing

So, here's what I'm trying to do. I'm working on an ecommerce system, with multiple analytics entities. I want to create a javascript variable that they can all access.
Checkout page example:
<script type="text/javascript">
(function(_analytics){
_analytics.page_type = '$page_type';
_analytics.cart = null;
_analytics.order = null;
_analytics.product = null;
if( $page_type == 'order_confirmation' ){
_analytics.order.products = [];
_analytics.order.total = '$order_total';
foreach ( $purchased_product ){
_analytics.order.products.push({
'sku' : $sku,
'price' : $price,
'quantity' : $quantity
});
}
}
}(_analytics = window._analytics || {}
));
</script>
This is the first tag on my page. After this tag, I include Google analytics, Google remarketing and the other analytics scripts. In those scripts I will loop through the javascript variables that are already created in order to track purchases.
Remarketing tag that comes after the first tag:
<script type="text/javascript">
var google_tag_params = {
ecomm_prodid: '',
ecomm_pagetype: _analytics.page_type,
ecomm_totalvalue = _analytics.order.total;
};
/* <![CDATA[ */
var google_conversion_id = XXXXXX;
var google_custom_params = window.google_tag_params;
var google_remarketing_only = true;
/* ]]> */
</script>
Are there any disadvantages or gotchas that I should be aware of when trying to implement something like this? Google tag assistant complains that the Google remarketing page type variable is incorrectly set, but if I console.log(google_tag_params.ecomm_pagetype) I get the correct value.
Just in case anyone has a similar problem / question I figured this out. I was using a "dummy" remarketing id to test (0000). Apparently, the remarkting tag didn't like this value. Once I changed it to the actual remarketing id, it worked just fine.

Categories

Resources