Loading widget on web page - javascript

I'm trying to make a loadable widget that would call an api when a button is clicked.
Widget page itself
widget.html
<html>
<head>
<title>Widget</title>
</head>
<body>
<button id="test"></button>
<script>
window.onload = init;
function clicked() {
// some logic
}
function init() {
document.getElementById('test').addEventListener('click', clicked, false);
}
export {clicked}
</script>
</body>
</html>
The page it embeds on
index.html
<!DOCTYPE HTML>
<html>
<head>
<title>Getting started</title>
</head>
<body>
<div>
<div id="widget_box"></div>
<script src="http://localhost:8080/js/widget.js" type="text/javascript"></script>
<script type="text/javascript">wdgt.init('widget_box');</script>
</div>
</body>
</html>
js code that loads the widget
widget.js
var wdgt= {
idBox: 'wdgt',
url_widget: 'http://localhost:8080/pages/widgets/widget.html',
url_style: 'http://localhost:8080/css/widget.css',
init: function (id) {
console.log("Begin Widget initialization");
if (!id) {
id = this.idBox;
}
if (document.getElementById(id)) {
this.addStyle();
try {
var XHR = ("onload" in new XMLHttpRequest()) ? XMLHttpRequest : XDomainRequest;
var xhr = new XHR();
xhr.open('GET', this.url_widget, true);
xhr.onload = function () {
if (this.response) {
document.getElementById(id).innerHTML = this.response;
}
}
xhr.onerror = function () {
console.log('onerror ' + this.status);
}
xhr.send();
} catch (ignore) {
}
} else {
console.log('The specified block id="' + id + '" is missing');
}
},
addStyle: function () {
style = document.createElement('link');
style.rel = 'stylesheet';
style.type = 'text/css';
style.href = this.url_style;
document.head.appendChild(style);
}
}
If I open the widget in a separate window, the code attached to the button works. But when I try to embed it, nothing happens.
Moreover, in this script, document.getElementById() returns null when trying to find the button.
The back is a simple Spring application that returns index.html

TL;DR: It's not supposed to work. You should use iframe element to fetch and render external pages.
Your script widget.js is trying to render the contents of fetched widget.html into a <div> element.
The browser will to do the best, though it won't be able to create another DOM tree inside of <div>. Because when you feed a whole HTML page to innerHTML, you basically ask the browser to render a document inside a document. This is not allowed.
Still it will render the contents of you document's body. So you'll be able to see the button. But it won't execute any <script> tags. It's a safety measure to prevent XSS attacks. A couple of dirty ways to execute your code do exist, but I won't recommend them.
So what should you do to add an external widget into a page? The answer is neat: meet the <iframe>! This dude is created just for those things. For you case, you should rewrite index.html this way:
<!DOCTYPE HTML>
<html>
<head>
<title>Getting started</title>
</head>
<body>
<iframe src="http://localhost:8080/pages/widgets/widget.html" width="800" height="600"></iframe>
</body>
</html>
With iframes, you don't need to manually fetch your widget HTML. The browser will download and render it inside your iframe. Like a document inside a document with all the styles, embedded resources and scripts.
Please note, that when you embed something using iframe, security limitations apply. You won't be able to directly interact with your main page from inside the iframe and vice versa. It will be like a separate window inside you document. And it will have additional constraints. Please address to the docs.

Related

document.getElementById().onclick not working inside Google Tag Manager

I am using a cookie consent management service, calling it via Google Tag Manager (custom HTML tag). Their script has function CookieControl.open(); which opens a sidebar to allow users to change their preferences.
<script src="https://cc.cdn.civiccomputing.com/9/cookieControl-9.x.min.js"></script>
<script>
var config = {
...
...
...
};
CookieControl.load(config);
window.onload = function () {
var cookiesExist = document.querySelector('#cookies');
if (cookiesExist) {
document.getElementById("cookies").onclick =
function () {
CookieControl.open();
};
}
};
</script>
In my footer, I have added a link <a id="cookies">Cookies</a> which the function in GTM is supposed to target. Unfortunately this is not working and the sidebar does not open when I click on the "Cookies" link. It does sometimes work, randomly, on Safari on the iPad. But it's not consistent.
Any ideas why most of the time this doesn't work (but other times it does)?
This is a working example.
// https://codesandbox.io/s/agitated-leaf-vznep?file=/src/index.js
We attached a click event listener to the button, added an href attribute and prevent its default behavior by using the e.preventDefault function. We also created a dedicated function for the CookieControl.open() method.
You may need to wrap the script inside this event listener to make sure that the document is ready before attaching the click event listener to the button.
window.addEventListener('DOMContentLoaded', (event) => {
console.log('DOM fully loaded and parsed');
// move the content of index.js file here
});
UPDATE: Duplicated the script in the stackoverflow sandbox in case the link above expires.
import "./styles.css";
// load cookie control
function openCookieControl(e) {
console.log("CookieControl open");
// CookieControl.open();
}
let button = document.getElementById("cookies");
button.addEventListener("click", (e) => {
e.preventDefault();
openCookieControl();
});
body {
font-family: sans-serif;
color: #000000;
}
<!DOCTYPE html>
<html>
<head>
<title>Parcel Sandbox</title>
<meta charset="UTF-8" />
<link rel="stylesheet" href="src/styles.css" />
</head>
<body>
Cookies
<script src="src/index.js"></script>
</body>
</html>

Set the title of angular application from the parent tab in chrome

I have an application which opens another angular application in a new tab on clicking a button. I need to override the title of the new tab from the parent tab and set a value. Some mock code below
<!DOCTYPE html>
<html>
<head>
<title>New tab</title>
</head>
<body>
<div>
<input id="toolId" placeholder="ToolId" style="padding: 4px 0; width: 70px;" />
<button id="button">Launch</button>
</div>
<script>
window.onload = function () {
document.getElementById('button').addEventListener('click', () => {
var newWin = window.open('http://localhost:4200', "_blank");
// add a load listener to the window so that the title gets changed on page load
newWin.onload = function() {
newWin.document.title = 'New Title';
console.log('Loaded');
};
// newWin.document.title = 'New Title';
})
}
</script>
</body>
</html>
But when i try to do this , i always see the default value set by the angular application rather than what is set by the above code. What am i missing here ? Is it blocked by chrome by chance, could not find any relavent doc if it is .
You can find an explanation for this in the documentation:
The problem with <title>:
The obvious approach is to bind a property of the component to the HTML like this:
<title>{{This_Does_Not_Work}}</title>
Sorry but that won't work. The root component of the application is an element contained within the tag. The HTML <title> is in the document , outside the body, making it inaccessible to Angular data binding.
You could grab the browser document object and set the title manually. That's dirty and undermines your chances of running the app outside of a browser someday.

Catch 22: Load Script if Element That Depends on Script Exists

My goal is to load javascript in the <head> only if a certain element exists in the <body>.
However, I have a problem: I am loading a Web Component, which is just a <script> hosted elsewhere and is pulled in as:
<script src="https://someurl.com/some-web-component.min.js"></script>
This web component file is huge, so I don't want to pull it in unless it is inserted into body by our Content Management System (CMS).
The constraints I am working under are:
• The <head> is shared between pages, so I need conditional logic
• I have no control over the <body> inserted by the CMS, which will potentially contain the <my-web-component> tag
• I NEED the script to load the web component, so I can't use jQuery's $(document).ready, at least I think I can't - an error will be thrown because the browser won't know the element exists
This plunker partially shows my problem, minus all the web component complexity:
https://plnkr.co/edit/GGif2RNHX1iLAvSk1nUw?utm_source=next&utm_medium=banner&utm_campaign=next&p=preview
Any way around this?
You can use DOMContentLoaded event.
The DOMContentLoaded event is fired when the initial HTML document has
been completely loaded and parsed, without waiting for stylesheets,
images, and subframes to finish loading.
In this case you can look for the Component and add the script with something like the following
document.addEventListener("DOMContentLoaded", function(event) {
if(document.querySelector('Component')){
var script = document.createElement('script');
script.src = 'https://someurl.com/some-web-component.min.js';
document.head.appendChild(script)
}
});
Probably a better approach though would be to add the script in the head with async attribute and later remove it if the component is not found.
Something like this
<script async src = "https://someurl.com/some-web-component.min.js"> </script>
<script >
document.addEventListener("DOMContentLoaded", function(event) {
if (document.querySelector('Component') == null) {
var script = document.querySelector('script[src="https://someurl.com/some-web-component.min.js"]')
document.head.removeChild(script)
}
});
</script>
More about DOM lifecycle events
More about async script loading
I am using $(document).ready and inside this method checking if element exists or not. It is working completely fine for me. Below is code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>jQuery Test Element Exists or Not</title>
<script src="https://code.jquery.com/jquery-1.12.4.min.js"></script>
<script type="text/javascript">
$(document).ready(function() {
var elem = document.querySelector('h1');
var isElemPresent = !!elem;
console.log('Is the element present: ', isElemPresent);
});
</script>
</head>
<body>
<h1>Hello Plunker!</h1>
<script>
var elem = document.querySelector('h1');
var isElemPresent = !!elem;
console.log('Oh NOW it works...: ', isElemPresent);
</script>
</body>
</html>
I am not sure where you are facing issue while using jQuery. There might be some other issue. Above approach is good enough to load script after checking if element is present.
Plunker link:
https://run.plnkr.co/preview/cjgczwlzt000knneldv5d52ea/

DOM & JavaScript - When is the right time to source a JavaScript file?

I'm working on a simple page that uses only <canvas> within the <body> of the page. All of the content is to be loaded through javascript. I am having trouble with using the document in my javascript and I was wondering if anyone could point me in the right direction of using <script> tags. Here is my main question:
What is the appropriate placement of <script> for a function loaded with window.onload
Here is the code I am working with:
index.html
----
<!DOCTYPE html>
<html>
<head>
<title></title>
<script src="window.js" type="text/javascript"></script>
</head>
<body>
<canvas>Canvas is not supported by your browser!</canvas>
</body>
window.js
----
Window = function(doc, win)
{
this.doc = doc;
this.win = win;
this.initialize();
}
Window.prototype =
{
initialize: function()
{
this.doc.documentElement.style.overflow = 'hidden';
this.doc.body.scroll = "no";
this.resize();
this.win.addEventListener('resize', this.resize.bind(this));
},
resize: function()
{
_canvas = this.doc.querySelector('canvas');
_canvas.width = this.win.innerWidth;
_canvas.height = this.win.innerHeight;
}
};
window.onload = new Window(document, window);
In all the tests of this script I have run, the only instance where it works is when the <script> is placed after the initial <body> tag. When I place the <script> in the <head> it gives me an error saying:
Uncaught TypeError: Cannot set property 'value' of null
Is it not a possibility for the sake of a clean looking document to have <script> be in the <head>?
Any clarification or direction on what the proper practice is would be greatly appreciated!
Script tags should go at the bottom of the page typically. This ensures all content has loaded and is ready for interaction...
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
<canvas>Canvas is not supported by your browser!</canvas>
<script src="window.js" type="text/javascript"></script>
</body>
</html>
If you don't put the script in after the element, as far as your script is concerned, that element does not exist. It needs to be in the bottom, or at least after the canvas element.
In your case, it should be in the bottom, after the <canvas> element.
It really doesn't matter where your JS files are loaded. Your problem is that the JS files could possibly load before your DOM is fully drawn. I've had pages where JS at the bottom of the page was executing before the browser was done loading the middle. That's why every JS framework contains something to check if the DOM is ready or not. in jQuery you would use ready
$(document).ready(function() { alert('My DOM is loaded!'); });
Outside of jQuery, you could use DOMContentLoaded. Put this at the bottom of your window.js file and you can load it in your header without issue.
document.addEventListener("DOMContentLoaded", function(event) {
new Window(document, window);
});

Execute Function when iFrame url Changes

I have a signup form which is inside an iframe on my site and I want to create a redirect when the url of the iframe changes (when user successfully signed-up).
These 2 sites are cross domain and I know that it is almost impossible to pull the url cross domains but is there a workaround? I know the src will not change and I was thinking to use onload(), when the iframe loads a second time (when user successfully signed-up), execute a function to redirect to a thank you page.
Here is an example using the javascript 'Porthole'.
Its possible, but keep in mind the safety issues with iframes. The solution: if you have control of the original page + iframe, you can 'trick' the browser by implementing some javascripts on both sides.
First create a 'proxy' page on both domains. Name it 'proxy.html' or something (note: you have to use 'porthole.min.js', you can get this from the sources in the bottom)
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<!-- Replace the url with your own location -->
<script type="text/javascript" src="porthole.min.js"></script>
<script type="text/javascript">
window.onload=function(){ Porthole.WindowProxyDispatcher.start(); };
</script>
</head>
<body>
</body>
</html>
On the parent page: (refer to the iframe proxy.html page)
<script type="text/javascript" src="porthole.min.js"></script>
<iframe id="guestFrame" name="guestFrame" src="http://iframe.otherdomain.com/"></iframe>
<script type="text/javascript">
var iframeDomain = 'http://iframe.otherdomain.com';
var redirectUrl = 'http://www.mydomain.com/redirect-to/signed-up';
function onMessage(messageEvent) {
if (messageEvent.origin == iframeDomain) {
if (messageEvent.data["action"]
&& messageEvent.data["action"] == 'signed-up) {
window.location.href = redirectUrl; // The final action!
// This is the eventual redirect that will happen
// once your visitor has signed-up within the iframe
}
}
}
var windowProxy;
window.onload=function(){
// Create a proxy window to send to and receive messages from the iFrame
windowProxy = new Porthole.WindowProxy(
'http://iframe.otherdomain.com/proxy.html', 'guestFrame');
// Register an event handler to receive messages;
windowProxy.addEventListener(onMessage);
};
</script>
On the iframe page (refer to the parent proxy.html page)
<script type="text/javascript">
var windowProxy;
window.onload=function(){
// Create a proxy window to send to and receive messages from the parent
windowProxy = new Porthole.WindowProxy(
'http://www.mydomain.com/proxy.html');
// Register an event handler to receive messages;
windowProxy.addEventListener(function(event) {
// handle event (not used here, the iframe does not need to listen)
});
};
</script>
From the iframe you can send a message with javascript to the parent page (and also the other way). If you use only 1 javascript within the iframe domain, you can do something like this to send a message to the parent frame when the url is changed to something specific, for example 'signed-up.php' (untested, but you'll get the idea)
<script type="text/javascript">
window.onload=function(){
if(window.location.href.indexOf("signed-up.php") > -1) {
windowProxy.post({'action': 'signed-up'}); // Send message to the parent frame
// On the parent page, the function 'onMessage' is triggered.
}
};
</script>
Sources:
http://ternarylabs.github.io/porthole/
Github: https://github.com/ternarylabs/porthole
Demo: http://sandbox.ternarylabs.com/porthole/

Categories

Resources