We have a NextJS page combined with a headless CMS.
Within this CMS I added an option for the admin to save arbitrary snippets to be injected either in the head or at the end of the body.
In nextJS I was trying to implement it in two ways:
I injected the HTML within a div with the "dangerouslySetInnerHTML":
{this.bodyScripts && (
<div dangerouslySetInnerHTML={{__html: `${this.bodyScripts}`}} />
)}
This works. However, I have two problems with that. The snippet is not really at the end right before the closing body tag and it is wrapped in an unnecessary div. Both problems are rather preferences than real problems.
This method can not be used in the page head since I do not have an HTML tag I could use to wrap it in.
I tried to inject the snippets in the componentDidMount function of my page component:
public componentDidMount() {
if (this.headerScripts) {
const head = document.getElementsByTagName('head')[0];
head.insertAdjacentHTML('beforeend', `${headerScripts}`);
}
if (this.bodyScripts) {
const body = document.getElementsByTagName('body')[0];
body.insertAdjacentHTML('beforeend',`${this.bodyScripts}`);
}
}
The snippets get injected and it seems they are not HTML encoded or anything. But they do not get executed.
For reference, I tried to create a script element and injecting that:
const body = document.getElementsByTagName('body')[0];
let myScript = document.createElement("script");
myScript.src = 'some URL to a script';
body.appendChild(myScript);
This worked. But it defeats the purpose of injecting arbitrary scripts, the admin should have the option to add whatever he wants. I am aware of the security risks but we decided to do that anyway :)
I also tried to use appendChild:
const body = document.getElementsByTagName('body')[0];
let div = document.createElement("div");
div.innerHTML = `${this.bodyScripts}`;
body.appendChild(div);
This did not work either. I am completely baffled since I do not understand why creating a script tag and injecting it would work but injecting a string variable would not. I used plain javascript to do it and as far as I know, nothing gets sanitized...
Any help would be appreciated :)
Systematically appending elements in head and body might be approached from Next.js custom _app component.
_app component receives Component and pageProps and could:
use next/head to dynamically append elements to the head based on pageProps
append anything after page component based on pageProps
Related
So I have been using v-html tag to render the html in my vue pages.
But I encountered a string which was a proper html file and it contained text kind of like this:
<html xmlns="https://www.w3.org/1999/xhtml">
<head>
....
<style>
</style>
</head>
<body style="....">
</body>
</html>
The problem is, I have the v-html on a div, but this code starts affecting the whole page and adds its styling to the whole page and not only to that specific div.
I tried adding "scope" to the style tags but it did not work. Maybe because there's also a style inline tag on body?
I need to find a way to make the html affect only on the div it is on, and not the whole page.
Your best bet would probably be to have a better control over the HTML added using v-html. I would suggest to parse it before and keep only the <body> tag. You could do it using a regex, but it would be easier using a dom parser lib. Example with DomParser:
const DomParser = require("dom-parser");
const parser = new DomParser();
export default {
// ...
computed: {
html() {
const rawHtml = "<html><body><div>test</div></body></html>"; // This data should come from your server
const dom = parser.parseFromString(rawHtml);
return dom.getElementsByTagName("body")[0].innerHTML;
}
}
}
Please note that it is an oversimplified solution as it does not handle the case where there is no <body> tag.
First, you should be very careful when using external HTML with v-html as it can make your site vulnerable to various sorts of attacks (see Vue docs).
Now if you trust the HTML source, other problem is how to embed it without affecting your own side. There is special element for this case, <iframe> - it is not without risk and you should definitely read a bit on how to make it safe but it should solve your problem because is "sandbox" external HMTL so it does not affect your site.
https://developer.mozilla.org/en-US/docs/Learn/HTML/Multimedia_and_embedding/Other_embedding_technologies
I've created and exported one Angular Element (web component in Angular) as a single script tag (user-poll.js).
To use this Angular Element I just need to specify below two lines in the host site:
<user-poll></user-poll>
<script src="path/to/user-poll.js"></script>
Working JSBin Example
I've following requirements:
Load multiple Angular Elements on the external site.
Load all Angular Elements dynamically
I've following issues:
Loading multiple components on a site gives Angular conflicts, as each component script is a complete Angular app in itself.
To solve conflict issue, I am loading each component in a separate Shadow Dom component (i.e. adding component tag and component script file link inside shadow dom container) so that each component is sandboxed. But component script inside shadow dom not working.
JSBin example of the dynamically loading component inside the shadow dom
<body>
</body>
setTimeout(() => {
loadJS();
}, 2000);
function loadJS() {
var elm = document.createElement("div");
elm.attachShadow({mode: "open"});
elm.shadowRoot.innerHTML = `<user-poll><h2>Custom Web Component - user-poll loaded</h2><script src="https://cdn.rawgit.com/SaurabhLpRocks/795f67f8dc9652e5f119bd6060b58265/raw/d5490627b623f09c84cfecbd3d2bb8e09c743ed4/user-poll.js"><\/\script></user-poll>`;
document.body.appendChild(elm);
}
So, what is the best way to dynamically load multiple Angular Elements on a site?
The fact that the script is not executed is not related to Shadow DOM, but to the way you try to insert the <script> element, that is via a string in innerHTML.
In this case the string is not interpreted and the script not executed.
If you want it to work you must insert the <script> via appendChild().
You can do it directly by adding a <script> element created with createElement(), or by using a <template> element.
1. with createElement()
Create a <script> element and append it to its parent:
var up = document.createElement( 'user-poll' )
var script = document.createElement( 'script' )
script.textContent = 'document.write( "executed" )'
up.appendChild( script )
elm.attachShadow( { mode: 'open' } )
.appendChild( up )
<div id="elm"></div>
2. with a <template> tag
Append the content of a <template> element:
elm.attachShadow( { mode: 'open' } )
.appendChild( tpl.content )
<template id="tpl">
<user-poll>
<script>document.write( 'exected' )</script>
</user-poll>
</template>
<div id="elm"></div>
The script is not executed when the <template> element is parsed, but when its content is appended to the DOM.
PS: anyway I'm not sure it's the best way to load multiple Angular elements: Shadow DOM won't act as a sandbox for Javascript code, as <iframe> does. Instead maybe you'll have to use a module loader.
I have two files (dialog.tag) and (radio.tag) included in the page, compiled and mounted with command riot.mount('*')
I am trying to dynamically add DOM elements to the already mounted riot tag (dialog):
<dialog-tag id="dialog">
<div ref="body"></div>
</dialog-tag>
in another script that runs after the previous tag is mounted:
let dialog = document.getElementById("dialog")._tag;
dialog.refs.body.innerHtml = "<div><radio label='some label'></radio></div>"
What I want to do is have the radio tags compiled after they are added to the dialog tag. Is there a way to so?
i think you are going at it in a wrong way. i'd suggest using the riot builtin tag yield for such things.
It makes it simpler and cleaner.
the tag definition:
<dialog-tag>
</yield>
</dialog-tag>
then the usage
<dialog-tag id="kuku">
dialog content... may include anything you want, including other tags
</dialog-tag>
I'm making a Chrome Extension that changes the DOM of a page. But I would like to give the user an option to switch between the page before the changes and the changed page.
It's a little bit like Google translate where you can change between the orginal language and the translated message.
I could not find anything in my own searches.
I know JavaScript but not JQuery yet.
Thanks for the help.
You could save the entire body in a variable, then start overwriting things. If you want to switch back load up the old body.
You could save all the original DOM content to a variable before running the content script. You can do this by using the following code at the top of your content script:
var originalDOM = document.getElementsByTagName("*");
This saves the entire DOM in an array called originalDOM. The * acts a universal tag, requesting every tag in the document. You can read more about the .getElementsByTagName() API here.
You could try:
var html = document.getElementsByTagName("html")[0];
var page = html.innerHTML;
This will give you everything between the <html> tags.
After the content script is injected, run:
var newPage = html.innerHTML;
Now, whenever you want to switch between the pages, simply run:
html.innerHTML = page; //or newPage
You can read more about the .getElementsByTagName() API here
I'm pretty new to jquery in particular and js in general, so I hope I didn't make a silly mistake.
I have the following js code:
var speechText = "Purpose of use:<br/>";
speechText += "<script>$(function(){$(\".simple\").tooltip();});</script>";
speechText += "simple use";
speechElement.innerHTML = speechText;
This function changes the content of an element on the html page.
When calling this function everything works, including displaying the link "simple use", but the tooltip doesn't appear.
I tried writing the exact thing on the html document itself, and it worked.
What am I missing?
First, Script tags inserted into the DOM using innerHTML as text, will not execute. See Can Scripts be inserted with innerHTML. Just some background on script tags, they're parsed on pageload by default in a synchronous manner meaning beginning with earlier script tags and descending down the DOM tree. However this behaviour can be altered via defer and async attributes, best explained on David Walsh's post.
You can however create a script node, assign it attribute nodes and content and append this node to an node in the DOM (or another node that is inserted into the DOM) as suggested by an answer in the aforementioned SO link (here: SO answer).
Secondly, You don't need to inject that piece of JavaScript into the DOM, you can just use that plugin assignment in the context of the string concatenation. So as an example you might refactor your code like this:
HTML
var speechText = "Purpose of use:<br/>";
speechText += "simple use";
speechElement.innerHTML = speechText;
$(function(){ $(".simple").tooltip(); });