How to prevent Google AdWords script from prevent reloading in SPA? - single-page-application

I have a SPA built on React JS stack. I'm using react-router to navigate through pages and i need to implement Google AdWords on my website.
<script type="text/javascript">
/* <![CDATA[ */
goog_snippet_vars = function() {
var w = window;
w.google_conversion_id = 333333;
w.google_conversion_label = "33333";
w.google_remarketing_only = false;
}
// DO NOT CHANGE THE CODE BELOW.
goog_report_conversion = function(url) {
goog_snippet_vars();
window.google_conversion_format = "3";
var opt = new Object();
opt.onload_callback = function() {
if (typeof(url) != 'undefined') {
window.location = url;
}
}
var conv_handler = window['google_trackConversion'];
if (typeof(conv_handler) == 'function') {
conv_handler(opt);
}
}
/* ]]> */
</script>
I embed this code in body and i run goog_report_conversion when i click on button which navigates me to another page. Which is unwanted behaviour for SPA.
<Link
className="btn btn-primary"
to="/settings"
onClick={() => goog_report_conversion('site.name/settings')}
>Go to settings</Link>
The problem is that once I do it, it fully reloads my webpage.
I know that this line causes the problem
window.location = url;
But without it script doesn't work.
I also tried to create this event in Google Tag Manager and follow advices given here Google Tag Manager causes full page reload in SPA - React but it didn't help me.
Have anyone faced same problem implementing AdWords in SPA? How did you solve it?

I feel that the implementation example for the asynchronous Remarketing/Conversion snippet is needlessly complex. Here's something that we used in a similar scenario.
First we define a little helper function that we can reuse:
<script type="text/javascript">
function triggerConversion(conversionID, conversionLabel) {
if (typeof(window.google_trackConversion) === "function") {
window.google_trackConversion({
google_conversion_id: conversionID,
google_conversion_label: conversionLabel,
google_remarketing_only: false
});
}
}
</script>
then we include Google's async conversion script (ideally somewhere where it doesn't block rendering):
<script type="text/javascript"
src="http://www.googleadservices.com/pagead/conversion_async.js"
charset="utf-8">
</script>
And now you can track conversions on any element, like so, to adapt your example:
<Link
className="btn btn-primary"
onClick={() => triggerConversion(333333, "33333")}
>Go to settings</Link>

Related

Get Scorm test result from inside an iframe (same domain)

My company is getting a scorm test from another company. (scorm schemaversion 1.2)
We are embedded the test in an iframe like this:
<html>
</head>
<iframe src="exam/scormcontent/index.html" name="course">
</iframe>
</html>
This is the test folder structure:
I am new this scorm solution. What we are trying to do is to get the final result of the scorm test (student passed/failed) in the parent html page.
The html page and the scorm are planned to be hosted on the same domain.
P.S: The entire project involves a react app, where at some stage, the user is supposed to do the scorm test, and he will only be allowed to continue if he passed the test. I am not sure if our plan to use iframe is what we should do. I would love to learn if there is a better option.
I have found a way to do it based on this:
https://github.com/hershkoy/react_scrom
The idea is to inject javascript code into the iframe (requires that the iframe and the parent are on the same domain).
The injected javascript code is listening to the button click events, and send a postMessage event to the parent when detects that the course is completed.
<div id="result"></div>
<input id="btn" type="button" value="Go to course" name="btnOpenPopup" onClick="OpenNewWindow()" />
<iframe style="display:none;" id="myiframe" src="http://localhost/training/content" name="course" frameborder="0" style="overflow:hidden;height:100%;width:100%" height="100%" width="100%"></iframe>
<script type="text/javascript">
const iframe = document.getElementById('myiframe');
const iframeWin = iframe.contentWindow || iframe;
const iframeDoc = iframe.contentDocument || iframeWin.document;
function OpenNewWindow() {
iframe.style.display="block";
document.getElementById('result').innerHTML = "";
document.getElementById('btn').style.display="none";
}
function injectThis() {
//alert("hi!");
document.addEventListener('click', (event) => {
console.log("click!");
let chk_condition = event &&
event.target &&
event.target.href &&
event.target.href.includes("exam_completed");
if (chk_condition) {
event.preventDefault();
event.stopPropagation();
window.parent.postMessage({type: 'course:completed'}, '*');
//window.close();
};
});
};
window.addEventListener('message', event => {
// IMPORTANT: check the origin of the data!
if ( true /*event.origin.startsWith('http://localhost:3002')*/) {
// The data was sent from your site.
// Data sent with postMessage is stored in event.data:
console.log(event.data);
if (event.data.type=="course:completed"){
iframe.style.display="none";
document.getElementById('result').innerHTML = "TEST PASSED!";
};
} else {
// The data was NOT sent from your site!
// Be careful! Do not use it. This else branch is
// here just for clarity, you usually shouldn't need it.
return;
}
});
var script = iframeDoc.createElement("script");
script.append('window.onload = ' + injectThis.toString() + ';');
iframeDoc.documentElement.appendChild(script);
</script>

Next.js - How to make sure <title> is set by the time the history change event fires?

In my Next.js app, I'm setting the <title> tag for individual pages using the recommended method:
import Head from 'next/head'
export default () => <>
<Head><title>My page title</title></Head>
</>
Here's the problem: when the history change event fires, the value of document.title doesn't always match the current URL.
You can test it yourself:
Router.events.on("routeChangeComplete", () => {
if ('browser' in process) {
console.log('--------');
console.log(window.location.href);
console.log(document.title);
}
});
Navigating between pages, you should observe that URL & title are often mismatched. The value of URL is always right, but the value of title is all over the place. It can have:
the right value
the value it had on the previous page
no value at all
This is an issue when using analytics, specifically GTM - Google Tag Manager, which uses the current URL & page title to uniquely identify visited pages.
I've had this issue with Next.js 7, and upgrading to 8 hasn't fixed it.
Do you know of any way to solve this problem? Maybe delaying the history change event until the first render of a component under /pages/?
Thanks!
This is a small hack. setTimeout(()=>{console.log(document.title)}, 0)
I found a work-around by intercepting events sent to window.dataLayer.push and adding a one-second delay in case the event is gtm.historyChange.
Here's my GtagScript component that I'm adding to <Head> under _document.js:
export const GtagScript = () => {
function intercept() {
const scriptTag = document.querySelector('#gtm-js');
if (scriptTag !== null)
scriptTag.addEventListener('load', () => {
window.dataLayer.pushOrig = window.dataLayer.push;
window.dataLayer.push = (e) => {
if (e.event === 'gtm.historyChange') {
setTimeout(function () {
window.dataLayer.pushOrig(e);
console.log(`URL: ${window.location.href} Title: ${document.title}`);
}, 1000);
} else {
window.dataLayer.pushOrig(e);
}
};
});
}
return <>
<script
id="gtm-js"
async
src={`https://www.googletagmanager.com/gtm.js?id=${GA_TRACKING_ID}`}
/>
<script
dangerouslySetInnerHTML={{
__html: `
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '${GA_TRACKING_ID}');
${intercept.toString()}
intercept();`
}}
/>
</>
};
I'll wait a while to see if an official fix comes from the ZEIT team. My solution doesn't actually answer the question. It doesn't set <title>, it just defers the event, which isn't optimal.
You can use setInterval
const firstPageViewEvent = setInterval(() => {
if (document.title) {
// send pageview event
clearInterval(firstPageViewEvent);
}
}, 200);

UIkit 3 Event is not working

I've been trying to add a tooltip to a button that only shown when certain conditions are met. I'm using uikit#3.0.0-beta.35. According to the documentation, I should return false on beforeshow event.
UIkit.tooltip($element, { pos: 'top' });
$element.on('beforeshow', function(){
return false;
});
if(condition){
UIkit.tooltip($element).show();
}
The problem is that the beforeshow function never fires for some reason. I even tried this syntax mentioned in UIkit documentation:
UIkit.util.on($element, 'beforeshow', function () {
return false;
});
Unfortunately, none of these methods worked for me.
the docs has some mistake, switcher has the same problem. the event is triggered on document not the target. you can use this syntax like this:
UIkit.util.on(document, 'event', '#target-id', callback)
the docs confused me a long time :(
The problem with your code is, that you're trying to listen to an event directly on the element, while the event is triggered on the document - there is an error in the documentation, as they say it's triggered on the element, but it's not.
There is also a fresh false bug report regarding this
var $element = $('#hoverButton');
var $check = $('#tooltipToggle');
UIkit.tooltip($element);
$(document).on('beforeshow', $element, function() {
if (!$check.prop('checked')) return false;
});
<!-- UIkit CSS -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/uikit/3.0.0-beta.35/css/uikit.min.css" />
<!-- UIkit JS & jQuery (not required by UIKit anymore) -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/uikit/3.0.0-beta.35/js/uikit.min.js"></script>
<div class="uk-position-center">
<label>show tooltip <input id="tooltipToggle" class="uk-checkbox" type="checkbox"></label><br><br>
<button id="hoverButton" class="uk-button uk-button-default" title="Hello World">Hover</button>
</div>
For Switcher:
$(document).on('show', $('#switcherId'), function(){
console.log('fired');
});
I may need this answer in the future too...

Loading Google Places Autocomplete Async Angular 2

I am trying to instantiate a Google Places Autocomplete input within an Angular 2 component. I use this code to do it:
loadGoogle() {
let autocomplete = new google.maps.places.Autocomplete((this.ref.nativeElement), { types: ['geocode'] });
let that = this
//add event listener to google autocomplete and capture address input
google.maps.event.addListener(autocomplete, 'place_changed', function() {
let place = autocomplete.getPlace();
that.place = place;
that.placesearch = jQuery('#pac-input').val();
});
autocomplete.addListener()
}
Normally, I believe, I would use the callback function provided by the Google API to ensure that it is loaded before this function runs, but I do not have access to it within a component's scope. I am able to load the autocomplete input 90% of the time, but on slower connections I sometimes error out with
google is not defined
Has anyone figured out how to ensure the Google API is loaded within a component before instantiating.
Not sure whether this will help, but I just use a simple script tag in my index.html to load Google API and I never get any error. I believe you do the same as well. I post my codes here, hope it helps.
Note: I use Webpack to load other scripts, except for Google Map API.
<html>
<head>
<base href="/">
<title>Let's Go Holiday</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Google Map -->
<script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?key=<your-key>&libraries=places"></script>
</head>
<body>
<my-app>Loading...</my-app>
</body>
</html>
And then in your component:
...
declare var google: any;
export class SearchBoxComponent implements OnInit {
ngOnInit() {
// Initialize the search box and autocomplete
let searchBox: any = document.getElementById('search-box');
let options = {
types: [
// return only geocoding results, rather than business results.
'geocode',
],
componentRestrictions: { country: 'my' }
};
var autocomplete = new google.maps.places.Autocomplete(searchBox, options);
// Add listener to the place changed event
autocomplete.addListener('place_changed', () => {
let place = autocomplete.getPlace();
let lat = place.geometry.location.lat();
let lng = place.geometry.location.lng();
let address = place.formatted_address;
this.placeChanged(lat, lng, address);
});
}
...
}
I used it the same way as explained above but as per google page speed i was getting this suggestion,
Remove render-blocking JavaScript:
http://maps.googleapis.com/…es=geometry,places&region=IN&language=en
So i changed my implementation,
<body>
<app-root></app-root>
<script src="http://maps.googleapis.com/maps/api/js?client=xxxxx2&libraries=geometry,places&region=IN&language=en" async></script>
</body>
/* Now in my component.ts */
triggerGoogleBasedFn(){
let _this = this;
let interval = setInterval(() => {
if(window['google']){
_this.getPlaces();
clearInterval(interval);
}
},300)
}
You can do one more thing, emit events once the value(google) is received,& trigger your google task
inside them.

IE9 not rendering iframe in ASP.NET application

I have a parent page which has an iframe and also has javascript which will create a form, append it to the iframe, and submits it via POST to an external URL upon page load.
The content from the external URL then loads in the iframe. This works fine in all browsers EXCEPT IE9.
I tried the 'meta http-equiv="X-UA-Compatible" content="IE=8" ' trick and this didn't help. Sometimes the iframe renders the content, sometimes it doesn't upon refresh. Debug statements in the javascript show it is firing each time (each page load) and Fiddler shows the successful request/response to the external URL. It's as if IE9 selectively decides whether to update the DOM.
Also I've noticed is that if there is any sort of delay with the external request (taking a few seconds), then the iframe content never renders. Has anyone experienced this with IE9 and have a solution?
<iframe frameborder="0" height="600px" id="ifPage" runat="server" width="700px" />
<script type="text/javascript" language="javascript">
var alreadyrunflag = 0 //flag to indicate whether target function has already been run
if (document.addEventListener) {//FireFox or Sarafi
document.addEventListener("DOMContentLoaded", function () { alreadyrunflag = 1; GetExternalPageContent() }, false)
}
else if (document.all && !window.opera)
{//IE
addLoadEvent(GetExternalPageContent)
}
function addLoadEvent(func) {
var oldonload = window.onload;
if (typeof window.onload != 'function') {
window.onload = func;
}
else {
window.onload = function () {
if (oldonload) {
oldonload();
}
func();
}
}
}
function GetExternalPageContent() {
var iframe = document.getElementsByTagName("iframe");
if (iframe != null) {
var uniqueString = "embFrame";
iframe[0].contentWindow.name = uniqueString;
var form = document.createElement("form");
form.target = uniqueString;
form.action = '<%=ExternalUrl %>';
form.method = "POST";
//parameter submitted to external URL to get appropriate content
var input = document.createElement("input");
input.type = "hidden";
input.name = "embParam";
input.value = "paramValue1";
form.appendChild(input);
document.body.appendChild(form);
form.submit();
}
}
</script>
I just wanted to let people know that the issue here is that IE doesn't like naming of the iframe content window like this:
iframe[0].contentWindow.name = uniqueString
Instead, the name attribute must be within the iframe tag itself. There were no javascript errors indicating this, it just didn't consistently render. Then, when you need to dynamically reference the iframe content, use:
var iframe = window.frames['embFrame']
Doing it this way solved the issue and now the iframe content is rendered consistently.

Resources