Loading/Appending JS script asynchronously - javascript

I'm using Laravel to implement templates based on a main page. Different pages have different JS scripts, so I created a template to import JS scripts:
<!-- jQuery 2.1.3 -->
<script src="{{ URL::asset('plugins/jQuery/jQuery-2.1.4.min.js') }}"></script>
<!-- Bootstrap 3.3.2 JS -->
<script src="{{ URL::asset('js/bootstrap.min.js') }}" type="text/javascript"></script>
<!-- Ajax Page Loading -->
<script>
function ajax(url) {
$('.main-content').fadeOut(100); //hide the page
$('.spinner').show(); // show a spinner
$.ajax(url, {
async: true,
success: function(data) {
$('#header').html(data[0]); //append received header to header
$('#content').hide().html(data[1]).fadeIn(500); //show the page again
$('body').append(data[2]); //append scripts to body
$('.spinner').hide();
},
});
}
</script>
#yield('extra-scripts') <--- /* HERE is where the scripts will be */
I'm also using AJAX to load only the content without refreshing the page.
The function ajax will be used to load any url into the div "content". However, I also need to load the scripts so the page works properly.
Data is an array with three fields:
0 is Header html
1 is Content html
2 are the dynamically added scripts
The problem is whenever I'm loading the page, I get this error:
Synchronous XMLHttpRequest on the main thread is deprecated because of
its detrimental effects to the end user's experience. For more help,
check https://xhr.spec.whatwg.org/.
I don't want script loading to affect user experience. Has to be async.
What I already tried:
jQuery.getScript
This solution requires me to move all the scripts to a separate JS file. This would probably solve it, but I would rather keep them all in the respective page.
AjaxPreFilter
$.ajaxPrefilter with options.async = true makes the scripts load after the page thus making some properties undefined and not working.

This warning will continue to happen as long as you inject the script to the body of the document.
I'd recommend using $.getScript as this will load the script correctly. I do not really understand why you'd want all javascripts to be in the same page from the first place.
You probably want to have them in a separate file anyway for easier maintenance down the road and separation of concerns.
You can also use vanilla javascript for that:
var head = document.getElementsByTagName('head')[0];
var script = document.createElement('script');
script.setAttribute('src','your_script.js');
head.appendChild(script);
If you insist on doing it this way - try injecting it directly to the head of the document and not the body and see if that helps.
On a more general note - seems like you're building a single page application (SPA) - have you looked into JS frameworks like Angular, Backbone, etc? they will handle all the heavy lifting for you and will help you scale your application better. This smells a little bit like trying to re-invent the wheel and could be a great thing as an educational process but might not be such as good idea in the long run.
Hope this helps.

What you are doing right now is not a best practice. If you want to load pages using Ajax and dynamically call the Js files, I would recommend you to use pjax.
Take a look at here: https://github.com/defunkt/jquery-pjax
Since you are using laravel, you can easily implement this it in pjax.
Here is a tutorial: https://laracasts.com/lessons/faster-page-loads-with-pjax

Related

Adding WFFM-form - Jquery issue

I'm using Sitecore 8 Update 2 to make a page where people can leave some personal information. If the user is logged in the information is filled in for them.
( Standard mvc 5 form )
I added a WFFM ( 8.0 rev 141217 ) login form ( if succesfull the page is reloaded and all the fields get filled in) but when this code is included it messes up Jquery.
#Html.Sitecore().Placeholder("form")
I can still see that the jquery file is loaded in the browser but the "Jquery" variable is now undefined. ( and i have a bunch of Jquery running on that page )
What can i do to prevent this from happening or how can i find out what causes this ?
EDIT:
I found the code where it all goes bad.
After this function is finished the Jquery variable is undefined. But this code is added as part of the WFFM as far as i can tell. ( inline with the html elements )
<script type="text/javascript">
if (typeof ($scw) === "undefined") {
window.$scw = jQuery.noConflict(true);
}
$scw(document).ready(function () {
$scw("#LVn73Spq0EGYvPQZ7-bA5Q").eventTracking({ pageId: "{55D3F49D-6EDA-43FB-81F1-191D34F896D4}", rules: "{"*":"{844BBD40-91F6-42CE-8823-5EA4D089ECA2}","regex":"{F3D7B20C-675C-4707-84CC-5E5B4481B0EE}","length":"{F3D7B20C-675C-4707-84CC-5E5B4481B0EE}","required":"{7E86B2F5-ACEC-4C60-8922-4EB5AE5D9874}"}" });
});
</script>
You need to include the WFFM script bundles in your forms. You can do this by registering the scripts in /Views/Form/Index.cshtml
#Scripts.Render("~/bundles/wffm/jquery")
#Scripts.Render("~/bundles/wffm/scripts")
#Styles.Render("~/content/wffm/jquery")
#Styles.Render("~/content/wffm/themes")
You only need to include the first two, the others adds default styling using jQuery UI themes, which you can then override (or not include them and style them yourself).
The reason for the js error is jQuery.noConflict(true) resets the jQuery name from scope. The WFFM noConflict call removes the last loaded jQuery library, which in this case is your manually loaded script earlier in the page and since it is the only jQuery library it has nothing to fall back to.
If necessary, you can free up the jQuery name as well by passing true
as an argument to the method. This is rarely necessary, and if you
must do this (for example, if you need to use multiple versions of the
jQuery library on the same page)
See jQuery.noConflict() and this blog post for more details about loading two versions of jQuery
If you are sure the version if jQuery you loaded is compatible with the WFFM loaded version then you could simply set window.$scw = jQuery; but you will still need to load the scripts bundle since it includes form specific functionality.
I had the exact same error occurring on a site. The error occurred due to the fact that I had included all the wffm jquery references at the bottom of the <body> element. After moving the wffm jquery references into the <head> element and reloaded the page I had no errors in my console.
Regarding the order of loading JavaScript files while bundling:
Preferably your project makes use of properly closured js files, (scoped usage of jQuery or $). If that's the case, you can simply load the WFFM scripts as the last library, even after your own custom application code.
That way these scripts will ommit the global jQuery variable while your closures keep reference to jQuery locally. Hence this is a great example why using an IIFE is encouraged.
(function($){
// $ = reference to jQuery remains intact
}(window.jQuery))
I can now savely load my bundle from the head with the async property enabled (eliminates render-blocking JavaScript to increase page loading speed).
<head>
...
#Scripts.RenderFormat("<script src='{0}' async='async'></script>", Sitecore.Context.PageMode.IsPageEditor
? "/scripts/sdw.editor.bundle.js"
: "/scripts/sdw.web.bundle.js");
...
</head>
A bundle example in order (In bold the WFFM requirements for the Experience Editor):
jQuery (bower updated version inside Experience Editor works usually fine)
jQuery-UI (WFFM dependency)
Bootstrap
jQuery validation (WFFM dependency)
jQuery unobtrusive validation (WFFM dependency)
My custom JS (still applicable for the editor?)
WFFM Scripts (sitecore modules > Web > Web Forms for Marketers > scripts > mvc > Fields > *.js)
On the other hand, jQuery dependant code from inline scripts or external js files, loaded before the closing </body>, don't know about jQuery anymore once the WFFM scripts are loaded. Either split them up or define $scw before loading WFFM scripts.
An example of bundling while mixing inline scripts:
<head>
...
#Scripts.Render("~bundles/libraries") // jQuery, jQuery-UI, Bootstrap, jQuery validation, ...
<script>window.$scw = jQuery.noConflict(false);</script>
#Scripts.Render("~bundles/wffmJs")
</head>
<body>
...
<script type="text/javascript">
if (typeof ($scw) === "undefined") {
window.$scw = jQuery.noConflict(true);
}
$scw(document).ready(function () {
$scw("##Model.ID.ToShortGuid()").eventTracking({ pageId: "#Model.PageId", rules: "#TrackingValidationErrorsProvider.Current.GetValidationRulesMap()" });
});
</script>
...
#Scripts.Render("~bundles/myCustomJs")
</body>
Conclusion:
Avoid synchronous loading from the head tag since that will block rendering and result in slower page loading.
Avoid inline script tags because they force you to load libraries from the head tag synchronously.
Define $scw = jQuery to bypass jQuery.noConflict(true) if needed.
Preferably one bundle asynchronously from the head tag.
Use closure to keep libraries referenced.

Knockout JS plays nicely with Ratchet and push.js until I add a data-transition

I am using the Ratchet.js/push.js library to create the UI for a mobile web app. In this library, links are handled by "pushing" the to-be-loaded file into the ".content" DOM element rather than loading the entire page. However, push.js does not load any scripts it finds when loading a page - which disables my Knockout.js code.
I found a solution here on StackOverflow that works pretty well - just add an event listener for the push event. I modified it so that it can load any script across multiple pages and so it works with external script files:
window.addEventListener('push', function () {
var scriptsList = document.querySelectorAll('script.js-custom'); // Add a "js-custom" class to your script tag
for (var i = 0; i < scriptsList.length; ++i) {
// Handle scripts in separate files by assigning the script file name to its id.
// We save it in a variable because the ".done" callback is asynchronous.
scriptName = scriptsList[i].id; // IMPORTANT: Only one loadable script per page!
$.getScript("/path info here/" + scriptName)
.done(function (script, textStatus) {
eval(script);
})
... error handling ...
}
});
In the target HTML page, scripts are given class and id tags so they work with the above:
<script src="Challenge.js" class="js-custom" id="challenge.js"></script>
Note, too, that Knockout bindings have to occur to a specific named DOM element so that knockout doesn't get confused:
ko.cleanNode($("#ChallengePage")[0]);
ko.applyBindings(challengeFn, $("#ChallengePage")[0]);
We use cleanNode to avoid the "already bound" errors.
OK! So all this works great and I hope that someone who is struggling with this finds it useful.
HOWEVER, when the link is given a transition:
<a href="challenge.html" data-transition="slide-in">....
Then it breaks with a "Cannot read property 'nodeType' of undefined. I had thought that maybe it was just a problem of waiting for the transition to finish, but even if I replace the eval of the script with:
scriptContents = script;
setTimeout(function () { eval(scriptContents); }, 1000);
it doesn't help.
Any advice or assistance would be greatly appreciated! I don't really need to "push" pages if I don't get to use the transitions so I am hoping that someone will have the last key to making this all work!
UPDATE: The error was occurring because the "document.querySelectorAll" call when using a transition uses the current document rather than the document being pushed. Also, using "webkitTransitionEnd" as my event works as well but this doesn't fix the document issue. Thus, I can make this work, but only for a single transition - now I don't have a way of getting the document being loaded. Ideally, a solution that works whether a links uses a transition or not is what I am looking for.
The combination of Ratchet and Knockout will likely be popular in the coming months so I hope that others find this solution.
To combine the Ratchet.js and Knockout.js libraries requires only that you handle the fact that Ratchet.js (via Push.js) will attempt to manage your page transitions. During a transition, the JavaScript on your target page - including Knockout - will not be run unless you specifically make this happen. That is what this solution does: it makes it possible to load and run your Knockout JavaScript code even though Ratchet is managing page transitions.
In my solution, we always place JavaScript in a separate file and implement Content Security Policy that forbids any JS code from running on the page. It is simply good security hygiene and helps reduce the attack surface for XSS attacks. So the solution below 1) assumes that the JS is in a separate file and 2) assumes that the HTML and JS files have the exact same name and path - except for the extensions (sort of like treating the .js file like an ASP.NET code-behind for the HTML file).
On your "root" page - the one that starts all of your interactions with other pages on your mobile web app, place the following function. It will load the appropriate .js file whenever the corresponding .html file is loaded by Ratchet:
window.addEventListener('push', function (params) {
var targetPage = params.target.document.baseURI.replace(".html", ".js");
$.getScript(targetPage)
.done(function (script, textStatus) {
eval(script);
})
.fail(function (jqxhr, settings, exception) {
alert("Error loading script: " + exception);
});
});
Note that you will have to apply your Knockout bindings to a named and unique div in your HTML page (generally a div that lives directly underneath the Ratchet .content div). This is just because each page load has to apply its Knockout bindings to just the HTML being loaded.
ko.cleanNode($("#DivPageName")[0]);
ko.applyBindings(KnockoutFn, $("#DivPageName")[0]);
UPDATE: I have found that this solution gets "confused" at times as pages are pushed and popped from the history stack. I have decided not to use it although it seems like it is about 97% there. If anyone has any improvements that would make this completely reliable, I am all ears!

YepNope - Waiting until dependencies loaded

In a ASP.NET Masterpage I am using YepNope to unconditionally and asynchronously load jQuery (from the Google CDN, with local fallback) and some scripts which are used on all pages in the site. In the MasterPage I have created a ContentPlaceHolder before the closing body tag (and below the YepNope script that loads those used on all pages) which is used for scripts used on individual page. As jQuery should be available on every page in the site it should not be loaded individually on those pages where there are specific scripts that use it.
The problem I have is that I can't use the callback or complete functions in the yepnope script where jQuery is loaded, as this is on the MasterPage and these are individual page scripts which are only used or added on that page, yet I need to be able to delay the execution of the individual page scripts until yepnope (which appears above the page scripts) has finished loading any dependencies (such as jQuery) used in them.
I can think of two options-
1- Make the script used on the page an external file and load that using the syntax -
yepnope('/url/to/your/script.js');
or
yepnope({ load: '/url/to/your/script.js' });
I'm not sure I like this idea as it introduces an extra HTTP request for a few lines of javascript which isn't going to be used on any other page.
2- Load jQuery again in another yepnope test object block, with the complete function wrapping up the page scripts (calling complete without a test seems to execute the function immediately, before the previous scripts are loaded) and relying on the following-
I am requesting a file twice and it's only loading once? By popular
demand, in yepnope 1.5+ we added the feature that scripts that have
already been requested not be re-executed when they are requested a
second time. This can be helpful when you are dealing with less
complex serverside templating system and all you really care about is
that all of your dependencies are available.
In the page I could presumably load the same version of jQuery from the Google CDN, which based on the above would not actually be loaded twice, and then load the page scripts in an anonymous function called from the complete function of the yepnope test object.
On the plus side this would mean that the page is no longer dependent on jQuery being loaded from the MasterPage, but a negative would be that (even assuming YepNope does not load the script twice now) we would be loading multiple versions of jQuery should the version in the MasterPage be changed without the same happening in the page in the future. From a maintenance point of view I don't feel this is a good idea, especially on the assumption (which I feel you should always make) that another developer would be the one making the changes.
It also does not seem especially elegant.
On balance I will almost certainly use the first option but I would like to know if there is a way to delay or defer scripts on a page until asynchronous loading is completed, and this cannot be done as part of the YepNope test object loading the resources.
How do other developers approach this problem?
I have come up with this as a solution I rather like.
In the MasterPage YepNope test object add the code-
complete: function() {
if (window.pageFunctions !== null && typeof (window.pageFunctions) === "function") {
window.pageFunctions();
}
}
If I want to add any JavaScript code or functions that rely on the dependencies loaded in the MasterPage I just wrap them in a function named "pageFunctions" like so-
<script type="text/javascript">
function pageFunctions() {
$(document).ready(function () {
...
});
}
</script>
I'm still interested in other (possibly better) solutions so I'm going to leave the question open for a couple of days.
I'd also appreciate comments on this as a solution.

SignalR $.connection is undefined

I admit it, I don't know what I'm doing.
I'm attempting to learn how to use SignalR and I'm following various samples online almost verbatim and I can't get pasted $.connection being undefined. I'm working in MVC 4.0 and trying to use either nuget-downloaded signalR js files or those from sample projects. These are my script references.
#Scripts.Render("~/Scripts/jquery-1.7.2.min.js")
#Scripts.Render("~/Scripts/knockout-2.0.0.js")
#Scripts.Render("~/Scripts/jquery.signalR.js")
#Scripts.Render("~/signalr/hubs")
The scripts seem to load - I've even put alerts at the start of them which fire, but $.connection is always undefined.
In a bizarre turn of events, I have a different project where I'm trying to use a different jQuery library and it uses $. and that object is ALSO always undefined.
I'm just looking for any possible explanation of what I might be doing wrong. Thanks very much in advance.
I got the same problem today.
Your answer pointed me in the right direction, because I had the EXACT same setup for script loading.
I had all my script tags on the top of the page (head) in the following order.
JQuery
SignalR
/signalr/hubs
my-script.js
And of course, the #Layout.cshtml was so thoughtful to add #Scripts.Render("~/bundles/jquery") at the bottom of the <body> tag.
Here's what's happening.
In this configuration, when the page loads, it loads up all the scripts in the head tag, in the order they're specified.
so it loads JQuery, then SignalR, which setup $.connection then the auto-generated hubs script which sets up the $.connection.hubName,then the site's custom script.
At this point your custom code runs. BUT your code at some point, waits for the body.onload or document.onReady before accessing the $.connection
By the time this event fires, the script bundle which the Layout template added for you at the end of body tag also downloads and executes. This bundle has the jquery library again.... which resets everything SignalR setup on $ and now your $.connection is not defined anymore.
How to Solve It
Easy fix, make sure you don't load JQuery twice, once before SignalR and once after SignalR. I moved all my scripts to load after the bundle and it fixed the issue. Best practice here is to use the 'scripts' section provided by the template.
#section scripts{
#*/* load your scripts here. You can exclude jQuery,
coz it's already in the template */ *#
}
So this is not a SignalR error at all...
That's 2 hours of my life... I'll never get back ...
Hope This Helps.
I solved the problem. I know what I did but I don't know why it worked for sure.
I was referencing the main jquery library (version 1.7.2) at the top of the _Layout.cshtml view, but was not referencing any other jquery scripts. I noticed that by default in the layout view it was loading
#Scripts.Render("~/bundles/jquery")
at the bottom of the view and that a scripts section was located below that.
#RenderSection("scripts", required:false)
I further changed my script references as shown and put my script references shown in the question above inside a
#section scripts
{
#Scripts.Render("~/Scripts/jquery.signalR-0.5.1.js")
#Scripts.Render("~/Scripts/jquery.color.js")
#Scripts.Render("~/signalr/hubs")
#Scripts.Render("~/Scripts/blasht.signalr.js")
}
wrapper on my view so that they would be included after the bundles were loaded. I assume that I was not loading all the necessary jquery because now it works.
Now if I can figure out how this applies to my other projects.
The reason this works is because of the order in which the javascript files are loaded. If you try and load jquery.signalR-0.5.1.js before loading jquery, then you will face the $.connection undefined because for this to register the jquery should be loaded first. In the _Layout.cshtml template the scripts load in the order
#Scripts.Render("~/bundles/jquery")
#RenderSection("scripts", required: false)
So inside any of your views you can load signalr under the script section and this should work fine.
I have had same issue.
for me I have included jquery 2 times in page and later in layout.
Removed one of them fixed the issue.
Hope it helps
This happened to me because I dynamically loaded the ~/signalr/hubs script before loading the jquery.signalR-1.1.4.js script.
When dynamically loading, no error was present, however if I took the output of ~/signalr/hubs and pasted it into my chrome dev tools, I received an error message VM1047:18 Uncaught Error: SignalR: SignalR is not loaded. Please ensure jquery.signalR-x.js is referenced before ~/signalr/hubs.
at <anonymous>:18:15
at <anonymous>:300:2

What is the most ideal way of loading javascript files?

What is the most ideal way of loading javascript files? Also, I want to make sure that order of the javascript files should be maintained. If I have
<script src="javascript1.js">
<script src="javascript2.js">
on my page, then javascript1.js should load before javascript2.js
Thanks.
EDIT: Thank you for your answers, but mine question is not only related with the order of js files. I want to load js files as quickly as possible without using any 3rd party js library. The solution which is similar can be found at www.nczonline.net/blog/2009/07/28/the-best-way-to-load-external-javascript/, but using this does not guarantee the order of the files for me, atleast.
There is no single "best" way of loading Javascript files. Different ways work best in different scenarios.
The normal way of loading Javascript files is to put the script tags in the head tag.
You can put some script tags inside the body tag instead, to make them load later. One common reason for this is to make the content of the page display without having to wait for the script to load.
The scripts are executed in the way that the tags are placed in the code. The execution of the code below a script tag waits for the Javascript to be executed first.
In your question you say that you want one script to load before the other, which can't be guaranteed by just using script tags in the code. Then you would have to generate the second script tag in the first Javascript and use document.write to put it in the page. To make the scripts execute in that order, you can just use your script tags the way that you do, and the order is guaranteed.
Note: You should specify the type attribute in the script tags, so that the tags validate without errors. You need to include the closing tag for the script tags.
<script type="text/javascript" src="javascript1.js"></script>
<script type="text/javascript" src="javascript2.js"></script>
As others have said, the scripts are loaded in order of placement on the page (unless they are wrapped in javascript to be loaded in later)
Putting the script tags at the bottom of the page can assist with the loading process for both old and new browsers. Although some scripts might (like modenizer) need to be loaded earlier on in the process. A good example can be seen at http://html5boilerplate.com/ on the index code sample.
Edit:
Following your edit, there is this info which can help
<script type="text/javascript">
document.writeln("<script type='text/javascript' src='Script1.js'><" + "/script>");
document.writeln("<script type='text/javascript' src='Script2.js'><" + "/script>");
</script>
The full documentation on this can be read here (including crevets of other methods) http://blogs.msdn.com/b/kristoffer/archive/2006/12/22/loading-javascript-files-in-parallel.aspx
HTML is a top down procedural language so anything that is posted first gets executed first. Hence the order which you wrote is correct.
Your web browser will execute javascript files in the order they are declared, so in your example:
<script src="javascript1.js">
<script src="javascript2.js">
javascript1.js will be executed before javascript2.js.
As for the most ideal way, this is all very subjective. I prefer progressive enhancement when using javascript so declare my javascript as the last element on a page, since it is not required for the site to function, any user can see the content and use the site even while the javascript is downloading.
I also prefer bundling all my scripts together, in a minified form, so the browser only has to make one request to get my javascript.
There is a school of thought that using parallel loading is good. This means the scripts are loaded like the GA snippet provided by google by using JS. A good way of doing this is to use modernizr. This script enables you to load the scripts when they are needed. You would need to include the modernizr script in the traditional way and then write some JS to load the other script when required.
The Best Answer Can Be Found Here:Here:http://www.html5rocks.com/en/tutorials/speed/script-loading/
Ideally do this if you need to load them in some particular order (In case of dynamically added scripts):
`
['//other-domain.com/1.js',
'2.js']
.forEach(function(src) {
var script = document.createElement('script');
script.src = src;
script.async = false;
document.head.appendChild(script);
});
`
And this for no order:
`
['//other-domain.com/1.js',
'2.js'
].forEach(function(src) {
var script = document.createElement('script');
script.src = src;
document.head.appendChild(script);
});
`
But if you just need static scripts then just ado this at the end of your body as suggested by many others:
`<script src="//other-domain.com/1.js"></script>
<script src="2.js"></script>`

Categories