When I run my rails application and enter likeButton into the console it gives me Uncaught ReferenceError: likeButton is not defined
at :1:1
(anonymous) # VM1591:1
I tried moving the script in html to head and body. I am currently trying to use DOMContentLoaded but it seems I'm missing something. My overall goal is to change the color of the button once pressed and also keep the color after page refresh. I am using sessionStorage for this process. I just want to make sure that likeButton variable is declared after html is loaded. If its possible to done in javascript only.
//first js file
const BASE_URL = "http://localhost:3000"
const GPUS_URL = `${BASE_URL}/gpus`
const USERS_URL = `${BASE_URL}/users`
const gpuCollection = document.querySelector('#gpu-collection')
let wish = sessionStorage.getItem('wish');
class Gpu {
constructor(gpuAttributes) {
this.title = gpuAttributes.title;
this.price = gpuAttributes.price;
this.features = gpuAttributes.features;
this.link = gpuAttributes.link;
this.image = gpuAttributes.image;
this.id = gpuAttributes.id;
}
render() {
let div = document.createElement('div');
div.classList.add('card');
let h = document.createElement('h2');
let t = document.createTextNode(`${this.title} ($${this.price})`);
h.appendChild(t);
div.appendChild(h);
let h1 = document.createElement('h1');
h1.classList.add('gpu-cat');
h1.innerHTML = `${this.features}`;
div.appendChild(h1);
let button = document.createElement('button');
button.classList.add('list_btn');
button.innerHTML = '♡';
div.appendChild(button);
let a = document.createElement('a');
let img = document.createElement('img');
a.href = `${this.link}`;
a.target = '_blank';
img.src = `${this.image}`;
img.classList.add('gpu-image');
a.appendChild(img);
div.appendChild(a);
gpuCollection.appendChild(div);
}
}
//second js file
document.addEventListener("DOMContentLoaded", function (){
let likeButton;
SignUp();
logInUser();
logOutUser();
function putGpusOnDom(gpuArray){
gpuArray.forEach(gpu => {
let newGpu = new Gpu(gpu)
newGpu.render()
});
likeButton = document.querySelector("button");
}
function fetchGpus(){
fetch(GPUS_URL)
.then(res => res.json())
.then(gpus => putGpusOnDom(gpus))
}
const enableWish = () => {
console.log(likeButton)
sessionStorage.setItem('wish', 'red')
}
gpuCollection.addEventListener('click', function (){
wish = sessionStorage.getItem('wish');
if(wish !== 'red'){
enableWish();
}else{
disableWish();
}
});
})
//html file
...
<body>
<div id = "gpu-collection"></div>
<script type="text/javascript" src="src/Gpu.js"></script>
<script type="text/javascript" src="src/index.js" ></script>
</body>
</html>
As I mentioned in a comment the like button is not available on DOMContentLoaded if it is added dynamically. You need to wait until the button has been placed in the DOM
Use something like the following, I'm making some guesses here as there are some gaps in your code
document.addEventListener("DOMContentLoaded", function (){
//document.querySelector("button"); not yet available
//NOTE: The likeButton variable will ONLY be in scope INSIDE the event listener function
// You will not be able to access directly in the console.
let likeButton;
SignUp();
logInUser();
logOutUser();
function putGpusOnDom(gpuArray){
gpuArray.forEach(gpu => {
let newGpu = new Gpu(gpu)
newGpu.render()
});
//Now you have rendered the button it is available
//CAUTION: querySelector("button") will grab the first button on the page
// and ONLY the first button
likeButton = document.querySelector("button");
//Log like button to console while it is still in scope.
console.log(likeButton);
}
function fetchGpus(){
fetch(GPUS_URL)
.then(res => res.json())
.then(gpus => putGpusOnDom(gpus))
}
const enableWish = () => {
console.log(likeButton)
sessionStorage.setItem('wish', 'red')
}
})
I am trying to develop a page constructor made all with javascript. The problem comes when I dynamically add 2 scripts on the page that depends on the other. In this example I am loading the jQuery from the CDN, and in the second script I call a jQuery function to modify the text of the title, and, because the first is not already loaded, I get the error ReferenceError: $ is not defined.
What is the best way? Maybe wait to load the next script until previous one gets loaded?
One tip, I dont want to use external libraries like RequireJS (this would need to be updating the page everytime the plugin updates, and it would never be possible in this case).
EXAMPLE
Thats my JS classes:
NS.Script = function(nHasCode, nType, nSrc, nContent){
this.hasCode = nHasCode;
this.src = nSrc;
this.type = nType;
this.content = nContent;
};
NS.Page = function(){
this.id;
this.isIndex;
this.title;
this.metas = [];
this.links = [];
this.styles = [];
this.scripts = [];
this.body;
};
NS.Page.prototype.addScript = function(hasCode, type, src = null, content = null){
var aux = new NS.Script(hasCode, type, src, content);
var pageScripts = this.scripts;
pageScripts.push(aux);
};
NS.Pages = {
load: function(page){
//document.write(page.body);
document.body.innerHTML = page.body;
document.title = page.title;
page.scripts.forEach(function(pageScript) {
if(pageScript.hasCode){
document.write("<script type="+pageScript.type+">"+pageScript.content+"<\/script>");
}else{
var s = document.createElement( 'script' );
s.setAttribute( 'type', pageScript.type );
s.setAttribute( 'src', pageScript.src );
if (s.readyState){ //IE
script.onreadystatechange = function(){
if (s.readyState == "loaded" ||
s.readyState == "complete"){
s.onreadystatechange = null;
//maybe here I have to call the script load of every dependance
}
};
} else { //Others
s.onload = function(){
//callback();
//maybe here I have to call the script load of every dependance
};
}
document.getElementsByTagName("head")[0].appendChild( s );
}
});
}
};
Finally I create a page and add two scripts:
var pagina = NS.Pages.new("Prueba", "Pagina 1");
pagina.title = "Page title";
pagina.isIndex = true;
pagina.body = "<h1 id='title'>Page title with H1</h1><p>This could be a paragraph</p>";
pagina.addScript(false, 'text/javascript', 'https://code.jquery.com/jquery-2.2.3.min.js');
pagina.addScript(true, 'text/javascript', null, "$('#title').text('The new title configured with jQuery');");
NS.Pages.load(pagina);
<script type="text/javascript">
function loadJavaScriptSync(filePath)
{
var req = new XMLHttpRequest();
req.open("GET", filePath, false); // 'false': synchronous.
req.send(null);
var headElement = document.getElementsByTagName("head")[0];
var newScriptElement = document.createElement("script");
newScriptElement.type = "text/javascript";
newScriptElement.text = req.responseText;
headElement.appendChild(newScriptElement);
}
loadJavaScriptSync("file1.js");
loadJavaScriptSync("file2.js");
</script>
Code snippet taken from: http://trevweb.me.uk/javascripthtml-synchronous-and-asynchronous-loading/
Looks like a chicken and egg problem. It stems from the fact that dynamically inserted <script> tags get a default "async" property and are executed in unpredictable order. According to Mozilla, you can request them to be synced:
var s = document.createElement( 'script' );
s.setAttribute( 'type', pageScript.type );
s.setAttribute( 'src', pageScript.src );
s.async = false;
I am attempting to create script that will automate sign in process (to login). However, when I run script it says that 'could not be tapped'. Below is what I have written so far:
var target = UIATarget.localTarget();
var app = target.frontMostApp();
var appWindow = app.mainWindow();
var settingsButton = appWindow.buttons()["CloseVector"];
var settingsView = app.mainWindow().staticTexts()["Settings"]
UIALogger.logStart("Sign in test")
settingsButton.tap();
target.delay(1);
if (settingsView.isValid())
{
UIALogger.logPass("Correct View");
}
else
{
UIALogger.logFail("Wrong View");
}
I have developed chrome extension , I have added ,
chrome.browserAction.onClicked.addListener
which will start the script once clicked on , the script in turn will add a div at the bottom of the web page on the tab the browser action is clicked ,
All I have to do is , I need to add a close link which will stop the content script and close the div at the bottom ,
I have tried windows.close() , self.close() but nothing seems to work ,I would atleast want it to work in a way that 2nd time some one clicks on browser action, the script should stop.
Here is my code,
background.js
chrome.browserAction.onClicked.addListener( function() {
chrome.tabs.executeScript( { file: 'myscript.js' } );
});
myscript.js
document.body.appendChild(div);
document.addEventListener("click",
function (e) {
e.preventDefault();
var check = e.target.getAttribute("id");
var check_class = e.target.getAttribute("class");
if(check=="ospy_" || check=="ospy_id" || check=="ospy_text" || check=="ospy_el" || check=="ospy_class" || check=="ospy_name" || check=="ospy_href" || check=="ospy_src"|| check=="ospy_wrapper"|| check=="ospy_style"|| check=="ospy_rx"|| check=="ospy_con"|| check_class=="ospy_td"|| check=="ospy_main_tab"|| check_class=="ospy_tab" || check_class=="ospy_ip"|| check_class=="ospy_lab")
{
}
else{
document.getElementById('ospy_id').value = "";
document.getElementById('ospy_class').value = "";
document.getElementById('ospy_el').value = "";
document.getElementById('ospy_name').value = "";
document.getElementById('ospy_style').value = "";
document.getElementById('ospy_href').value = "";
document.getElementById('ospy_text').value = "";
document.getElementById('ospy_src').value = "";
document.getElementById('ospy_con').value = "";
document.getElementById('ospy_').value = "";
document.getElementById('ospy_rx').value = "";
var dom_id=e.target.getAttribute("id");
// var dom_id = e.target.id.toString();
var dom_name = e.target.name.toString();
var dom_class = e.target.className.toString();
// var dom_class = this.class;
var dom_html = e.target.innerHTML;
var dom_href = e.target.getAttribute("href");
var dom_text = e.target.text;
var dom_el= e.target.tagName;
var dom_src= e.target.src;
//var XPATH = e.target.innerHTML;
var rel_xpath = "";
var field ="";
var field_value = "";
field="id";
field_value = dom_id;
rel_xpath = dom_el+"[#"+field+"='"+field_value+"']";
if(dom_id == null){
field="href";
field_value= dom_href;
//var rel_xpath = dom_el+"[contains(text(), '"+dom_text+"')]";
rel_xpath = dom_el+"[#"+field+"='"+field_value+"']";
if(dom_href==null || dom_href=="#")
{
field="src";
field_value= dom_src;
rel_xpath = dom_el+"[#"+field+"='"+field_value+"']";
//rel_xpath = "nope nothing";
if(dom_src==null)
{
var rel_xpath = dom_el+"[contains(text(), '"+dom_text+"')]";
if(dom_text=="")
{
field="class";
field_value= dom_class;
rel_xpath = dom_el+"[#"+field+"='"+field_value+"']";
}
}
}
}
var con_xpath = "";
var con_xpath = dom_el+"[contains(text(), '"+dom_text+"')]";
if(dom_text==null)
{
con_xpath = "NA";
}
var css ="color: ";
css += getComputedStyle(e.target).color;
css +="\nWidth: ";
css += getComputedStyle(e.target).width;
css +="\nHeight: ";
css += getComputedStyle(e.target).height;
css +="\nbg: ";
css += getComputedStyle(e.target).background;
css +="\nfont: ";
css += getComputedStyle(e.target).font;
css +="\nvertical-align: ";
css += getComputedStyle(e.target).verticalalign;
css +="\nmargin: ";
css += getComputedStyle(e.target).margin;
var node = getXPath(e.target.parentNode);
document.getElementById('ospy_id').value = dom_id;
document.getElementById('ospy_class').value = dom_class;
document.getElementById('ospy_el').value = dom_el;
document.getElementById('ospy_name').value = dom_name;
document.getElementById('ospy_style').value = css;
document.getElementById('ospy_href').value = dom_href;
document.getElementById('ospy_text').value = dom_text;
document.getElementById('ospy_src').value = dom_src;
document.getElementById('ospy_').value = node;
document.getElementById('ospy_rx').value =rel_xpath;
document.getElementById('ospy_con').value =con_xpath;
}},
false);
window.close() is for closing a window, so no wonder it does not work.
"Unloading" a content-script is not possible, but if you want to remove an element (e.g. your div) from the DOM, just do:
elem.parentNode.removeChild(elem);
(Whether you bind this behaviour to a link/button in your <div> or have the browser-action, trigger an event in the background-page that sends a message to the corresponding content-script which in turn removes the element is up to you. (But clearly the former is much more straight-forward and efficient.)
If you, also, want your script to stop performing some other operation (e.g. handling click events) you could (among other things) set a flag variable to false (when removing the <div>) and then check that flag before proceeding with the operation (e.g. handling the event):
var enabled = true;
document.addEventListener('click', function (evt) {
if (!enabled) {
/* Do nothing */
return;
}
/* I am 'enabled' - I'll handle this one */
evt.preventDefault();
...
/* In order to "disable" the content-script: */
div.parentNode.removeChild(div);
enabled = false;
Notes:
If you plan on re-enabling the content-script upon browser-action button click, it is advisable to implement a little mechanism, where the background-page sends a message to the content-script asking it to re-enable itself. If the content-script is indeed injected but disabled, it should respond back (to confirm it got the message) and re-enable itself. If there is no response (meaning this is the first time the user clicks the button on this page, the background-page injects the content script.
If it is likely for the content script to be enabled-disabled multiple times in a web-pages life-cycle, it would be more efficient to "hide" the <div> instead of removing it (e.g.: div.style.display = 'none';).
If you only need to disable event handler, instead of using the enabled flag, it is probably more efficient to keep a reference to the listener you want to disable and call removeEventListener().
E.g.:
function clickListener(evt) {
evt.preventDefault();
...
}
document.addEventListener('click', clickListener);
/* In order to "disable" the content-script: */
div.parentNode.removeChild(div);
document.removeEventListener('click', clickListener);
I want to dynamically include a script tag in a webpage however I have no control of it's src so src="source.js" may look like this.
document.write('<script type="text/javascript">')
document.write('alert("hello world")')
document.write('</script>')
document.write('<p>goodbye world</p>')
Now ordinarily putting
<script type="text/javascript" src="source.js"></script>
In the head works fine but is there any other way I can add source.js dynamically using something like innerHTML?
jsfiddle of what i've tried
var my_awesome_script = document.createElement('script');
my_awesome_script.setAttribute('src','http://example.com/site.js');
document.head.appendChild(my_awesome_script);
You can use the document.createElement() function like this:
function addScript( src ) {
var s = document.createElement( 'script' );
s.setAttribute( 'src', src );
document.body.appendChild( s );
}
There is the onload function, that could be called when the script has loaded successfully:
function addScript( src, callback ) {
var s = document.createElement( 'script' );
s.setAttribute( 'src', src );
s.onload=callback;
document.body.appendChild( s );
}
It's almost a decade later and nobody bothers to write the Promise version, so here is mine (based on this awnser):
function addScript(src) {
return new Promise((resolve, reject) => {
const s = document.createElement('script');
s.setAttribute('src', src);
s.addEventListener('load', resolve);
s.addEventListener('error', reject);
document.body.appendChild(s);
});
}
Usage
try {
await addScript('https://api.stackexchange.com/js/2.0/all.js');
// do something after it was loaded
} catch (e) {
console.log(e);
}
a nice little script I wrote to load multiple scripts:
function scriptLoader(scripts, callback) {
var count = scripts.length;
function urlCallback(url) {
return function () {
console.log(url + ' was loaded (' + --count + ' more scripts remaining).');
if (count < 1) {
callback();
}
};
}
function loadScript(url) {
var s = document.createElement('script');
s.setAttribute('src', url);
s.onload = urlCallback(url);
document.head.appendChild(s);
}
for (var script of scripts) {
loadScript(script);
}
};
usage:
scriptLoader(['a.js','b.js'], function() {
// use code from a.js or b.js
});
When scripts are loaded asynchronously they cannot call document.write. The calls will simply be ignored and a warning will be written to the console.
You can use the following code to load the script dynamically:
var scriptElm = document.createElement('script');
scriptElm.src = 'source.js';
document.body.appendChild(scriptElm);
This approach works well only when your source belongs to a separate file.
But if you have source code as inline functions which you want to load dynamically and want to add other attributes to the script tag, e.g. class, type, etc., then the following snippet would help you:
var scriptElm = document.createElement('script');
scriptElm.setAttribute('class', 'class-name');
var inlineCode = document.createTextNode('alert("hello world")');
scriptElm.appendChild(inlineCode);
document.body.appendChild(scriptElm);
You can try following code snippet.
function addScript(attribute, text, callback) {
var s = document.createElement('script');
for (var attr in attribute) {
s.setAttribute(attr, attribute[attr] ? attribute[attr] : null)
}
s.innerHTML = text;
s.onload = callback;
document.body.appendChild(s);
}
addScript({
src: 'https://www.google.com',
type: 'text/javascript',
async: null
}, '<div>innerHTML</div>', function(){});
A one-liner (no essential difference to the answers above though):
document.body.appendChild(document.createElement('script')).src = 'source.js';
This Is Work For Me.
You Can Check It.
var script_tag = document.createElement('script');
script_tag.setAttribute('src','https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js');
document.head.appendChild(script_tag);
window.onload = function() {
if (window.jQuery) {
// jQuery is loaded
alert("ADD SCRIPT TAG ON HEAD!");
} else {
// jQuery is not loaded
alert("DOESN'T ADD SCRIPT TAG ON HEAD");
}
}
Loads scripts that depends on one another with the right order.
Based on Satyam Pathak response, but fixed the onload.
It was triggered before the script actually loaded.
const scripts = ['https://www.gstatic.com/firebasejs/6.2.0/firebase-storage.js', 'https://www.gstatic.com/firebasejs/6.2.0/firebase-firestore.js', 'https://www.gstatic.com/firebasejs/6.2.0/firebase-app.js']
let count = 0
const recursivelyAddScript = (script, cb) => {
const el = document.createElement('script')
el.src = script
if(count < scripts.length) {
count ++
el.onload = () => recursivelyAddScript(scripts[count])
document.body.appendChild(el)
} else {
console.log('All script loaded')
return
}
}
recursivelyAddScript(scripts[count])
Well, there are multiple ways you can include dynamic javascript,
I use this one for many of the projects.
var script = document.createElement("script")
script.type = "text/javascript";
//Chrome,Firefox, Opera, Safari 3+
script.onload = function(){
console.log("Script is loaded");
};
script.src = "file1.js";
document.getElementsByTagName("head")[0].appendChild(script);
You can call create a universal function which can help you to load as many javascript files as needed. There is a full tutorial about this here.
Inserting Dynamic Javascript the right way
No one mentioned it, but you can also stick the actual source code into a script tag by making a URL out of it using URL and Blob:
const jsCode = `
// JS code in here. Maybe you extracted it from some HTML string.
`
const url = URL.createObjectURL(new Blob([jsCode]))
const script = document.createElement('script')
script.src = url
URL.revokeObjectURL(url) // dispose of it when done
as for the jsCode, you may have gotten it from some HTML.
Here's a more full example of how you'd handle any number of scripts in an HTML source:
main()
async function main() {
const scriptTagOpen = /<script\b[^>]*>/g
const scriptTagClose = /<\/script\b[^>]*>/g
const scriptTagRegex = /<script\b[^>]*>[\s\S]*?<\/script\b[^>]*>/g
const response = await fetch('path/to/some.html')
const html = await response.text()
someElement.innerHTML = html
// We need to get the script tags and manually add them to DOM
// because otherwise innerHTML will not execute them.
const codes =
html
.match(scriptTagRegex)
?.map(code => code.replace(scriptTagOpen, '').replace(scriptTagClose, ''))
.map(code => URL.createObjectURL(new Blob([code]))) || []
for (const code of codes) {
const script = document.createElement('script')
script.src = code
someElement.append(script)
URL.revokeObjectURL(code)
}
}
the only way to do this is to replace document.write with your own function which will append elements to the bottom of your page. It is pretty straight forward with jQuery:
document.write = function(htmlToWrite) {
$(htmlToWrite).appendTo('body');
}
If you have html coming to document.write in chunks like the question example you'll need to buffer the htmlToWrite segments. Maybe something like this:
document.write = (function() {
var buffer = "";
var timer;
return function(htmlPieceToWrite) {
buffer += htmlPieceToWrite;
clearTimeout(timer);
timer = setTimeout(function() {
$(buffer).appendTo('body');
buffer = "";
}, 0)
}
})()
I tried it by recursively appending each script
Note If your scripts are dependent one after other, then position will need to be in sync.
Major Dependency should be in last in array so that initial scripts can use it
const scripts = ['https://www.gstatic.com/firebasejs/6.2.0/firebase-storage.js', 'https://www.gstatic.com/firebasejs/6.2.0/firebase-firestore.js', 'https://www.gstatic.com/firebasejs/6.2.0/firebase-app.js']
let count = 0
const recursivelyAddScript = (script, cb) => {
const el = document.createElement('script')
el.src = script
if(count < scripts.length) {
count ++
el.onload = recursivelyAddScript(scripts[count])
document.body.appendChild(el)
} else {
console.log('All script loaded')
return
}
}
recursivelyAddScript(scripts[count])
Here is a minified snippet, same code as Google Analytics and Facebook Pixel uses:
!function(e,s,t){(t=e.createElement(s)).async=!0,t.src="https://example.com/foo.js",(e=e.getElementsByTagName(s)[0]).parentNode.insertBefore(t,e)}(document,"script");
Replace https://example.com/foo.js with your script path.
window.addEventListener("load", init);
const loadScript = async (url) => {
const response = await fetch(url);
const script = await response.text();
eval(script);
}
function init() {
const wistiaVideo = document.querySelector(".wistia_embed");
if ("IntersectionObserver" in window && "IntersectionObserverEntry" in window && "intersectionRatio" in window.IntersectionObserverEntry.prototype) {
let lazyVideoObserver = new IntersectionObserver(function (entries, observer) {
entries.forEach(function (entry) {
if (entry.isIntersecting) {
setTimeout(() => loadScript("//fast.wistia.com/assets/external/E-v1.js"), 1000);
lazyVideoObserver.unobserve(entry.target);
console.log("E-v1.js script loaded from fast.wistia.com");
}
});
});
lazyVideoObserver.observe(wistiaVideo);
}
}
<div style="height: 150vh; background-color: #f7f7f7;"></div>
<h1>Wistia Video!</h1>
<div class="wistia_embed wistia_async_29b0fbf547" style="width:640px;height:360px;"> </div>
<h1>Video Ended!</h1>