54

I typically put all the JavaScript scripts into one file e.g. scripts.js (the less HTTP request, the better). So, as expected, some scripts are needed for some pages, some aren't.

To target a specific page, I use something like:

if ($("body#share").length > 0) {
    // Place the logic pertaining to the page with ID 'share' here...
}

// The file / script continues...

Other or better suggestions? Thanks!

Clarification: I was not looking for the pros / cons between consolidating multiple JS files into one big file and keeping multiple separate JS files. The answer for this is surely 'depends on the situation' (we know that). My question is, assuming all my JS logic is placed into one big file, how do I make a particular (chunk of) script runs only when the corresponding page is loaded? One way I used to do is using if ($('#id-of-target-element')) { /* run the script */}; is there a better way?

moey
  • 10,587
  • 25
  • 68
  • 112
  • 2
    Are you using a server-side language like php? You could have the server check the current page and only send back the appropriate code for that page. – Brian Glaz Dec 07 '11 at 04:12
  • If all the code is being downloaded, such comparisons shouldn't be necessary. Assuming I'm reading your question accurately. – Jeffrey Sweeney Dec 07 '11 at 04:13
  • @JeffreySweeney he probably needs to check on which page he will act with a specific function... but still, for me it is a pretty weird approach, I hope I don't seem too rigid –  Dec 07 '11 at 04:20
  • 4
    If you do just have one .js file for all your code, wouldn't most browsers cache that file upon first load and not have to download it again for your other pages? – ToddBFisher Dec 07 '11 at 06:46
  • 1
    @DorinDuminica: You're right; I need to check which function to execute depending on the page I am on. – moey Dec 08 '11 at 02:18
  • As an aside, as a site grows, using classes on to denote features and using $("html").hasClass() is likely a better long term solution that using body IDs. – mpdonadio Jan 27 '12 at 20:17
  • [Paul Irish's approach](http://paulirish.com/2009/markup-based-unobtrusive-comprehensive-dom-ready-execution/) is definitely worth a look - I'd recommend it any day. Checkout my answer for more info. – Charlino Jan 31 '12 at 01:37
  • @MPD Just curious, why is using classes on html a better long term solution as applied to this question than using body IDs? – stepanian Jul 22 '16 at 20:08
  • @moey I think your method is simpler and more practical than the accepted answer. – stepanian Jul 22 '16 at 20:12

11 Answers11

26

I like Paul Irish's approach... you don't have to follow it exactly, but the general idea is a very solid one.

It might look something like this for your example

Html

<body id="share">

Your page specific javascript

YourNamespace = {
  share : {
    init : function(){
      // Place the logic pertaining to the page with ID 'share' here...
    }
  }
}

Paul Irish's Javascript that makes the magic happen

UTIL = { 
  fire : function(func,funcname, args){
    var namespace = YourNamespace;  // indicate your obj literal namespace here

    funcname = (funcname === undefined) ? 'init' : funcname;
    if (func !== '' && namespace[func] && typeof namespace[func][funcname] == 'function'){
      namespace[func][funcname](args);
    }
  }, 

  loadEvents : function(){
    var bodyId = document.body.id;

    // hit up common first.
    UTIL.fire('common');

    // do all the classes too.
    $.each(document.body.className.split(/\s+/),function(i,classnm){
      UTIL.fire(classnm);
      UTIL.fire(classnm,bodyId);
    });

    UTIL.fire('common','finalize');
  }
};

// kick it all off here 
$(document).ready(UTIL.loadEvents);

So the line you see directly above will kick off the following

YourNamespace.common.init()
YourNamespace.share.init()
YourNamespace.common.finalize()

Have a read of his blog post and a few of the variations linked from it.

Mick
  • 30,759
  • 16
  • 111
  • 130
Charlino
  • 15,802
  • 3
  • 58
  • 74
  • 2
    Rather than using the body id, I would use a `data-*` attribute, which would allow you to have JS code shared between pages. For example, if you have two pages: page1 with: ``, and page2 with: ``, then those pages both share JS (category1), and have page-specific JS (page1 and page2). Obviously you would need to modify the JS code to split the data-page attribute by space and loop through each value, executing the methods in order. – jbyrd Sep 30 '15 at 16:02
  • This is actually very nice. I would like to add my approach that is quite similar https://gist.github.com/bacloud14/826329ab11a3d4b00d95b9f0a97d6c6c – Curcuma_ Jun 11 '21 at 06:42
9

Similar questions have been already asked and the correct answer was and always will be

It depends on the situation.

However, if your concern is about minimizing the round-trip time (RTT) then it is certain that

Combining external scripts into as few files as possible cuts down on RTTs and delays in downloading other resources.

It is good to keep it as few as possible, but you don't necessarily have to keep it into one file strictly.

Let's take a look at why it is so.

While partitioning code into modular software components is a good engineering practice, importing modules into an HTML page one at a time can drastically increase page load time. First, for clients with an empty cache, the browser must issue an HTTP request for each resource, and incur the associated round trip times. Secondly, most browsers prevent the rest of the page from from being loaded while a JavaScript file is being downloaded and parsed.

These images show it more clearly why combining a number of JavaScript files into fewer output files can dramatically reduce latency:

All files are downloaded serially, and take a total of 4.46 seconds to complete All files are downloaded serially, and take a total of 4.46 seconds to complete.

After collapsing the 13 js files into 2 files: The same 729 kilobytes now take only 1.87 seconds to download The same 729 kilobytes now take only 1.87 seconds to download

Edit after Clarification given by Siku-Siku.Com: Sorry! I totally misunderstood your question. I don't know of any better way for making a particular (chunk of) script run only when the corresponding page is loaded. I think your way is good enough.

Community
  • 1
  • 1
Sajib Mahmood
  • 3,382
  • 3
  • 37
  • 50
8

Your suggestion seems OK. I would however use an HTML 5 data- attribute to tag every page like this:

<body data-title="my_page_title">

You can then write conditional javascript code by checking this property (jQuery 1.4.3 onwards):

if ($("body").data("title") === "my_page_title") {
    // Place the logic pertaining to the page with title 'my_page_title' here...
}

This allows you to systematically group all code for a certain page in a sensible way

Wesley
  • 2,204
  • 15
  • 14
5

Another option is that you could have, within the HTML code

<script>
    callYourJSFunctionHere();  /*Things you would want to happen on Page load*/
</script>

This will follow the normal flow of the page. Therefore if you are playing with elements on the page, you will need to place this <script> portion at the bottom of the page, after all the elements have been loaded by the browser.

I'm not 100% sure this is more efficient then the current solution you have, but on the other hand, it will be telling someone looking at your HTML page what JavaScript will be run when the page loads. This might be helpful in terms of maintenance down the road.... no so much a guessing game as to what script runs when the page loads.

Hope this helps!

blo0p3r
  • 6,790
  • 8
  • 49
  • 68
  • 1
    +1 A good alternative to the (also good) suggested approach in the question. If there is a framework like jQuery in play, then you don't have to put the code at the bottom of the page, just wrap it in a `$(document).ready()` or similar... – Stobor Jan 28 '12 at 17:01
  • 3
    @Stobor the two are not the same thing. $(document).ready() has to execute $(document), parse the function you feed ready(), and call ready() all where it is written. If you put the code at the end of the page then it isn't executed until everything before it has been parsed. Additionally, $(document).ready() waits until the entire page loads, including external resources like images. The equivalent (in terms of functionality, not processing order) to putting the code at the end of the page would be simply setting window.onload. –  Jan 30 '12 at 02:51
  • 2
    @Skier88 : wrong, document.ready (or jQuery's $(function(){...}); ) runs the passed function as soon as the DOM is loaded, it does NOT wait for external content such as files to load! The Window.onload does... see http://stackoverflow.com/questions/3698200/window-onload-vs-document-ready – Pierre Henry Feb 04 '13 at 09:49
4

Ok. Lets hope, code example will say it better, than wall of text:


your one-and-only.js file:

var MY_SITE = {
    // you have one namespace-object where you hold all your stuff
    main_page: {
        // inside you have smaller chunks, for specific pages or widgets
        // this one is for main page
        _init: function () {
        ... 
        },
        ... 
        showSplashScreen: function () {
        ...
        }      
    },
    ... 
    // but there are other pages
    contacts: { ... },
    // or widgets
    cool_menu: { ... },
    // and maybe you want to put common functions into dedicated block
    global_utilities: { ... },
    // and of course - the loader
    _start: function () {
        // here we simply call one _init of the page module that was specified 
        this[PAGE_TYPE]._init(); 
        // and more tricky stuff - we can search the page for elements
        // that contain special data-attribute, collect them
        var widgets = $('[data-our-widget]').each().getAttributeValue( 'data-or-widget' );
       // and then _init each of those widgets just like we did with main page 
        widgets.forEach( function (v) {
            this[v]._init();
        }
    }
};
document.on('ready', MY_SITE.start); // here we assume some random js framework

your <script> tags on the page:

<script>var PAGE_TYPE = 'main_page';</script>
<script src="one-and-only.js" />

your 'magic' elements on the page:

<div id="whatever" data-our-widget="cool_menu"> ... </div>

Disclaimer: This is high level overview! Implementation details may and should vary, depending on your needs.

c69
  • 19,951
  • 7
  • 52
  • 82
2

Your question is asking how to only load chunks of your scripts based upon a particular page. However, since your stated goal is minimizing http requests (and traffic, I assume), I think my unique (complicated) implementation is at least relevant to this question, if not a good alternative/complement to your approach.

I use this in an embedded application where I combine all javascript files into one large one for reducing http requests. I had to figure out a caching method as well. A 120kb javascript file in addition to several decently-sized images take a while to load when using a barebones 50kb webserver.

First off, I use YUI Compressor to compress my javascript files, as that's an easy way to save bandwidth.

A cool "feature" I discovered was if you link to anything (css, js, img) like the following, you'll simply load the file and ignore the parameters past the '?' in 'src':

<script type="text/Javascript" src="js/all.js?2382a97f099f42cc43c1b616cd24f281"></script>

Now, that seemingly random jumble of numbers and letters is actually the md5 checksum of the javascript file! I then modified my server's response header for the correlated extensions (css, js, img, etc) to never expire. This header is not sent for html files, as we always want the latest versions of those.

Cache-Control:max-age=290304000

This means clients will load the javascript file once and only once until the checksum changes. Caching works by looking up the entire request, including file parameters.

I'm actually compiling my html/js/css/image files in with C code, so I have a perl script automatically insert those md5 checksums into the html files before compiling. This obviously won't work in your case, but you can use any number of ways to handle it, as long as you have a server-side language to aid you. For example, you can have php calculate the checksum on-the-fly, or you can have php store the checksums in a database or flat file to easily look them up. In order to flag php to recalculate the checksum, you could check the filetime, or delete the database entries/flat file.

I realize this is a lot of upfront work and might not be what you're after, but it works wonderfully for me. The performance gains for me are incredible. The first page load is awfully slow (8.5 seconds), but after that, all your scripts, css, and images are all cached now. New page loads within the same application drop to ~300ms.

Jeff Lamb
  • 5,755
  • 4
  • 37
  • 54
  • That's not really all that unique - but it is a good description of current best practices. (Most people use version numbers instead of the md5sums, but that's just a matter of choice...) – Stobor Jan 28 '12 at 16:55
2

As you've rightly mentioned, the idea of splitting / combining files really does depend on the situation.

I don't think there can be a better solution for what you're doing, as I have seen many big websites implement a similar strategy albeit more for CSS than js.

Eg (An excerpt from facebook):

<body class="hasLeftCol home fbx hasSlimHeader safari4 win Locale_en_US defaultScrollbar">

There is one more great advantage you can reap out of this. You can selectively apply some JS code into groups of pages using the class attribute.

ie.

if $('body.pagesThatHaveThisClass').length > 0)
{.... code ...}

if $('body#singlePageWithThisId').length > 0)
{.... code ...}

Else, there is a technique that many JS/jQ based menus use to automatically give a different style to the current page. You can match the window.location to specific a address.

ie.

if (window.top.location.href == 'http://domain.com/mypage1.html')
{ .... code ....}

or even

switch (window.top.location.href)
{
   case 'http://domain.com/mypage1.html' :
    { .... code ....}

   case 'http://domain.com/mypage2.html' :
    { .... code ....}

}

I would still prefer your technique as you can group code for multiple pages together. The only reason I can think of for using the second method is when the JS people don't have access to the HTML of the page to change the class / id etc.

Hope this is what you were looking for!

Gaurav Ramanan
  • 3,655
  • 2
  • 21
  • 29
2

How about testing window.location? That seems the obvious way for me.

Or as you already seem to do, test for the required elements to be there. That is a rather robust method, I see nothing wrong with that.

Has QUIT--Anony-Mousse
  • 76,138
  • 12
  • 138
  • 194
1

This is going to be a high level overview, but I recommend using Backbone to organize the logic for each "view" (or page.) I use a root element and change the class of that element to switch between the other views (or states) of the layout. You can then organize the secondary styles by class and keep a primary style under the ID.

HTML

<div id="layout" class="default">
    <!-- PUT DEFAULT HTML IN HERE -->
</div>
<script id="shareTemplate" type="text/html">
    <!-- PUT SHARE HTML IN HERE -->
</script>

CSS

#layout {
    /* primary style */
}

#layout.share {
   /* secondary style */
}

JavaScript

window.LayoutView = Backbone.View.extend({

    el: "#layout",

    shareTemplate: _.template($("#shareTemplate").html()),

    initialize: function() {
        this.render(shareTemplate, data);
        this.el.className = "share";
    },

    render: function(template, data) {
        $(this.el).html(template(data));
        return this;
    }

});

window.Layout = new LayoutView;
rxgx
  • 5,089
  • 2
  • 35
  • 43
1

Based on the assumption that all of the different pages on your site is categorically or at least logically grouped into different "view models", with a different view model for a different type of page (e.g. "search results", "blogposts", "front page", "single blogpost", etcetera), you could have a single javascript file that contains the scripts in common for all of your site, and one specific for each view model (if you deem it necessary).

You could then have the server insert the name of the view model into, for instance, a HTML data-tag, for later use by JavaScript, and an additional script tag after the main script library.

For instance:

<head>
  <!--
    Two JavaScript files, one for the common functionality,
    and one for the view model specfic
  -->
  <script type="text/javascript" src="path/to/scriptslib.js"></script>
  <script type="text/javascript" src="path/to/viewmodel.front_page.js"></script>
</head>
<body data-view-model="front_page">
  ...
</body>

In scriptslib.js:

var
  GlobalSettings = {},
  ViewModel = {};

$(function () {
  // Save the view model in a variable when the body's been loaded
  GlobalSettings.viewModel = $("body").attr("data-view-model");
  // Assuming that the viewmodel JavaScript file has been loaded, call a method that it'll have by convention here
  ViewModel.initialize();
  // Additionally, you can later test for specific view models and perform necessary actions, if needed
  if (viewModel === "front_page") {
    // ...
  }
});

Then, in viewmodel.front_page.js (or any other view model file):

ViewModel.FrontPage = {
  // data and methods specific for this view model
}
ViewModel.initialize = function () {
  // do stuff specific for this view model
  this.FrontPage.someFunction();
};

Note, that the way I've described the approach here is for sites that rely on page reloads for shifting between pages. If you were using Ajax to shift between pages, you'd want JavaScript to dynamically load the new view models and execute them.

Hope this could work for you ;)

Sune Rasmussen
  • 956
  • 4
  • 14
0

You can follow your approach, but suppose your "cocktail" javascript file has 20k lines of code, and you include it in all you html files, even those that show a "404" or "500", wouldn't it use more unnecessary bandwidth?

I opt for using different js for each HTML page and whatever is used across multiple pages dump it in a "utils.js" or whatever you want to call it, example

\www
  \pages
    \index.html
    \dashboard.html
    \account.html
    \ ...
  \scripts
    \index.js <- for index.html
    \dashboard.js <- for dashboard.html
    \account.js <- for account.html
    \ ...
    \utils.js|shared.js|sharedlib.js <- used by all pages

so basically it will result in:

  • 1 request for the html
  • 1 for the page script
  • 1 for the shared script, if used
  • n for css's, but I assume is 1

This is just my two cents..

  • 2
    It's not that bad to have one 'big' (compressed) script file that is used on all pages. This one file is cached and thus actually very efficiently used as it's downloaded only once per client. – Wesley Jan 27 '12 at 14:04
  • With "HTTP/2 Server Push" you can push all the javascript files in the first request. So this is no more an issue, what leads me to have one global file and one file per view. – Peter Húbek Apr 01 '18 at 11:50