How to load Scripts on AJAX requests? - javascript

I'm trying to make a SPA web.
Apparently if i load content with AJAX on the innerHTML of an element, the script tag in the AJAX content won't work.
The problem is that i need to make another AJAX request in the AJAX content.
I can't just use the JavaScript code of the base template because at the initial page loading, where i bind the events to the elements, the AJAX content is not loaded, so no binding.
STRUCTURE
Basically what i want to do is this:
Base Template: it has a navigation bar that loads AJAX content on a div called ajaxContent, the links Products, FAQ and About Us load the respective templates products.html... on the div ajaxContent.
Products Template: This is the products catalog, that has another navigation bar with the category names, it should behave similar to the nav in the base template: loading the AJAX content in a div called AjaxCatalog, but i can't make a AJAX request in this template because the script tags won't work.
I'm doing this with Flask.
Thanks!

You haven't shown how you are loading the HTML - in fact you've shown no code at all
However, if your AJAX is getting the HTML as a string, you can use the following
const loadHtml = (text, dest) => {
const p = new DOMParser();
const doc = p.parseFromString(text, 'text/html');
const frag = document.createDocumentFragment();
while (doc.body.firstChild) {
frag.appendChild(doc.body.firstChild);
}
const ret = Promise.all([].map.call(frag.querySelectorAll('script'), script =>
new Promise(res => {
const scriptParent = script.parentNode || frag;
const newScript = document.createElement('script');
if (script.src) {
newScript.addEventListener('load', e => {
res({src:script.src, loaded: true});
});
newScript.addEventListener('error', e => {
res({src:script.src, loaded:false});
});
newScript.src = script.src;
} else {
newScript.textContent = script.textContent;
res({src:false, loaded:true});
}
scriptParent.replaceChild(newScript, script);
})
));
dest = document.querySelector(dest);
if (replace) {
dest.innerHTML = '';
}
dest.appendChild(frag);
return ret;
};
usage
let yourHTMLtext = '....'; // this would be the result of your AJAX call
loadHTML(yourHTMLtext, '#ajaxContent').then(results => {
// at this point, all scripts have been loaded, if that's something you need to know
});

Related

When importing an external HTML page and appending it to a div in Angular, the associated JavaScript does not function properly

I need to append an external HTML page to a div in my Angular app. To achieve this, I'm making an HTTP call to fetch the entire HTML content along with the associated CSS and JavaScript. I'm extracting the styles and scripts separately and using Renderer2 to append them to the document properly.
However, it appears that the script is not working properly. There is a slider in the HTML page that did not load correctly. I've created a StackBlitz app to demonstrate this issue. Here is the link:
app
Inside the Angular app, this slider is missing.
Code sample:
app.component.ts
<p>Place your website url to load into Angular</p>
<div style="margin:20px auto; width:80%" [innerHtml]="htmlContent"></div>
and inside the .ts file:
ngOnInit() {
let styleElement: HTMLElement;
let scriptElement: HTMLElement;
this.http
.get(
'https://cdn.cohora-test.com/t/T_T062ob1/TEMPLATE/0/P07Znp1/cf742d62-fb59-49d4-81b1-f027ac6b5649.html',
{ responseType: 'text' }
)
.subscribe((html) => {
const doc = new DOMParser().parseFromString(html, 'text/html');
console.log('onTemplateSelect ~ doc', doc);
const styleTag = doc.querySelector('style');
const scriptTag = doc.querySelector('script');
let styleRules = '';
let scriptCode = '';
if (styleTag) {
styleRules = styleTag.innerHTML;
}
if (scriptTag) {
scriptCode = scriptTag.innerHTML;
}
// Append the style rules to the head of the document
styleElement = this.renderer.createElement('style');
const styleText = this.renderer.createText(styleRules);
this.renderer.appendChild(styleElement, styleText);
this.renderer.appendChild(document.head, styleElement);
// Append the script to the body of the document
scriptElement = this.renderer.createElement('script');
const scriptText = this.renderer.createText(scriptCode);
this.renderer.appendChild(scriptElement, scriptText);
this.renderer.appendChild(document.body, scriptElement);
// Sanitize and assign the HTML content to the component property
const safeHtml = this.sanitizer.bypassSecurityTrustHtml(
doc.documentElement.outerHTML
);
this.htmlContent = safeHtml;
});
}
What have a done wrong here? can someone please help?
Please use the app link in the stack blitz for coding samples.

Returned JavaScript value wont render inside a div element

This a basic question (Posting this again, as it was not re-opened after I updated the question). But I couldn't find any duplicates on SO.
This is a script I intend to use in my project on different pages. The purpose is to override the default ID shown in a span element to the order number from the URL parameter session_order. This doesn't affect anything and only enhances the UX for my project.
scripts.js (loaded in the header):
function get_url_parameter(url, parameter) {
var url = new URL(url);
return url.searchParams.get(parameter);
}
And in my HTML template, I call the function this way,
<div onload="this.innerHTML = get_url_parameter(window.location.href, 'session_order');">24</div>
Also tried this,
<div><script type="text/javascript">document.write(get_url_parameter(window.location.href, 'session_order'));</script></div>
When the page is rendered, nothing changes. No errors or warnings in the console either for the first case.
For the second case, console logged an error Uncaught ReferenceError: get_url_parameter is not defined, although script.js loads before the div element (without any errors).
Normally, I'd do this on the server-side with Flask, but I am trying out JavaScript (I am new to JavaScript) as it's merely a UX enhancement.
What am I missing?
Try this:
// This is commented because it can't be tested inside the stackoverflow editor
//const url = window.location.href;
const url = 'https://example.com?session_order=13';
function get_url_parameter(url, parameter) {
const u = new URL(url);
return u.searchParams.get(parameter);
}
window.onload = function() {
const el = document.getElementById('sessionOrder');
const val = get_url_parameter(url, 'session_order');
if (val) {
el.innerHTML = val;
}
}
<span id="sessionOrder">24</span>
Define the function you need for getting the URL param and then on the window load event, get the URL parameter and update the element.
Here you go. Try to stay away from inline scripts using document.write.
function get_url_parameter(url, parameter) {
var url = new URL(url);
return url.searchParams.get(parameter);
}
window.addEventListener('load', function() {
const url = 'https://yourpagesdomain.name/?session_order=hello'; //window.location.href;
const sessionOrder = get_url_parameter(url, 'session_order');
document.getElementById('sessionOrder').innerText = sessionOrder;
});
<div id="sessionOrder"></div>
The order of your markup and script matters...
<div></div>
<script>
function get_url_parameter(url, parameter) {
var url = new URL(url);
return url.searchParams.get(parameter);
}
</script>
<script>
document.querySelector('div').innerHTML = get_url_parameter('https://example.com?session_order=2', 'session_order');
</script>

Javascript - DOM parser load ajax requests, scripts no run

When a user clicks on a link instead of loading a whole new page I load the new page's HTML data through an ajax request (and also with a query string I get the server to not send the nav bar data each time) the resulting data from the ajax request I then put through DOMParser to allow me to just get the content from the div with the id of "content" and replace the current document's "context" div's innerHTML.
After doing a request through this method though any script tags within the newDOM don't run after being put in the content div. Also, it does appear to run while it is in newDOM either, because if you have a script that instantly edits the document while it loads there is no effect when you log out newDOM
AjaxRequest(href, function(data) {
var parser = new DOMParser();
var newDOM = parser.parseFromString(data.responseText, "text/html");
//Setup new title
var title = '';
if (newDOM.getElementsByTagName('title').length > 0 && newDOM.getElementsByTagName('title')[0] !== null) {
title = newDOM.getElementsByTagName('title')[0].innerHTML;
} else {
title = rawhref;
}
document.title = title;
history.pushState({}, title, rawhref);
if (newDOM.getElementById('content') === null) {
//If there is an error message insert whole body into the content div to get full error message
document.getElementById('content').appendChild(newDOM.getElementsByTagName('body')[0]);
} else {
document.getElementById('content').appendChild(newDOM.getElementById('content'));
}
MapDOM();
if (typeof(onPageLoad) == "function") {
onPageLoad();
}
});
Note: the variable "rawhref" is just the request URL without ?noheader so that it will be easier for users to go back though their history.
NOTE: Also after any new load I also have a function that overwrites any new a tag so that it will work though this method for the next new page.
Also, it would be much preferred if the answer didn't use jQuery.
Some one just answered this and while I was testing it they deleted their solution.... Um, thanks so much who ever you were, and for anyone in the future who has this problem here is the code they showed, but I didn't have time to fully understand why it worked.... but I think can work it out.
function subLoader(dest, text) {
var p = new DOMParser();
var doc = p.parseFromString(text, 'text/html');
var f = document.createDocumentFragment();
while (doc.body.firstChild) {
f.appendChild(doc.body.firstChild);
}
[].map.call(f.querySelectorAll('script'), function(script) {
var scriptParent = script.parentElement || f;
var newScript = document.createElement('script');
if (script.src) {
newScript.src = script.src;
} else {
newScript.textContent = script.textContent;
}
scriptParent.replaceChild(newScript, script);
});
dest.appendChild(f);
}

Don't load scripts appended with innerHTML?

I'm appending a whole HTML page to a div (to scrape). How do I stop it from requesting script, and css files ? I tried immediately removing those nodes but they still get requested.
It's for a browser addon, I'm scraping with JS
As #adeneo wrote you don't have to add the html to a page in order to scrape information from it, you can turn it into DOM tree that is disconnected from the page DOM and process it there.
In jQuery it is simple $("html text here"). Then you can scrape it using the API,
eg.
function scrape_html(html_string) {
var $dom = $(html_string);
var name = $dom.find('.name').text();
return name;
}
without jQuery:
function scrape_html(html_string) {
var container = document.createElement('div');
container.innerHTML = html_string;
var name = container.getElementsByClassName('name')[0].innerText;
return name;
}
Setting the innerHTML of a temporary HTML element that has not been added to the document, will not execute scripts, and since it does not belong to your document, the style will not be applied either.
This will give you an opportunity to strip out any unwanted elements before copying the innerHTML to your own document.
Example:
var temp = document.createElement('div');
temp.innerHTML = html; // the HTML of the 'other' page.
function removeElements(element, tagName)
{
var elements = temp.getElementsByTagName(tagName);
while(elements.length > 0)
{
elements[0].parentNode.removeChild(elements[0]);
}
}
removeElements(temp, 'script');
removeElements(temp, 'style');
removeElements(temp, 'link');
container.innerHTML = temp.innerHTML;

Javascript, Firefox replacing entire document without document.write

I have this method, which works fine, except in Firefox:
function write(template) {
document.open("text/html", "replace");
document.write(template);
document.close();
}
write("<!DOCTYPE html><head><title>MyPage</title></head><body>111</body><html>");
In firefox the entire page can no longer be refreshed and when you update the hash in the address field it forces a complete refresh of the page.
This doesn't happen in chrome.
Is there a different way to update entire document that makes Firefox play along?
Thanks!
-----------EDIT----------
I noticed there is a document.childNodes[0].remove() method to call which will remove the old document, but I am unable to add a new document node to that array.
var node = document.createElement("html");
node.innerHTML = "<body>1111</body>";
document.childNodes[0].remove();
document.childNodes[0] = node;
Does not seem to be working. Any hints?
-----------EDIT 2 ----------
function write(template) {
var node = document.createElement("html");
node.innerHTML = template;
var head = node.getElementsByTagName("head")[0];
var body = node.getElementsByTagName("body")[0];
if ( head ) {
document.documentElement.replaceChild(
head,
document.documentElement.getElementsByTagName("head")[0]
);
}
if ( body ) {
document.documentElement.replaceChild(
body,
document.documentElement.getElementsByTagName("body")[0]
);
}
}
This successfully does replace the dom, unfortunately, unlike write it doesn't revaluate the style or script tags, making it useless :(
------- EDIT 3 -------
Yields the same as EDIT 2 making it also useless:
function write(template) {
var node = document.createElement("html");
node.innerHTML = template;
document.replaceChild(node, document.childNodes[0]);
}
----- EDIT 4 -----
See my answer below
Apparently, jQuery is able to revaluate scripts when html() is used correctly, here is the write method :
function write(template) {
var node = document.createElement("html");
node.innerHTML = template;
var head = node.getElementsByTagName("head")[0];
var body = node.getElementsByTagName("body")[0];
if ( head ) {
jQuery("head").html(head.innerHTML);
}
if ( body ) {
jQuery("body").html(body.innerHTML);
}
}
write("<!DOCTYPE html><head><title>MyPage</title></head><body>111</body><html>");
:D
I would recommend you to make use of the .html() function of jQuery. (or innerHTML if you prefer to make use of pure Javascript)
If the document DOM has already been loaded you shouldn't be using document.write.
After replacing the DOM, you can manually revalute scripts and styles, by creating new elements:
function write(template) {
var node = document.createElement("html");
node.innerHTML = template;
var head = node.getElementsByTagName("head")[0];
var body = node.getElementsByTagName("body")[0];
if ( head ) {
document.documentElement.replaceChild(
head,
document.documentElement.getElementsByTagName("head")[0]
);
}
if ( body ) {
document.documentElement.replaceChild(
body,
document.documentElement.getElementsByTagName("body")[0]
);
}
var scripts = document.getElementsByTagName("script"),
styles = document.getElementsByTagName("style");
var or_scripts_length = scripts.length,
or_styles_length = styles.length;
//reproduce scripts
for(var i=0; i<or_scripts_length; i++){
new_script = document.createElement("script");
new_script.setAttribute("src", scripts[i].src);
document.head.appendChild(new_script);
if(i == or_scripts_length)
break; //in order to avoid revaluating new created scripts which results to an infinite loop
}
//do the same for styles
}

Categories

Resources