Permalink
Browse files

Initial import of soda project from ashes of JSAL.

  • Loading branch information...
0 parents commit 7bdef7713511828f98c7c550f38855803b45a13e Thomas Yandell committed Nov 26, 2009
10 Makefile
@@ -0,0 +1,10 @@
+# © Thomas Yandell 2009
+
+JSDOC = contrib/jsdoc-toolkit
+YUICOMP=contrib/yuicompressor-2.4.2.jar
+
+compress: src/soda.js
+ java -jar $(YUICOMP) -o src/soda.comp.js --type js -v src/soda.js
+
+#docs: src/testjs.js
+# java -Djsdoc.dir=$(JSDOC) -jar $(JSDOC)/app/js.jar $(JSDOC)/app/run.js -c=$(PWD)/etc/jsdoc.conf
109 README.txt
@@ -0,0 +1,109 @@
+Soda - Javascript Asynchronous Loader
+ by Thomas Yandell
+
+Version 1.2
+
+Description
+===========
+
+Implements a simple asyncronous loader and module pattern based on dynamic
+script tags (or load function for command line Javascript). This intended
+to be loaded before javascript libraries that use the module pattern, allowing
+those libraries to interoperate.
+
+Usage
+=====
+
+In you html file:
+
+<script type="text/javascript" src="soda.comp.js"></script>
+
+<script type="text/javascript">
+
+ // tell Soda where to find modules that start mylib
+ soda.lib('mylib', '../mylib/src');
+
+ // load mylib and mylib.blah
+ soda.load(['mylib', 'mylib.blah'], function () {
+ // ../mylib/src/mylib.js and ../mylib/src/mylib/blah.js are loaded here
+ });
+
+ // don't assume they are loaded here - the loader is async
+
+</script>
+
+In your module (Soda module pattern):
+
+soda.module({
+ name : 'mylib', // this must match the name used to load it
+ depends : ['myotherlib'], // dependencies are automatically loaded
+ onload : function () {
+ // dependencies are available here
+ }
+});
+
+API
+===
+
+Function: soda.lib
+------------------
+
+Tell Soda where to load modules within a particular namespace from.
+
+Arguments:
+ namespacePrefix - (string/array of strings) prefix(es) of namespaces to load.
+ urlPrefix - (string) prefix of the URL to load modules from.
+
+Function: soda.load
+-------------------
+
+Load a module/modules and run a function when done.
+
+Arguments:
+ module - (string/array of strings) module(s) to load.
+ callback - (function, optional) callback to run when the module(/modules) is loaded.
+
+Function: soda.module
+---------------------
+
+Create a new module with the Soda module pattern.
+
+Arguments:
+ opts - (object) options object containing:
+ name - (string, required) the module name (must correspond to the filename).
+ depends - (array of strings, optional) the modules that this module depends on.
+ onload - (function, required) called when the dependencies are loaded. This is
+ where the module implementation is put.
+
+Installation
+------------
+
+Copy the file soda.comp.js to a convenient location (can be inlined
+if you want to avoid a HTTP request).
+
+Library authors are encouraged to distribute Soda with their library.
+
+License
+-------
+
+The MIT License
+
+Copyright (c) 2008 Thomas Yandell
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
BIN contrib/yuicompressor-2.4.2.jar
Binary file not shown.
201 src/soda.js
@@ -0,0 +1,201 @@
+
+if (! this.soda) (function (main) {
+
+ /**
+ * Namespace: soda - Javascript Asynchronous Loader
+ *
+ * Implements a simple asyncronous loader and module pattern based on dynamic
+ * script tags (or load function for command line Javascript). This intended
+ * to be loaded before javascript libraries that use the module pattern, allowing
+ * those libraries to interoperate.
+ */
+ var soda = main.soda = {},
+
+ // array of arrays, each containimg namespace regex, url prefix and a namespace character count
+ inc = [],
+
+ // script elements and timeouts keyed by module name, for removal
+ scriptElements = soda.scriptElements = {},
+
+ // loaded Module objects keyed by name
+ modules = soda.modules = {},
+
+ // used to create id's for module load hooks
+ hookIdCounter = 0,
+
+ // hooks to be run when modules are loaded
+ // keyed on unique id, each value is array containing array of mods and callback
+ onLoadModules = soda.onLoadModules = {},
+
+ // <head> tag to add script elements to
+ head = main.document ? main.document.getElementsByTagName('head')[0] : false,
+
+ // called when a module isn't loaded after the timeout
+ ontimeout = soda.ontimeout = function (mod, url) {
+ if (console && console.log)
+ console.log("Soda: loading module " + mod + " timed out (url: '" + url + "')");
+ },
+
+ // number of seconds to allow for modules to load before debug
+ timeout = soda.timeout = 20,
+
+ // regex to match module names and namespace prefixes
+ nsRegex = /^\w+(?:\.\w+)*$/,
+
+ // private class: Module - internal representation of a module
+ Module = function (opts) {
+ this.name = opts.name;
+ this.depends = opts.depends;
+ this.onload = opts.onload;
+ this.ran = false;
+ var script = scriptElements[this.name];
+ if (script) {
+ main.clearTimeout(script[1]);
+ head.removeChild(script[0]);
+ delete scriptElements[this.name];
+ }
+ };
+
+ soda.version = 1.2;
+
+ // private method: Module.run - execute the implementation function, called when dependencies have loaded.
+ Module.prototype.run = function () {
+ this.ran = true;
+ this.onload.call(main);
+ callHooks();
+ };
+
+ // private function: callHooks - check if there are any callbacks ready to run and run them
+ function callHooks () {
+ var i, l, mods, name, hookId, ready, hook;
+ for (hookId in onLoadModules) {
+ ready = true;
+ mods = onLoadModules[hookId][0];
+ for (i = 0, l = mods.length; i < l; i++) {
+ if (! (name = mods[i])) continue;
+ if (modules[name] && modules[name].ran) {
+ mods.splice(i--, 1);
+ }
+ else ready = false;
+ }
+ if (ready) {
+ hook = onLoadModules[hookId][1];
+ delete onLoadModules[hookId];
+ main.setTimeout ?
+ main.setTimeout(hook, 0) :
+ hook.call(main);
+ }
+ }
+ }
+
+ /**
+ * Function: soda.lib
+ *
+ * Tell Soda where to load modules within a particular namespace from.
+ *
+ * Arguments:
+ * namespacePrefix - (string/array of strings) prefix(es) of namespaces to load.
+ * urlPrefix - (string) prefix of the URL to load modules from.
+ *
+ * Example:
+ * soda.lib(['myLib', 'myOtherLib'], 'http://mylib.com/1.0');
+ * soda.load('myLib'); // loads http://mylib.com/1.0/myLib.js
+ * soda.load('myLib.aNamespace'); // loads http://mylib.com/1.0/myLib/aNamespace.js
+ * soda.load('myOtherLib.anotherNamespace'); // loads http://mylib.com/1.0/myOtherLib/anotherNamespace.js
+ * soda.load('myLib2'); // throws unknown module error
+ */
+ soda.lib = function (nsPrefix, urlPrefix) {
+ if (typeof(nsPrefix) == 'array' || nsPrefix instanceof Array) {
+ for (var i = 0, l = nsPrefix.length; i < l; i++)
+ soda.lib(nsPrefix[i], urlPrefix);
+ }
+ else {
+ if (! nsRegex.test(nsPrefix))
+ throw "soda.lib: invalid namespace prefix '" + nsPrefix + "'";
+ inc[inc.length] = [
+ new RegExp('^' + nsPrefix.replace('.', '\\.') + '(?:\\.|$)'),
+ urlPrefix,
+ nsPrefix.length
+ ];
+ }
+ };
+
+ /**
+ * Function: soda.load
+ *
+ * Load a module/modules and run a function when done.
+ *
+ * Arguments:
+ * module - (string/array of strings) module(s) to load.
+ * callback - (function, optional) callback to run when the module(/modules) is loaded.
+ */
+ soda.load = function (module, callback) {
+ if (typeof(module) == 'array' || module instanceof Array) module = module.slice();
+ else module = [module];
+ var url, i, l, j, jl, mods = {}, name, urlBase, script, chars;
+ for (i = 0, l = module.length; i < l; i++) {
+ name = module[i];
+ if (modules[name] || scriptElements[name]) continue;
+ chars = 0;
+ for (j = 0, jl = inc.length; j < jl; j++) {
+ if (inc[j][0].test(name) && inc[j][2] > chars) {
+ urlBase = inc[j][1];
+ chars = inc[j][2];
+ }
+ }
+ if (! urlBase) throw "soda.load: no lib configured for '" + name + "'";
+ url = urlBase + '/' + String(name).replace(/\./g, '/') + '.js';
+ if (main.document && main.document.createElement && head) {
+ script = document.createElement('script');
+ script.setAttribute('type', 'text/javascript');
+ script.setAttribute('src', url);
+ head.appendChild(script);
+ scriptElements[name] = [
+ script,
+ soda.ontimeout ?
+ main.setTimeout(
+ function () {
+ if (soda.ontimeout) soda.ontimeout(name, url);
+ },
+ soda.timeout * 1000
+ ) : 0
+ ];
+ }
+ else if (main.load) {
+ main.load(url);
+ }
+ else {
+ throw 'Soda: cannot load, unsupported environment';
+ }
+
+ }
+ if (callback) {
+ onLoadModules[hookIdCounter++] = [module, callback];
+ callHooks();
+ }
+ };
+
+ /**
+ * Function: soda.module
+ *
+ * Create a new module with the Soda module pattern.
+ *
+ * Arguments:
+ * opts - (object) options object containing:
+ * name - (string, required) the module name (must correspond to the filename).
+ * depends - (array of strings, optional) the modules that this module depends on.
+ * onload - (function, required) called when the dependencies are loaded. This is
+ * where the module implementation is put.
+ */
+ soda.module = function (opts) {
+ var mod = new Module(opts);
+ modules[mod.name] = mod;
+ if (mod.depends && mod.depends.length) {
+ soda.load(mod.depends, function () { mod.run(); });
+ }
+ else {
+ mod.run();
+ }
+ };
+
+})(this);
34 src/soda/debug.js
@@ -0,0 +1,34 @@
+
+soda.module({
+ name : 'soda.debug',
+ onload : function () {
+ /**
+ * function soda.debug.status
+ *
+ * Get the loaded and pending modules as a text report.
+ */
+ soda.debug = {};
+ soda.debug.status = function () {
+ var res = '== ran\n', mod;
+ for (mod in soda.modules) {
+ if (mod.ran) continue;
+ res += '- ' + mod + '\n';
+ }
+ res += '\n== waiting for dependencies\n';
+ for (mod in soda.modules) {
+ if (! mod.ran) continue;
+ res += '- ' + mod + '\n';
+ }
+ res += '\n== not loaded\n';
+ for (mod in soda.scriptElements) {
+ res += '- ' + mod + ' from ' + soda.scriptElements[mod][0].src + '\n';
+ }
+ res += '\n== module hooks\n';
+ for (var ident in soda.onLoadModules) {
+ res += soda.onLoadModules[ident][0].join(',') + ' - ' + soda.onLoadModules[ident][1] + '\n';
+ }
+ return res;
+ };
+ }
+});
+
55 tests/index.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml" lang="en-GB" xml:lang="en-GB">
+ <head>
+ <title>Soda Test</title>
+ <meta http-equiv="Content-type" content="text/html; charset=UTF-8" />
+ <script type="text/javascript" src="../src/soda.js"></script>
+ <script type="text/javascript">
+ var results;
+ function addResult (res) {
+ var li = document.createElement('li');
+ li.innerHTML = res;
+ results.appendChild(li);
+ }
+ window.onload = function () {
+ results = document.getElementById('result');
+ soda.lib('test1', 'lib1');
+ soda.load('test1', function () {
+ addResult('3 (1 of 2). test1 loaded callback');
+ });
+ soda.lib(['test2', 'test3'], 'lib2and3');
+ soda.load(['test2', 'test3.mod1'], function () {
+ addResult('3 (1 of 2). test2, test3.mod1 loaded callback');
+ });
+ window.setTimeout(function () {
+ soda.lib('soda', '../src');
+ soda.load('soda.debug', function () {
+ document.getElementById('status').innerHTML = soda.debug.status();
+ });
+ }, 1000);
+ };
+
+ </script>
+ </head>
+ <body>
+
+ <h1>Soda Test</h1>
+
+ <p>
+ There should be 8 items below.
+ </p>
+
+ <ul id="result"></ul>
+
+ <p>
+ A status report is output here after a second.
+ </p>
+
+ <pre id="status">
+
+ </pre>
+
+ </body>
+</html>
9 tests/lib1/test1.js
@@ -0,0 +1,9 @@
+
+soda.module({
+ name : 'test1',
+ depends : ['test1.mod1'],
+ onload : function () {
+ addResult('2 (1 of 2). test1 implementation');
+ }
+});
+
8 tests/lib1/test1/mod1.js
@@ -0,0 +1,8 @@
+
+soda.module({
+ name : 'test1.mod1',
+ onload : function () {
+ addResult('1 (1 of 4). test1.mod1 implementation');
+ }
+});
+
10 tests/lib2and3/test2.js
@@ -0,0 +1,10 @@
+
+soda.module({
+ name : 'test2',
+ depends : ['test2.mod1', 'test2.mod2'],
+ onload : function () {
+ addResult('2 (1 of 2). test2 implementation');
+ }
+});
+
+
9 tests/lib2and3/test2/mod1.js
@@ -0,0 +1,9 @@
+
+soda.module({
+ name : 'test2.mod1',
+ onload : function () {
+ addResult('1 (1 of 4). test2.mod1 implementation');
+ }
+});
+
+
9 tests/lib2and3/test2/mod2.js
@@ -0,0 +1,9 @@
+
+soda.module({
+ name : 'test2.mod2',
+ onload : function () {
+ addResult('1 (1 of 4). test2.mod2 implementation');
+ }
+});
+
+
8 tests/lib2and3/test3/mod1.js
@@ -0,0 +1,8 @@
+
+soda.module({
+ name : 'test3.mod1',
+ onload : function () {
+ addResult('1 (1 of 4). test3.mod1 implementation');
+ }
+});
+
31 tests/test.js
@@ -0,0 +1,31 @@
+
+load('../src/soda.js');
+
+function addResult (res) {
+ print(res);
+}
+
+print("Soda Test");
+print("=========");
+print("");
+print("There should be 8 items below.")
+print("");
+
+soda.lib('test1', 'lib1');
+ soda.load('test1', function () {
+ addResult('3 (1 of 2). test1 loaded callback');
+});
+soda.lib(['test2', 'test3'], 'lib2and3');
+soda.load(['test2', 'test3.mod1'], function () {
+ addResult('3 (1 of 2). test2, test3.mod1 loaded callback');
+});
+
+print("");
+print("A status report should be output here.");
+print("");
+
+soda.lib('soda', '../src');
+soda.load('soda.debug', function () {
+ print(soda.debug.status() + '\n');
+});
+

0 comments on commit 7bdef77

Please sign in to comment.