Jump to content

Year

Day

24 Ways to impress your friends

18 12/2007 Keeping JavaScript Dependencies At Bay

by Christian Heilmann

As we are writing more and more complex JavaScript applications we run into issues that have hitherto (god I love that word) not been an issue. The first decision we have to make is what to do when planning our app: one big massive JS file or a lot of smaller, specialised files separated by task.

Personally, I tend to favour the latter, mainly because it allows you to work on components in parallel with other developers without lots of clashes in your version control. It also means that your application will be more lightweight as you only include components on demand.

Starting with a global object

This is why it is a good plan to start your app with one single object that also becomes the namespace for the whole application, say for example myAwesomeApp:

  1. var myAwesomeApp = {};

You can nest any necessary components into this one and also make sure that you check for dependencies like DOM support right up front.

Adding the components

The other thing to add to this main object is a components object, which defines all the components that are there and their file names.

  1. var myAwesomeApp = {
  2. components :{
  3. formcheck:{
  4. url:'formcheck.js',
  5. loaded:false
  6. },
  7. dynamicnav:{
  8. url:'dynamicnav.js',
  9. loaded:false
  10. },
  11. gallery:{
  12. url:'gallery.js',
  13. loaded:false
  14. },
  15. lightbox:{
  16. url:'lightbox.js',
  17. loaded:false
  18. }
  19. }
  20. };
  21. Source: /code/keeping-javascript-dependencies-at-bay/2.txt

Technically you can also omit the loaded properties, but it is cleaner this way. The next thing to add is an addComponent function that can load your components on demand by adding new SCRIPT elements to the head of the documents when they are needed.

  1. var myAwesomeApp = {
  2. components :{
  3. formcheck:{
  4. url:'formcheck.js',
  5. loaded:false
  6. },
  7. dynamicnav:{
  8. url:'dynamicnav.js',
  9. loaded:false
  10. },
  11. gallery:{
  12. url:'gallery.js',
  13. loaded:false
  14. },
  15. lightbox:{
  16. url:'lightbox.js',
  17. loaded:false
  18. }
  19. },
  20. addComponent:function(component){
  21. var c = this.components[component];
  22. if(c && c.loaded === false){
  23. var s = document.createElement('script');
  24. s.setAttribute('type', 'text/javascript');
  25. s.setAttribute('src',c.url);
  26. document.getElementsByTagName('head')[0].appendChild(s);
  27. }
  28. }
  29. };
  30. Source: /code/keeping-javascript-dependencies-at-bay/3.txt

This allows you to add new components on the fly when they are not defined:

  1. if(!myAwesomeApp.components.gallery.loaded){
  2. myAwesomeApp.addComponent('gallery');
  3. };
  4. Source: /code/keeping-javascript-dependencies-at-bay/4.txt

Verifying that components have been loaded

However, this is not safe as the file might not be available. To make the dynamic adding of components safer each of the components should have a callback at the end of them that notifies the main object that they indeed have been loaded:

  1. var myAwesomeApp = {
  2. components :{
  3. formcheck:{
  4. url:'formcheck.js',
  5. loaded:false
  6. },
  7. dynamicnav:{
  8. url:'dynamicnav.js',
  9. loaded:false
  10. },
  11. gallery:{
  12. url:'gallery.js',
  13. loaded:false
  14. },
  15. lightbox:{
  16. url:'lightbox.js',
  17. loaded:false
  18. }
  19. },
  20. addComponent:function(component){
  21. var c = this.components[component];
  22. if(c && c.loaded === false){
  23. var s = document.createElement('script');
  24. s.setAttribute('type', 'text/javascript');
  25. s.setAttribute('src',c.url);
  26. document.getElementsByTagName('head')[0].appendChild(s);
  27. }
  28. },
  29. componentAvailable:function(component){
  30. this.components[component].loaded = true;
  31. }
  32. }
  33. Source: /code/keeping-javascript-dependencies-at-bay/5.txt

For example the gallery.js file should call this notification as a last line:

  1. myAwesomeApp.gallery = function(){
  2. // [... other code ...]
  3. }();
  4.  
  5. myAwesomeApp.componentAvailable('gallery');
  6. Source: /code/keeping-javascript-dependencies-at-bay/6.txt

Telling the implementers when components are available

The last thing to add (actually as a courtesy measure for debugging and implementers) is to offer a listener function that gets notified when the component has been loaded:

  1. var myAwesomeApp = {
  2. components :{
  3. formcheck:{
  4. url:'formcheck.js',
  5. loaded:false
  6. },
  7. dynamicnav:{
  8. url:'dynamicnav.js',
  9. loaded:false
  10. },
  11. gallery:{
  12. url:'gallery.js',
  13. loaded:false
  14. },
  15. lightbox:{
  16. url:'lightbox.js',
  17. loaded:false
  18. }
  19. },
  20. addComponent:function(component){
  21. var c = this.components[component];
  22. if(c && c.loaded === false){
  23. var s = document.createElement('script');
  24. s.setAttribute('type', 'text/javascript');
  25. s.setAttribute('src',c.url);
  26. document.getElementsByTagName('head')[0].appendChild(s);
  27. }
  28. },
  29. componentAvailable:function(component){
  30. this.components[component].loaded = true;
  31. if(this.listener){
  32. this.listener(component);
  33. };
  34. }
  35. };
  36. Source: /code/keeping-javascript-dependencies-at-bay/7.txt

This allows you to write a main listener function that acts when certain components have been loaded, for example:

  1. myAwesomeApp.listener = function(component){
  2. if(component === 'gallery'){
  3. showGallery();
  4. }
  5. };
  6. Source: /code/keeping-javascript-dependencies-at-bay/8.txt

Extending with other components

As the main object is public, other developers can extend the components object with own components and use the listener function to load dependent components. Say you have a bespoke component with data and labels in extra files:

  1. myAwesomeApp.listener = function(component){
  2. if(component === 'bespokecomponent'){
  3. myAwesomeApp.addComponent('bespokelabels');
  4. };
  5. if(component === 'bespokelabels'){
  6. myAwesomeApp.addComponent('bespokedata');
  7. };
  8. if(component === 'bespokedata'){
  9. myAwesomeApp,bespokecomponent.init();
  10. };
  11. };
  12. myAwesomeApp.components.bespokecomponent = {
  13. url:'bespoke.js',
  14. loaded:false
  15. };
  16. myAwesomeApp.components.bespokelabels = {
  17. url:'bespokelabels.js',
  18. loaded:false
  19. };
  20. myAwesomeApp.components.bespokedata = {
  21. url:'bespokedata.js',
  22. loaded:false
  23. };
  24. myAwesomeApp.addComponent('bespokecomponent');
  25. Source: /code/keeping-javascript-dependencies-at-bay/9.txt

Following this practice you can write pretty complex apps and still have full control over what is available when. You can also extend this to allow for CSS files to be added on demand.

Influences

If you like this idea and wondered if someone already uses it, take a look at the Yahoo! User Interface library, and especially at the YAHOO_config option of the global YAHOO.js object.

This article available in German at webkrauts.de

Like what you read?

Comments

Got something to add? You can just leave a comment.

Commenting is closed for this article.

About the author

Christian Heilmann

Christian Heilmann grew up in Germany and, after a year working with people with disabilities for the red cross, spent a year as a radio producer. From 1997 onwards he worked for several agencies in Munich as a web developer. In 2000 he moved to the States to work for Etoys and, after the .com crash, he moved to the UK where he lead the web development department at Agilisys. In April 2006 he joined Yahoo! UK as a web developer. He publishes an almost daily blog at http://wait-till-i.com and runs an article repository at http://icant.co.uk. He is a member of the Web Standards Project's DOM Scripting Task Force and the author of Beginning JavaScript with DOM Scripting and Ajax: From Novice to Professional.

More information

In association with:

Perch - a really little cms