I'm looking for best practices for using javascript/jQuery snippets in an asp.net project. I know that it is best to put all the scripts in a separate file rather than inline. That's good. It is easy to move these script functions to a common file (may be a couple of different ones to even out the performance of loading a single large file for small functions).
But there is some jQuery stuff that needs to happen on document.Ready on each page. How will I move this to a common .js file? I would like to avoid one script per page as it would be just too many.
For example, say Page1 has a need to manipulate a few radio buttons on load and has the following script inline. (just for illustration)
<script>
$(document).ready(function() {
//check checkboxes
if(true)
call function1();
});
</script>
Same with Page2 but for some other condition, calling different function function2.
I can move the function1 and function2 to a common .js file but how about the document ready sections. Should that stay inline? I assume so because otherwise I'm not sure how the common.js will differentiate between document.ready for different pages.
Then does it defeat the purpose of not having inline javascript? If anyone can throw some light into this, it is much appreciated.
I did some research, but probably due to incorrect keywords, so far I haven't been able to find any good information along the same lines. Unobtrusive JavaScript seems promising in the comments below.
You should specify what behaviors should exist within the HTML using data-* attributes.
You can then use a single universal piece of Javascript code to read these attributes and apply behaviors.
For example:
<div data-fancy-trick="trick-3">...</div>
In the JS file, you can write something like
$('[data-fancy-trick]'.each(function() {
var trickName = $(this).data('fancy-trick');
switch (trickName) {
...
}
});
For real-life examples of this technique, look at Bootstrap's Javascript components.
You can simply have separate js files per page and include them in relevant pages. For shared script code, have a common js file. Following your example:
common.js
var myCommonVar = {};
function myCommonFunction(...){
...
}
page1.js
$(document).ready(function() {
...
function1();
...
});
page2.js
$(document).ready(function() {
...
function2();
...
});
page1.html
...
<script src='/js/common/js'></script>
<script src='/js/page1.js'></script>
...
page2.html
...
<script src='/js/common/js'></script>
<script src='/js/page2.js'></script>
...
Consider the usage of AMD (Asynchronous Module Definiton) design pattern. Put your JavaScript code into modules and on each page use just those you really need to. For example requirejs does a great job and I've been using it with success. If you have a bigger project you can split your modules into namespaces. This approach will keep excellent code maintainability and it's reliable. You simply put the "starter" javascript file on each page and load only those required modules you need to work with per each page.
There are many ways to deal with this problem, either using a JavaScript Framework that is aiming to treat your website as a 'Webapp' (Angular and Ember among the popular), or using your own custom script that will do just that - invoking the appropriate JavaScript per loaded page.
Basically, a custom script that will be able to handle it, will have to make use of (pseudo) 'Namespaces' to separate modules/pages code sections.
Assuming you have 2 hypothetical pages, Home and Browse, Simplified code sample may look like this:
HTML:
<body data-page="Home">
Global.js:
var MyApp = {}; // global namespace
$(document).ready(function()
{
var pageName = $('body').data('page');
if (pageName && MyApp[pageName] && MyApp[pageName].Ready)
MyApp[pageName].Ready();
});
Home.js:
MyApp.Home = MyApp.Home || {}; // 'Home' namespace
MyApp.Home.Ready = function()
{
// here comes your 'Home' document.ready()
};
Browse.js:
MyApp.Browse = MyApp.Browse || {}; // 'Browse' namespace
MyApp.Browse.Ready = function()
{
// here comes your 'Browse' document.ready()
};
MyApp.Browse.AnotherUtilFunc = function()
{
// you could have the rest of your page-specific functions as well
}
Also, since you're using ASP.NET MVC, sometimes your Controller name may fit as the qualified page name, you can set it automatically in your Layout.cshtml (if you have one):
<html>
<head>
</head>
<body data-page="#ViewContext.RouteData.Values["Controller"].ToString()">
#RenderBody()
</body>
</html>
I think its not worth stuffing up everything in a single file and separating them with conditional statements, just to avoid adding a reference on the respective file.
If you have code that can be called on 2,3 or more pages, then we can opt for having them in a common file. But if its going to be called on a single page then we must write code on the respective page only. This will also increase the overhead of declaring the functions that are not going to be called on the current page
And when you are using the common js file, then you don't need to worry about the $(document).ready(); event, you can use a single ready event in the common file and separate the code by using conditional statements.
The new versions of the script manager will combine everything into one blob of a script. In theory it makes fewer round trips and things run faster. In practice you could end up with several large scripts that are nearly identical and each page needs its own blob of a script. If your making one of those never change the url website pages then this is the way to go.
I came up with these best practices when I was working with jquery on ASP.Net
Load Jquery in your master page above the first script manager. Jquery is now available on every page. The browser will only get it once and cache it.
If bandwidth is an issue use a jquery loader like googleload or MS content delivery network
Document.load is always at the bottom of the page to guarantee that everything needed is already loaded.
From my blog that I haven't updated in years...Google Load with ASP.Net
One common way to address this problem would be to have your common script include followed by a per-page script element:
<!-- In 'shoppingcart.html' -->
<script src="main.js"></script>
<script>
// Let there be a onDomReady JS object inside main.js
// that defines the document.ready logic on a per-page basis
$(document).ready(onDomReady.shoppingCart);
</script>
Great question, I have been dealing with the same thing.
Here is what I have been doing:
Have your $(document).ready() call different init functions (if they exist), where each .js file has its own init which adds event listeners and loads functions, messes with css, etc.. Each .js file is separated out into different pieces of functionality.
This way you have one document ready that calls all of your initializers. So each page would include the .js functionality it needs. This way you can separate out what is different.
ex:
ready.js:
$(document).ready(function(){
if (typeof menuNavInit == 'function'){
menuNavInit();
}
if (typeof menuNavDifferentInit == 'function'){
menuNavDifferentInit();
}
//other .js functionality
});
menuNav.js
function menuNavInit(){
$("#menu").on('click', menuNavClick)
}
function menuNavClick(){
//do something
}
menuNavDifferent.js
function menuNavDifferentInit(){
$("#menu").on('click', menuNavDifferentClick)
}
function menuNavDifferentClick(){
//do something else
}
page1.html
...
<script src='scripts/ready.js'></script>
<script src='scripts/menuNav.js'></script>
...
page2.html
...
<script src='scripts/ready.js'></script>
<script src='scripts/menuNavDifferent.js'></script>
...
Related
What I need is to hook/intercept other external JS load.
I can place js anywhere in document
Example:
<script src="hook.js"></script>
<script src="a.js"></script>
<script src="b.js"></script>
Hook.js should intercept a.js and b.js. Problem is, that when hook.js is executed, i cannot see other scripts (document.scripts contains only hook.js) and document ready event is too late (scripts a.js and b.js are executed).
Is there any way to "see" other script tags, before are executed ?
Thanks for any help
Edit
I need to do any "magic" inside hook.js without modyfing (statically) other HTML.
No jQuery
Credit goes here: https://stackoverflow.com/a/59424277/2016831
You can use a MutationObserver to see what elements are being added to the DOM, and when they are being added, simply change the source code, or if its referencing another URL, just redirect it to your own server, with the original URL as a get parameter instead, and return the modified code that way.
Based on the above answer, you could do something like this:
<script>
new MutationObserver((m, o) => {
let potentialScript = document.querySelector("script + script");
console.log(potentialScript.textContent);
if(potentialScript) {
o.disconnect();
potentialScript
.textContent =
potentialScript
.textContent
.replace(
"})()",
`
window.wow = mySecretMethod;
})()
`
);
}
}).observe(
document.body,
{
childList:1
}
);
</script>
<script>
(function() {
let mySecretMethod = () => {
//does a bunch of evil secret stuff
console.log("HA!");
};
})();
wow()
</script>
<script>
console.log(wow())
</script>
Alternatively you can redirect the HTTP requests with a chrome extension, see https://stackoverflow.com/a/61202516/2016831 for more
If I understand what you're trying to do correctly...
If you can control how scripts A and B are loaded, the best approach is to place them on the same domain as the current page (possibly via proxy), load the files via AJAX, and insert your hooks that way. A library like jQuery as m.casey suggested would make the details of the AJAX and executing the script quite simple.
Otherwise, Javascript does not really have the ability to interact with the parsing of the document (which is what is causing scripts a and b to be loaded in your example, and what would be need to be modified to "intercept" script loading), except by using the evil of document.write to modify the HTML stream. Of course, this only works if hook.js is loaded synchronously (as it is in your example code), if it's loaded into HTML and not XHTML, if you can place a second hook afterwards to postprocess the modified HTML stream, and if you are sure the HTML stream won't escape your mechanism.
For example..
<script id="hook1">document.write("<"+"textarea id='capture'>");</script>
<script src="a.js"></script>
<script src="b.js"></script>
<script id="hook2">document.write("<"+"/textarea");</script>
<script id="hook3">doSomethingWith(document.getElementById("capture").value)</script>
Note that this is a huge hack and you probably shouldn't be doing it.
If you're using jQuery, you could have hook.js load the scripts you wish to intercept as follows:
$.getScript("a.js");
$.getScript("b.js");
This would dynamically create the script tags and you would be certain that hook.js would always proceed a.js and b.js.
I'm implementing this Developr theme from themeforest into a Meteor app.
I'm currently accomplishing this by placing the javascripts in question to /public and appending them using jQuery:
Meteor.startup(function() {
$('head').append('<script src="/template_stuff.js"></script>');
// .. all 7 scripts or so
});
If the required scripts are placed in /client/js/, it appears that they either run too early or before the DOM is done loading. If they are placed directly in the header of the main html file, they seem to bug out the same way.
Am I missing something here - is there a more elegant way to make the scripts load after DOM has loaded?
There are several methods for waiting until the DOM has loaded to inject your scripts:
Meteor.startup (as you illustrated)
jQuery's document ready: $(function () { ... })
script tag at the bottom of your layout template
Regarding elegance, I use a handlebars helper as it allows me to consolidate all 'after body' code in one place. Then use jQuery's document ready as needed.
For example,
// in client/handlebars.js
Handlebars.registerHelper('afterBody', function(name, options) {
function isMobileSafari() {
return navigator.userAgent.match(/(iPod|iPhone|iPad)/) &&
navigator.userAgent.match(/AppleWebKit/)
}
if (isMobileSafari()) {
$(function () {
FastClick.attach(document.body)
})
}
//// load scripts however you want...
// $(function () {
// $.getScript as Daniel suggests
// $('body').append as you have
// pure js: document.createElement('script')
// })
})
// in client/main.html (using mini-pages router)
<template name="layout_no_header">
{{{yield}}}
{{afterBody}}
</template>
<template name="layout">
{{> header}}
{{{yield}}}
{{afterBody}}
</template>
Use jQuery getScript()
(function ($) {
$.getScript("/template_stuff.js");
})(jQuery);
I think you might have a different problem. It sounds like the 3rd party code is being wrapped in a closure and is not working correctly. Try and place them inside the client/compatibility folder. This will prevent it from being wrapped in a closure, which can solve 3rd party problems. Be sure the load order inside of here is correct, it loads files in alphabetical order inside of a folder. (load order)
If that doesn't work, find out where the code is being executed and comment out the call. Then load your html inside of a template, even if it's just a 'page' template with all the html. Create a js file for the template and call the methods in the templates rendered callback.
// Execute this function when myPage template is rendered
Template.myPage.rendered = function() {
stuff.init();
stuff2.run();
};
Note, the to call stuff2, etc.. you'll likely need to have it's script in the compatibility folder so you can reach the namespaces from the template_stuff.js.
Can I create a jQuery prototype in this way:
<script
src="/Scripts/jquery/jquery-1.8.2.min.js"
type="text/javascript">
</script>
<script src="/Scripts/common/layout/jquery.fn.buildTableOfContent.js"></script>
When the buildTableOfContents.js file looks like this:
$.fn.buildTableOfContent = function () {
"use strict";
var h2 = this.find('h2');
if (h2.length > 0) {
.....
What I am concerned about is does the prototype function build depend on jQuery
being completely loaded and with my file like this will the buildTableOfContents file
always get executed after the jQuery has fully loaded?
Yes, your code requires $ to be defined, and hence jQuery to be loaded. And yes, putting the jQuery include before your plugin include guarantees that jQuery will be available when your plugin is created.
Upon reaching a script block, the browser will halt any further rendering of the page, download the external document, execute the code inside it, before proceeding to render the rest of the page. This is why you want to put your script blocks just before closing the body (so that the rest of the page will render regardless of blocking requests).
Im doing an embed code for our clients, so the clients could have multiple embed of our code (cant do it over iframe). I would want to try requirejs to do it, so my question is if i have multiple requirejs on the same page, both load different js with different paths+shims, will the oncomplete function for both of them work?
like the client would have on the main content (on their cms on specific page)
<script src="myexample.com/video.js?key=123"></script>
and on the sidebar (on their cms on all pages)
<script src="myexample.com/content.js?key=123"></script>
so my video.js would have
require.config({path: { video : '' }..., shim : {} ....});
require(['video','utils'], function(){ do something on video });
and the content.js would have
require.config({path: { content : '' }..., shim : {} ....});
require(['content','dom'], function(){ do something on content });
From what i go from here https://groups.google.com/forum/?fromgroups=#!topic/requirejs/MwQ-CNHxGKc it seems not possible that the 2 functions would be executed, meaning it would only process one of them.
If its not possible in requirejs can someone point me to other amd loaders where its possible. Thanks
You could use one loader with a minimal function that start both of your modules:
require(['video','content'], function(video, content){
video.start();
content.start();
});
Have a look at how Shootitlive solved it with their widget
One can pass arbitrary parameters to each embed
The embed code is short
It loads asynchronously
One can have multiple players on one page
http://shootitlive.com/2012/07/developing-an-embeddable-javascript-widget/
I have read quite a bit about unobtrusive JS and how to generate it and all that jazz...
My problem is this: I have a website that heavily relies on mod_rewrite, so essentially all the pages requests are sent to index.php that generates the main structure of the page and then includes the appropriate page. Now, there are different sections in the site and each section uses different Javascript functions (e.g. for different AJAX requests).
Now, if I just were to attach a function to the onload of the page obviously the thing would not work, as I do not have to initialise the same things for each page... so what is the best way to handle this situation?
I hope the situation is clear, I'll be happy to clarify if needed
You can use addEventListener (standard) or attachEvent in the HTML generated by the subsidiary PHP pages.
The syntax is simple. E.g.
document.addEventListener("load", someFunction, false);
This allows you to generate the full body tag in index.php but run different load handlers for each page. Also note that you can use this multiple times on the same element.
Nico, I would suggest creating a custom javascript code with each included page (doesn't matter where on the page you include the script tag) and, as Matthew suggested, after you define a function to run on page load, use the addEventListener to load that custom function on "load"
Let's say you define a function pageinit() somewhere in the body of the included document
function pageinit(){..
}
window.addEventListener("load", function() { pageinit(); }, false);
Does that make sense for your project?
I would simply put it in a .js file.
mysite_common.js - site wide common utils and functions
mysite_page_a.js - unique functionality for page a
mysite_page_b.js - unique functionality for page b
for page b you include b.js while on page a you would include a.js
Then in your respective unique.js you can wrap your functionality in a ondomready or similar.
Keep it separate from your PHP, then it is much less of an annoyance later, it also means that you can rely on caching for your js to keep your page loads slimmer.
You can also look at things like YUI loader which allows you to do much more complex things like ondemand loading of bits of js functionality.
You can use event delegation to provide different functionality depending on context.
Basically it works by attaching an event listener to a container element which captures clicks on child elements. You can then do away with individual event listeners alltogether, as well as look at hints from the parent.
say:
<div id='container' class='page_a'>
...
<input name='somename'>
...
</div>
Then
var attachDelegates = function(container){
container.onclick = function(e) {
e = e || window.event;
var t = e.target || e.srcElement;
//Your logic follows
if(t.name === 'somename'){
dosomething(t);
}
if(t.className === 'someclass'){
... something else ...
}
};
and onload = function(){attachDelegates('container');};
The attachDelegates function could be different for each page, or you could have a monolithic one and simple attach hints to the container or be selective about which classes you attach.
These are much more coherent explanations and examples:
http://cherny.com/webdev/70/javascript-event-delegation-and-event-hanlders
http://blog.andyhume.net/event-delegation-without-javascript-library
Personally I use YUI3
http://developer.yahoo.com/yui/3/examples/node/node-evt-delegation.html
as it gives me CSS3 style selectors and is pretty hassle free so far.