JavaScript Loader
Author: Samuel Williams When: Friday, 05 February 2010April 2009
May 2009
August 2009
September 2009
October 2009
- Building a Concrete Bath
- LED Lighting Comparison
- Thinking about Programming Languages
- How To Be A Consultant
- Lucid Programming Dojo
- Exim4 + ClamAV + SpamAssassin
- Secure login using AJAX
- Ramaze And Rack
- ActiveMerchant
- Concurrency And Immutability
- Floating Point Numbers
- Programming And Debugging
- Useful jQuery Plugins
- Loading Anonymous Ruby Classes
- 尺八 (Shakuhachi)
- Card Trick
- Object Oriented C
- Gemcutter
- Writing Clearly
- Richard Stallman In Christchurch
- Magnatune
- Client Side Graphing
- Zena CMS
November 2009
February 2010
March 2010
April 2010
May 2010
June 2010
July 2010
August 2010
September 2010
December 2010
January 2011
March 2011
May 2011
August 2011
September 2011
I recently started experimenting with SyntaxHighlighter for making code on my blog more readable. Generally, SyntaxHighlighter has been great. However one issue I ran into was the number of files that need to be loaded. Each language has its own JavaScript file that must be loaded in order to display that particular language. This leads to slow page loads, even if the majority of the code is cached.
To solve this problem, I've written a dynamic JavaScript and CSS loader using jQuery. It is really very simple.
This class is a helper class to load external code. It assists with this process by allowing code to load asynchronously, and once it has all been loaded, to execute a given function.
function ScriptLoader () {
this.count = 0;
this._finishCallback = null;
this.jobs = []
}
ScriptLoader.prototype._finishLoad = function () {
this.count -= 1;
if (this.count == 0) {
if (this.jobs.length > 0) {
this.loadImmediately(this.jobs.shift());
} else if (this._finishCallback != null) {
this._finishCallback();
}
}
}
ScriptLoader.prototype.load = function(path) {
if (this.count == 0)
this.loadImmediately(path);
else
this.jobs.push(path);
}
ScriptLoader.prototype.loadImmediately = function(path) {
this.count += 1;
var scriptLoader = this;
$.ajax({
type: "GET",
url: path,
success: function (data) {
scriptLoader._finishLoad();
},
dataType: "script",
cache: true
});
};
ScriptLoader.prototype.onFinish = function(callback) {
if (this.count == 0) {
callback();
} else {
this._finishCallback = callback;
}
};
Here is a simple example of how to use this loader:
var scriptLoader = new ScriptLoader();
scriptLoader.load("teapot.js");
scriptLoader.load("optional1.js");
scriptLoader.load("optional2.js");
scriptLoader.onFinish(function() {
Teapot.initialize();
});
In this example, the final function Teapot.initialize() will only be run after all the scripts have been loaded. The order of loading scripts is also enforced. This is important, because since the scripts are loaded asynchronously, we don't know when the Teapot will be available.
Here is the code which scans through all <pre> elements and loads the appropriate files:
DefaultSyntaxBrushes = [
['ActionScript3', 'shBrushAS3', ['as3', 'actionscript3']],
['Bash/shell', 'shBrushBash', ['bash', 'shell']],
['C#', 'shBrushCSharp', ['c-sharp', 'csharp']],
['C++', 'shBrushCpp', ['c++', 'cpp', 'c']],
['CSS', 'shBrushCss', ['css']],
['Delphi', 'shBrushDelphi', ['delphi', 'pas', 'pascal']],
['Diff', 'shBrushDiff', ['diff', 'patch']],
['Groovy', 'shBrushGroovy', ['groovy']],
['JavaScript', 'shBrushJScript', ['js', 'jscript', 'javascript']],
['Java', 'shBrushJava', ['java']],
['JavaFX', 'shBrushJavaFX', ['jfx', 'javafx']],
['Perl', 'shBrushPerl', ['perl', 'pl']],
['PHP', 'shBrushPhp', ['php']],
['Plain Text', 'shBrushPlain', ['plain', 'text']],
['PowerShell', 'shBrushPowerShell', ['ps', 'powershell']],
['Python', 'shBrushPython', ['py', 'python']],
['Ruby', 'shBrushRuby', ['rails', 'ror', 'ruby']],
['Scala', 'shBrushScala', ['scala']],
['SQL', 'shBrushSql', ['sql']],
['Visual Basic', 'shBrushVb', ['vb', 'vbnet']],
['XML', 'shBrushXml', ['xml', 'xhtml', 'xslt', 'html']],
['X86 ASM', 'shBrushX86ASM', ['x86-asm']],
['AppleScript', 'shBrushAppleScript', ['applescript']]
];
function loadCSS (path) {
$("head").append("<link>");
css = $("head").children(":last");
css.attr({
rel: "stylesheet",
type: "text/css",
href: path
});
}
function setupSyntaxHighlighter (settings) {
var scriptLoader = new ScriptLoader();
var alias_list = {};
var core_loaded = false;
$("pre").each(function(pre) {
var match = this.className.match(/brush: (.+?)(;|$)/);
if (match)
alias_list[match[1]] = true;
});
for (i in DefaultSyntaxBrushes) {
var brush = DefaultSyntaxBrushes[i];
var load = false;
for (j in brush[2]) {
if (alias_list[brush[2][j]])
load = true;
}
if (load) {
if (!core_loaded) {
scriptLoader.load(settings['root'] + 'shCore.js');
loadCSS(settings['root'] + 'shCore.css');
for (k in settings['stylesheets']) {
loadCSS(settings['root'] + settings['stylesheets'][k]);
}
core_loaded = true;
}
scriptLoader.load(settings['root'] + brush[1] + '.js');
}
}
if (core_loaded) {
scriptLoader.onFinish(function() {
SyntaxHighlighter.highlight();
});
}
}
In my main page HTML, I have the following which kicks the whole process off:
<script type="text/javascript" src="@rel@/_static/sh/loader.js"></script>
<script type="text/javascript">
$(function() {
var SyntaxHighlighterSettings = {
root: '@rel@/_static/sh/',
stylesheets: ['shThemeDefault.css'],
};
setupSyntaxHighlighter(SyntaxHighlighterSettings);
});
</script>
As you can see, due to the script loader, the code is relatively simple. As an alternative, my jQuery.Syntax highlighter dynamically loads all code by default, avoiding the need for additional loader scripts.
Comments
Please note, you can leave a comment that uses (limited) XHTML and Textile syntax.