How do you organize large JS/jQuery codebases across your entire website? There are plenty of good resources on how to organize pieces of your code, but nothing really about how to pull it all together and fit each piece into place: side wide code organization, multiple pages using the same code, staying DRY with loose coupling, etc.
Below is how I deal with it. I've never been comfortable organizing my code like this, because I think it's sloppy and can lead to maintainability/scaling problems, but I don't really know any better.
I realize I everyone has their own set of requirements and there’s no turn-key solutions, but I’d love to hear some opinions about what I’m doing wrong, WHY I’m doing it wrong, and suggestions on how to write more maintainable code.
What I think I’m really trying to get at:
How do you deal with logic that you need to use in multiple places, on multiple pages?
How do you organize page-specific code? Is namespacing each page into a global object a good idea?1.
What do you do from the start to ensure you’re not constantly re-writing your organization patterns as your app grows larger and larger? I’m probably on my 4th iteration writing this thing.2.
Each page receives the main application.js file. Each additional page has it's own application.pagename.js file. I use server side logic to include the files (first checking to see if one even exists for the page - some pages don't need JS), and then init them in order.
So my home page looks like:
<script src="js/application.js"></script>
<script src="js/application.index.js"></script>
<script>
MyApp.init();
MyApp.index.init();
</script>
my URL convention is /page/subpage/id/. I have about 10 pages and a whole slew of subpages, each subpage requiring their own logic. see the last example in this post.
Most of my code is already modularized into either jQuery UI widgets or jQuery plugins, so I'd say 75% of the code in these files is require()'ing a widget and initing it.
I use requireJS to pull in widgets as necessary.
// application.js
var MyApp = {
init: function(){
var self = this;
// these widgets are available on every single page
// notice the call to jquery.deparam.js - i'll use this later to init subpage logic.
require(['js/widget1.js', 'js/widget2.js', 'js/widget3.js', 'js/jquery.deparam.js'], function(){
// deparam the query string. I'll use this later.
self.querystring = $.deparam.querystring();
// init widgets once the document is ready
$(function(){
$("#widget1").widget1();
$("#widget2").widget2();
// make these bindings available immediately as well.
self.rebindable();
});
});
},
// I use jQuery UI Dialog extensively as a window manager, and each dialog is loaded
// via an AJAX request. I'll call this method after each AJAX request to
// rebind some key widgets.
rebindable: function(){
$("#widget3").widget3();
}
};
// application.index.js
// home page specific stuff. this file is only included on the home page.
MyApp.index = {
// my convention is that init is automatically called after the script
// is included in a page, outside of a doc.ready statement.
init: function(){
var self = this;
require(['js/widget4.js'], function(){
$(function(){
self.widget4( $("#foo") );
});
});
},
// passing elements to each method allows me to call this init code
// outside of the index page. I can require() this file, and only init
// widget4, and even use a different element.
widget4: function( element ){
var config = {
something: "custom to the home page"
};
element.widget4( config );
}
};
// application.foo.js
// page "foo" stuff
MyApp.foo = {
init: function(){
var self = this;
// this page happens to use the same widget3 and configuration present
// in MyApp.index. this is where things can get sloppy and unmaintainable
// really quickly.
require(['js/application.index.js'], function(){
$(function(){
MyApp.index.widget3( $("#bar") );
});
});
// page "foo" has three subpages (or actions) and require
// their own logic. url convention: /foo/subpage1/
// init whichever page we're on...
switch( self.querystring.subpage ){
case "subpage1":
self.subpage1.init();
break;
case "subpage2":
self.subpage2.init();
break;
case "subpage3":
self.subpage3.init();
break;
}
},
subpage1: function(){
init: function(){
var self = this;
// once the DOM is ready init dialog.
$(function(){
self.dialog( $("#openDialog") );
});
},
dialog: function( element ){
element.bind("click", function(){
$('<div></div>').dialog({
open: function(){
// see what i'm doing here?
MyApp.rebindable();
// maybe more bindings specific to this
// dialog here
}
});
});
}
},
subpage2: function(){
init: function(){
}
},
subpage3: function(){
init: function(){
}
}
};