I have to use Google Tag Manager to make a change to fix something temporarily until I gain access to the back-end of a site. I am trying to remove a div container based on class. I have been able to successfully create a custom HTML tag with the following code to add remove the div container like so:
<script>
function changeHtml ()
{
document.getElementsByClassName("signup")[0].style.display = "none";
}
change = changeHtml();
</script>
The issue is that the problematic item in this div container is a form. I quickly found out that if you apply display: none; via css the form still works. The true way to remove this would be to remove it from the DOM. I tried adding the following JS but not having any success:
<script>
const elem = document.getElementsByClassName('signup');
elem.parentNode.removeChild(elem);
</script>
Receiving error:
Javascript Compiler error: Error at line 3, character 1: This language feature is only supported for ECMASCRIPT6 mode of better: const declaration
Use remove() method to remove the selected element from the DOM:
<script>
document.getElementsByClassName('signup')[0].remove();
</script>
You could achieve this easily by trying this instead
<script>
const elem = document.getElementsByClassName('signup')[0];
elem.outerHTML = '';
</script>
As of now, GTM supports ES6 only in custom templates, so the use of const in a custom javascript tag will result in an error. Your description is somewhat generic ("does not work"), so will just assume that this is your problem. Also your tag must be executed only after the element you want to remove actually exists (on DOM ready, or with a visibility trigger).
Related
I am embedding some HTML content inside a webpage and I want it to be isolated from outside CSS. My users are able to create HTML content dynamically and these pages can contain some simple JS.
I've been trying to run these JS inside a shadow root. Since the JS is created dynamicaly when the user creates the page I need a way to make it possible to run the code inside the shadowroot. I'm adding content as follows:
const header = document.createElement('header');
const shadowRoot = document.createElement('body')
shadowRoot.innerHTML = pageContent
var script = document.createElement('script')
script.textContent = `document.querySelectorAll('#ii0b7a'); document.createElement(........`
shadowRoot.appendChild(script)
header.attachShadow({
mode: 'open'
}).appendChild(shadowRoot)
document.getElementById('div-box-one').appendChild(header)
So far so good, but the code won't run correctly, because the script is accessing the document elements and not the shadowRoot. So I should first access shadowroot and then I'll be able to access the elements inside it. To do so, i'm wrapping the js code inside a function and renaming document variable like this:
(function(document) {
// code here
}(document.getElementById('shadow_root').shadowRoot));
But this approach is not working correctly, because in some case I have to use the function "document.createElement" which will then throw an error.
My question is: How can I rename document variable only when needed. For example
document.querySelectorAll('#') = document.getElementById('shadow_root').shadowRoot.querySelectorAll('#')
document.createElement = document.createElement
I know it is almost impossible to use libraries inside shadowRoot and iframe is another solution, but iframe brings me other problems with it. (shadow-dom library)
Is it possible to achieve what I want renaming somehow the document variable?
So I've came up with the following solution:
function documentParse() {
var element = document.getElementById('shadow_root').shadowRoot
element.createElement = function createElement(type) {
return document.createElement(type)
}
element.head = document.head
return element
}
script.textContent = `(function(document) {
//script source text
}(documentParse()));`
....
Wrapping the script in this way will bring every search on document to the scope of elements inside the shadowroot dom.
I have tried to create my first One Note Add In using the JavaScript API. I have tried the example in the MS documentaion (Build your first OneNote task pane add-in). This one works.
Now I want to try to change the formatting of an element in the document. For example I want to change the font colour of a text. However, I have not yet found a way to access the elements in a document.
Can I access elements in a document via a JS Add In to change their "style" property?
How can I do that?
Thanks
Micheal
Finally, I found a way to access the OneNote page content from the JS Add In. You can load the page content using
var page = context.application.getActivePage();
var pageContents = page.contents;
context.load(pageContents);
Now you have access to the page content in the qued commands.
return context.sync().then( function() {
var outline = pageContents.items[0].outline;
outline.appendHtml("<p>new paragraph</p>");
var p = outline.paragraphs;
context.load(p);
...
});
So consequently you can access element by element in document the hirarchy.
I have a code like this
<div class="footer"><p>some text<p></div>
as you can see closing tag for p is omit.
how can I fix that without edit original code?
I think JavaScript can do this.
You can just modify the innerHTML of the footer element.
var footer = document.getElementsByClassName('footer')[0];
footer.innerHTML = "<p>some text</p>";
This will replace the content in the footer.
var div = document.querySelector('div.footer'); // get div element
Array.prototype.forEach.call(div.childNodes, function(childNode){ // for every element inside of div
if (childNode.innerHTML === '') { // check if that element is empty
div.removeChild(childNode); // remove that element
}
});
Working example: http://jsfiddle.net/soygb9cs/
It works because modern browsers automatically closes tags. So you get one additional empty tag if you omit backslash in closing tag.
From <div><p>some text<p></div> browser creates <div><p>some text</p><p></p></div>
JavaScript can do that, however because JavaScript runs after the page has loaded, your web page will still be classed as W3C invalid (https://validator.w3.org/).
If you just have lots of HTML with this error and you're trying to save yourself some manual labour, I suggest using something like notepad++. Copy your HTML into a new document and do a regular expression find and replace like so:
I have written some code that takes a string of html and cleans away any ugly HTML from it using jQuery (see an early prototype in this SO question). It works pretty well, but I stumbled on an issue:
When using .append() to wrap the html in a div, all script elements in the code are evaluated and run (see this SO answer for an explanation why this happens). I don't want this, I really just want them to be removed, but I can handle that later myself as long as they are not run.
I am using this code:
var wrapper = $('<div/>').append($(html));
I tried to do it this way instead:
var wrapper = $('<div>' + html + '</div>');
But that just brings forth the "Access denied" error in IE that the append() function fixes (see the answer I referenced above).
I think I might be able to rewrite my code to not require a wrapper around the html, but I am not sure, and I'd like to know if it is possible to append html without running scripts in it, anyway.
My questions:
How do I wrap a piece of unknown html
without running scripts inside it,
preferably removing them altogether?
Should I throw jQuery out the window
and do this with plain JavaScript and
DOM manipulation instead? Would that help?
What I am not trying to do:
I am not trying to put some kind of security layer on the client side. I am very much aware that it would be pointless.
Update: James' suggestion
James suggested that I should filter out the script elements, but look at these two examples (the original first and the James' suggestion):
jQuery("<p/>").append("<br/>hello<script type='text/javascript'>console.log('gnu!'); </script>there")
keeps the text nodes but writes gnu!
jQuery("<p/>").append(jQuery("<br/>hello<script type='text/javascript'>console.log('gnu!'); </script>there").not('script'))`
Doesn't write gnu!, but also loses the text nodes.
Update 2:
James has updated his answer and I have accepted it. See my latest comment to his answer, though.
How about removing the scripts first?
var wrapper = $('<div/>').append($(html).not('script'));
Create the div container
Use plain JS to put html into div
Remove all script elements in the div
Assuming script elements in the html are not nested in other elements:
var wrapper = document.createElement('div');
wrapper.innerHTML = html;
$(wrapper).children().remove('script');
var wrapper = document.createElement('div');
wrapper.innerHTML = html;
$(wrapper).find('script').remove();
This works for the case where html is just text and where html has text outside any elements.
You should remove the script elements:
var wrapper = $('<div/>').append($(html).remove("script"));
Second attempt:
node-validator can be used in the browser:
https://github.com/chriso/node-validator
var str = sanitize(large_input_str).xss();
Alternatively, PHPJS has a strip_tags function (regex/evil based):
http://phpjs.org/functions/strip_tags:535
The scripts in the html kept executing for me with all the simple methods mentioned here, then I remembered jquery has a tool for this (since 1.8), jQuery.parseHTML. There's still a catch, according to the documentation events inside attributes(i.e. <img onerror>) will still run.
This is what I'm using:
var $dom = $($.parseHTML(d));
$dom will be a jquery object with the elements found
I am writing a script that needs to add DOM elements to the page, at the place where the script is located (widget-like approach).
What is the best way to do this?
Here are the techniques I am considering:
Include an element with an id="Locator" right above the script. Issues:
I don't like the extra markup
If I reuse the widget in the page, several elements will have the same "Locator" id. I was thinking about adding a line in the script to remove the id once used, but still...
Add an id to the script. Issues:
even though it seems to work, the id attribute is not valid for the script element
same issue as above, several elements will have the same id if I reuse the script in the page.
Use getElementsByTagName("script") and pick the last element. This has worked for me so far, it just seems a little heavy and I am not sure if it is reliable (thinking about deferred scripts)
document.write: not elegant, but seems to do the job.
[Edit] Based on the reply from idealmachine, I am thinking about one more option:
Include in the script tag an attribute, for example goal="tabify".
Use getElementsByTagName("script") to get all the scripts.
Loop through the scripts and check the goal="tabify" attribute to find my script.
Remove the goal attribute in case there's another widget in the page.
[Edit] Another idea, also inspired by the replies so far:
Use getElementsByTagName("script") to get all the scripts.
Loop through the scripts and check innerHTML to find my script.
At the end of the script, remove the script tag in case there's another widget in the page.
Out of the box : document.currentScript (not supported by IE)
I've worked for OnlyWire which provides, as their main service, a widget to put on your site.
We use the var scripts = document.getElementsByTagName("script"); var thisScript = scripts[scripts.length - 1]; trick and it seems to work pretty well. Then we use thisScript.parentNode.insertBefore(ga, thisScript); to insert whatever we want before it, in the DOM tree.
I'm not sure I understand why you consider this a "heavy" solution... it doesn't involve iteration, it's a pure cross-browser solution which integrates perfectly.
This works with multiple copies of same code on page as well as with dynamically inserted code:
<script type="text/javascript" class="to-run">
(function(self){
if (self == window) {
var script = document.querySelector('script.to-run');
script.className = '';
Function(script.innerHTML).call(script);
} else {
// Do real stuff here. self refers to current script element.
console.log(1, self);
}
})(this);
</script>
Either document.write or picking the last script element will work for synchronously loaded scripts in the majority of web pages. However, there are some options I can think of that you did not consider to allow for async loading:
Adding a div with class="Locator" before the script. HTML classes has the advantage that duplicates are not invalid. Of course, to handle the multiple widget case, you will want to change the element's class name when done adding the HTML elements so you do not add them twice. (Note that it is also possible for an element to be a member of multiple classes; it is a space-separated list.)
Checking the src of each script element can ensure that tracking code (e.g. Google Analytics legacy tracking code) and other scripts loaded at the very end of the page will not prevent your script from working properly when async loading is used. Again, to handle the multiple widget case, you may need to remove the script elements when done with them (i.e. when the desired code has been added to the page).
One final comment I will make (although you may already be aware of this) is that when coding a widget, you need to declare all your variables using var and enclose all your code within: (JSLint can help check this)
(function(){
...
})();
This has been called a "self-executing function" and will ensure that variables used in your script do not interfere with the rest of the Web page.
Whether you drop a <script> tag in or a <div class="mywidget">, you're adding something to the markup. Personally, I prefer the latter as the script itself is only added once. Too many scripts in the page body can slow down the page load time.
But if you need to add the script tag where the widget is going to be, I don't see what's wrong with using document.write() to place a div.
I just found another method that seems to answer my question:
How to access parent Iframe from javascript
Embedding the script in an iframe allows to locate it anytime, as the script always keeps a reference to its own window.
I vote this the best approach, as it'll always work no matter how many times you add the script to the page (think widget). You're welcome to comment.
What pushed me to consider iframes in the first place was an experiment I did to build a Google gadget.
In many cases this work well (hud.js is the name of the scipt):
var jsscript = document.getElementsByTagName("script");
for (var i = 0; i < jsscript.length; i++) {
var pattern = /hud.js/i;
if ( pattern.test( jsscript[i].getAttribute("src") ) )
{
var parser = document.createElement('a');
parser.href = jsscript[i].getAttribute("src");
host = parser.host;
}
}
Also you can add individual script's name inside them.
either inside some js-script
dataset['my_prefix_name'] = 'someScriptName'
or inside HTML - in the <script> tag
data-my_prefix_name='someScriptName'
and next search appropriate one by looping over document.scripts array:
... function(){
for (var i = 0, n = document.scripts.length; i < n; i++) {
var prefix = document.scripts[i].dataset['my_prefix_name']
if (prefix == 'whatYouNeed')
return prefix
}
}
I haven't had access to internet explorer since forever, but this should work pretty much everywhere:
<script src="script.js"
data-count="30"
data-headline="My headline"
onload="uniqueFunctionName(this)"
defer
></script>
and inside script.js:
window.uniqueFunctionName = function (currentScript) {
var dataset = currentScript.dataset
console.log(dataset['count'])
console.log(dataset['headline'])
}