I'm working with Angular and JQuery on a site, and set the header and footer as partials in order to maximize re-use. The issue I have is that I need some javascript to be handled within the footer upon load, but that is not supported by Angular via the ng-include tag or JQuery with the $.load() method.
I have multiple 3rd party scripts that I want to run, but the one I'm working on right now is one of those Verisign scripts which returns an image, which is loaded using the <script src=""></script> pattern. I know that at least presently there is no way for Javascript to pull in external scripts, so my thought was to simply create a container div where I want the image to appear, to run the 3rd party script from the main page, and to then take that content and populate the container div via reference, conceptually looking like this:
//footer.html
<div id="siteVerify"></div>
//index.html
//call a method that takes 3rd party that i can point to siteVerify
Is it possible to point that 3rd party script to the siteVerify div without having the script run inside the div?
Conversely, if there is an alternative preferred method for html code re-use so that I can simply edit changes in one place and have them propegate throughout the site?
edit:
I put:
// If you know these dead scripts will be in a certain container, refine your jQuery selector
$(document).ready(function(){
// If you know these dead scripts will be in a certain container, refine your jQuery selector
$('.deadScriptContainer script').each(function(){
// If a script has content, we want to execute it
if($(this).html().length > 0){
// Eval() will execute any JavaScript it is passed
eval($(this).html());
}else{
// If this script has no content, let's see if it has a src
if($(this).src){
// Create a new script tag
var newScript = document.createElement("script");
newScript.type = "text/javascript";
newScript.src = $(this).src;
// Append new script to the head
document.querySelector("head").appendChild(newScript);
}
}
});
});
into a js file, and call it at the bottom of my index.html file. Meanwhile in my footer, I have something like this:
<div class='deadScriptContainer'><span id='siteseal'>
<script type="text/javascript" src="https://seal.godaddy.com/getSeal?sealID=...."></script></span>
</div>
so it should be finding the script tag within the div with the class 'deadScriptContainer', and looking up its src, which should be the https:.... I put an alert in the code and it indeed appears to be finding the script tag, but when i check to see what the src value is, it always returns 'undefined'.
I have a feeling my selectors are probably wrong, but I can't see what it is about them.
When you load in script tags using AJAX, the problem is they're brought in as placed where you want it, hence why it doesn't fire. There is an easy way to revive these dead scripts though, so you're in luck!
Once you have your data back from your AJAX request, evaluate any internal JavaScript.
For external JavaScript, what you need to do is create a new <script> tag and give it the dead script's source.
The entire process should look something like this once you've loaded in your new data:
// If you know these dead scripts will be in a certain container, refine your jQuery selector
$('deadScriptContainer script').each(function(){
// If a script has content, we want to execute it
if($(this).html().length > 0){
// Eval() will execute any JavaScript it is passed
eval($(this).html());
}else{
// If this script has no content, let's see if it has a src
if($(this).src){
// Create a new script tag
var newScript = document.createElement("script");
newScript.type = "text/javascript";
newScript.src = $(this).src;
// Append new script to the head
document.querySelector("head").appendChild(newScript);
}
}
});
Related
Either I'm not using the correct terminology or my answer isn't out there. I have to add some code to a web include javascript file in which the application applies the tags to. However, the code I'm supposed to add has one line that contains tags - which you can't put tags within tags. Is there a different way to do this? This is what I was asked to add to the file:
<script async src="https://somesite.com"></script>
(more javascript here, with no tags since the tags are added programmatically)
So, my main question is, can I add the top line in a different way, excluding the tags?
If you're trying to add script to the page dynamically (without the HTML code), you can try something like this --
var script = document.createElement('script');
script.src = "https://somesite.com";
script.async = true;
document.head.appendChild(script);
Say I have a few js-files stored externally, or I just want to load a new dependency like JQuery or Angular in my shadow-root, is there a way to load it into it?
I know for css-stylesheets you can just do:
var style = document.createElement('style');
style.setAttribute('type','text/css');
style.innerText = '#import "' + csspath + '";';
is there a similar way to do that with js?
Since just doing this:
var script = document.createElement('script');
script.type = "text/javascript";
script.src = jspath;
doesn't work :/
Also I am doing this:
var root = document.getElementById('container').createShadowRoot();
var DOM = new DOMParser().parseFromString(html.responseText,'text/html').getElementsByTagName('html')[0];
DOM.getElementsByTagName('head')[0].appendChild(script);
the "parseFromString(html.responseText,'text/html')" is because I am getting my html from an external source as well.
Here is a plunkr
http://plnkr.co/edit/YM1lXN8QEhjMd4n9u0wf?p=preview
As you figured out, Shadow DOM does not create a new scope for JavaScript, it's only concerned with DOM and CSS scoping.
There are a couple approaches to adding JavaScript to the document. The first is the classic script injection:
<script>
var script = document.createElement('script');
script.src = "//somehost.com/awesome-widget.js";
document.getElementsByTagName('head')[0].appendChild(script);
</script>
You were doing this in your previous example but you were appending the script to the wrong place. Just add it to the main document instead of the html that you loaded. I think it should work in either instance, but it makes more sense for it to follow the typical pattern of appending to the main document's head.
The other approach, if you're using HTML Imports, is to include a link tag, which has a script that points to your resource. This is often what we do with Polymer elements. We link all of our dependencies at the top, that way they get loaded before our element definition is registered.
So I kinda figured this out,
I didn't understand what a #shadow-root is.
Its not like an iFrame where it gets its own #document and has a completely different environment to the rest of the site.
You can load js into a shadow-root by calling its parent element and then its elements,
So say this is my structure:
div [id='app']
| #shadow-root
| | div [id='content']
then in order to work some magic on the '#content' tag, using jquery you can change it by doing this:
$('#app').find('#content').html('hello world')
Even if this code is ran from inside the shadow-root
I've hooked up a lazy loader in Angular. It pulls in full templates and extracts key information from that full template in order to populate a partial. This full page template has script tags which load in and then register with the existing app. All of this works fine. My problem is that I'd like to remove the only use of jQuery in this approach.
The root issue is that the JS inside of something.js doesn't execute when using $element.html(), but it does execute when using $.html(), despite the script tag being placed in the DOM in both approaches.
Working code, including lazy loader and post-bootstrap registration of lazy-loaded JS:
$http.get("/path/to/file.html").success(function(response) {
// response is a full HTML page including <doctype>
var partial = getOnlyWhatWeNeed(response);
// partial is now something like: '<script type="text/javascript" src="/path/to/something.js"></script><div ng-controller="somethingCtrl">{{something}}</div>'
// i'd like the following to not rely on full jQuery.
$("#stage").html(partial);
$("#stage").html($compile(partial)($scope)); // it is necessary to do it once before compile so that the <script> tags get dropped in and executed prior to compilation.
});
I've tried what seems like the logical translation:
$element.html($compile(partial)($scope));
and the DOM is created properly, but the JS inside of the loaded <script> tag doesn't actually execute. My research suggested this was an $sce issue, so I tried:
$element.html($compile($sce.trustAsHtml(partial)($scope));
but i get the same result. the DOM is fine, but the JS doesn't actually execute and so I get undefined controller issues.
I've tried playing with $sce.JS and $sce.RESOURCE_URL but the docs didnt elaborate much so I'm not sure I know whether or not what I'm trying is even right.
I've also tried $element[0].innerHTML but I get the same result as $element.html().
Preemptive disclaimer: I can trust the incoming HTML/JS. I know it's inadvisable. This isn't my baby and it is much more complicated than I explained so please try to stay on topic so other people in this position may not have as hard of a time as I am :)
The $http.get happens in a provider, and the $element.html happens in a directive. I consolidated them to remove noise from the problem.
Jquery will find any script tags and evaluate them (either a direct eval or appending them to the head for linked scripts) when calling html(), see this answer. I'm assuming angular's jquery lite doesn't do this. You would need to effectively replicate what jquery is doing and look for script tags in the html you are appending.
Something like this (although I haven't tested it):
$http.get("/path/to/file.html").success(function(response) {
// response is a full HTML page including <doctype>
var partial = getOnlyWhatWeNeed(response);
// partial is now something like: '<script type="text/javascript" src="/path/to/something.js"></script><div ng-controller="somethingCtrl">{{something}}</div>'
var d = document.createElement('div');
d.innerHTML = partial;
var scripts = d.getElementsByTagName('script');
for (var i = 0; i < scripts.length; i++) {
document.head.appendChild(scripts[0]);
}
$("#stage").html($compile(partial)($scope)); // it is necessary to do it once before compile so that the <script> tags get dropped in and executed prior to compilation.
});
This is far from an ideal solution as it gives you no guarantee of when things are loaded and doesn't really handle dependencies across scripts. If you can control the templates it would be simpler to remove the scripts from them and load them independently.
I'm writing a Javascript file which will be a component in a webpage. I'd like it to be simple to use - just reference the script file in your page, and it is there. To that end however there is a complication - where should the HTML go that the Javascript generates? One approach would be to require a placeholder element in the page with a fixed ID or class or something. But that's an extra requirement. It would be better if the HTML was generated at the location that the script is placed (or, at the start of body, if the script is placed in head). Also, for extra customizability, if the fixed ID was found, the HTML would be placed inside that placeholder.
So I'm wondering - how do I detect my script's location in the page? And how do I place HTML there? document.write() comes to mind, but that is documented as being pretty unreliable. Also it doesn't help if the script is in the head. Not to mention what happens if my script is loaded dynamically via some AJAX call, but I suppose that can be left as an unsupported scenario.
I am doing that with this code...
// This is for Firefox only at the moment.
var thisScriptElement = document.currentScript,
// Generic `a` element for exploiting its ability to return `pathname`.
a = document.createElement('a');
if ( ! thisScriptElement) {
// Iterate backwards, to look for our script.
var scriptElements = document.body.getElementsByTagName('script'),
i = scriptElements.length;
while (i--) {
if ( ! scriptElements[i].src) {
continue;
}
a.href = scriptElements[i].src;
if (a.pathname.replace(/^.*\//, '') == 'name-of-your-js-code.js') {
thisScriptElement = scriptElements[i];
break;
}
}
}
Then, to add your element, it's simple as...
currentScript.parentNode.insertBefore(newElement, currentScript);
I simply add a script element anywhere (and multiple times if necessary) in the body element to include it...
<script type="text/javascript" src="somewhere/name-of-your-js-code.js?"></script>
Ensure the code runs as is, not in DOM ready or window's load event.
Basically, we first check for document.currentScript, which is Firefox only but still useful (if it becomes standardised and/or other browsers implement it, it should be most reliable and fastest).
Then I create a generic a element to exploit some of its functionality, such as extracting the path portion of the href.
I then iterate backwards over the script elements (because in parse order the last script element should be the currently executing script), comparing the filename to what we know ours is called. You may be able to skip this, but I am doing this to be safe.
document.write is very reliable if used as you indicate (a default SharePoint 2010 page uses it 6 times). If placed in the head, it will write content to immediately after the body element. The trick is to build a single string of HTML and write it in one go, don't write snippets of half-formed HTML.
An alternative is to use document.getElementsByTagName('script') while the document is loading and assume the the last one is the current script element. Then you can look at the parent and if it's the head, use the load or DOM ready event to add your elements after the body. Otherwise, just add it before or after the script element as appropriate.
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'])
}