Currently, in practice this is a singleton kept in bu_ScriptLoader.
If you just want to load and evaluate some random file (that is, the equivalent of a Perl 'do'), do not use this class. There are other methods for that, such as those of burst.runtime.AbstractRuntime . Instead this is intended for the equvalent of a Perl 'require'.
NOTE: the astute examiner will realize that in the simple case, all the complexity here accomplishes precisely nothing, because the single library file is already designed to contain the concatenated source script files in the correct order.
Overall, our approach is more similar to Perl 'use' than it is to Java 'import'. This stands to reason since ECMAScript, like Perl, is interpreted. A Perl 'use' will use some documented transformations on its argument to find a file, and will then load it at most once. Loading the file may have some side effects. In contrast, a Java 'import' is a namespace operation which applies only at compilation time.
Like both Java and Perl, we assume a certain convention to be followed which relates a namespace hierarchy with a source directory hierarchy, and like both, there are circumstances in which this convention can be violated without penalty.
Dependencies are declared and managed at the file level, not the namespace or object level. Following Perl, for purposes of dependency tracking, we introduce the concept of "module name" for identification. For convenience, a module name may be specified in any of these ways:
Module Name File 'Foo' Foo.js 'foo.Bar' foo/Bar.js 'foo/Bar.js' foo/Bar.js
The rules are as follows:
Scripts are loaded by a ScriptLoader instance which has this state:
Note that while in the future a single ScriptLoader may support multiple base urls to search through (ala Java ClassLoader and CLASSPATH), for now each ScriptLoader has just a single base url.
The set of scripts loaded is indexed by relative file name, so that irrelevant differences in module naming are ignored.
Because of the current restriction that each ScriptLoader has a different base url, in effect each instance represents a different "library", insofar as a "library" means a set of scripts available under a single directory, versus a single file.
There is a builtin instance of ScriptLoader, available from the static method burst.ScriptLoader.getScriptLoader, which is used for managing the scripts in the library it itself came from. It is in effect the bootstrap loader.
There are global convenience functions bu_require and bu_loaded which are used within the library to manage intra-library dependencies.
The scripts in a ScriptLoader which loaded (or not yet loaded, but indicated by a dependency) are instances of burst.Script.
At this time, we do not have support for automatically choosing a particular ScriptLoader instance based on namespace/package, nor for iterating through all ScriptLoader instances. If you want to have additional ScriptLoader instances besides the builtin bootstrap one, you have to create it yourself and keep a handle to it.
At this time, we require that a script that is loaded must explicitly call into ScriptLoader to indicate that it is loaded (we do not simply assume that because we invoked a load function that it succeeded).
There are several kinds of dependencies:
In ECMAScript, when a script is loaded, the runtime inseparably carries out both a parse and execute phase (where all statements in the file are run). Therefore, any functions which will be called (perhaps indirectly) at load time must already have been loaded. On the other hand, if a script B just defines a function which calls functionality in some other script A, but does not cause that function to be called at load time, then it doesn't matter whether script A has been loaded yet: it only matters that the script is ready later when the function is called.
An event dependency is one where something from the runtime must happen (versus just a dependency on some other script). For example, the internal widget manager may need to parse the DOM for inline constructors, after the DOM document has had its "onload" event. Another example event dependency is that after the configuration object is available (which may not be until an html "head" section is completed), a script may want to do some initialization.
To support these requirements, every Script instance is in one of these states:
'loading' initial state, learned from a module asking for load deps 'requested' initial state, learned from a dependency 'loaded' file has loaded, but not all call dependencies are 'loaded' 'callable' all call dependencies are 'loaded', but event dependencies haven't completed 'failed' a final state: a load operation failed, or some event handler threw an exception 'ready' a final state: after 'loaded', all event dependencies have completed
Note that if there is a loop in declared load dependencies, then a deadlock will ensue where neither of two scripts can enter the 'loadable' state, because they both have a load dependency on another script not in the 'loaded' state.
Note that load dependencies only require that the other script be in the 'loaded' state, not that they be in the 'ready' state. So it is still the case that a script author must carefully consider what happens at startup time. (Note that we do things this way so that script dependencies can be resolved entirely within a browser page "head" section if desired.)
When a script is loaded, it can register a handler for certain event dependencies. A script need not provide a handler object, nor define all methods in the handler object. This handler object may define any of these methods with one of these names:onCallDeps called on a script in 'loaded' state after its call deps are loaded, before moving to state 'callable' onConfig called when the global config object is ready onConfigDone called right after all 'onConfig' handlers have been called onDocumentLoad (DOM environment only) called after document 'onload' onDocumentLoadDone (DOM environment only) called after all 'onDocumentLoad' handlersThose methods are generally called in the order indicated above, if multiple handler methods exist.
When all of a script event handler's methods are called (with the exception of onDocumentLoad when there is no document), the script instance is put in the 'ready' state. When not in a browser environment, any "onDocumentLoad*" methods are never called, and have no bearing on a transition to 'ready'.
Except for the doubled event distinction (with and without "Done"), there is no application control over the order in which multiple Script instances subscribed to the same event will get executed.
Note that "onCallDeps" is different from the others, in that it is an event particular to the Script instance. The other events are global events which happen only once.
Note that the events are "queued" for delivery to every Script, even future Scripts, so that a all Script instances, regardless of when created, will have their onConfig* and onDocumentLoad* handler methods called (if any), regardless of whether the original event actually fired prior to their instantiation. Furthermore, a Script instance will not have these methods called until it enters the 'callable' state.
Note that at least in the case of onDocumentLoad, a script could register an event handler through other means (e.g. register for document onload directly using the DOM API). This ScriptLoader mechanism differs from other event notification mechanisms an author might choose in that:
Each ScriptLoader instance takes care of the event dependencies for its own Script instances.
In any directory "foo", a script file "foo/foo_first.js" will be loaded exactly once by a ScriptLoader prior to any other script being loaded from that same directory.
This is a convenience so that if there is some common code that must be run prior to any of the scripts in the directory, it can be done just in that one file, and the other scripts need not declare it as an explicit dependency.
This is also used in the library to ensure that runtime workarounds (such as supplying a definition for Array.splice) are available prior to any other script being loaded, without it having to worry about it.
That initialization script may itself have its own dependencies: when loaded, it may call back into the ScriptLoader, indicating other required modules.
Before firing onDocumentLoad, a ScriptLoader instance will sanity check that all its Script instances have reached the 'callable' state. If not, it will throw an exception.
An application can cause the same checking to happen at any time by calling checkScripts.
If for some reason the application has chosen to load the ScriptLoader functionality yet disable it (in global config), then the ScriptLoader will load scripts but will not call their event handlers.
If a script file load fails or an event handler throws an exception, an exception is thrown to the user library. Generally this should be considered fatal. If an application chooses to catch this exception, at this time we provide no guarantees about what will happen subsequently. It may for example be the case that all future calls to the ScriptLoader instance will also throw an exception.
Public Member Functions
|ScriptLoader (String init_url, Boolean is_base)|
|void||require (String modulename, Array load_deps)|
|Indicates that the specified modules in load_deps must be loaded now, because they must be available prior to the code about to evaluated (presumably because it is part of module modulename). |
|void||loaded (String modulename, Array call_deps, handler)|
|Indicates that the specified module has completed loading. |
|void||loadModule (String modulename, Function done_handler)|
|In modulename, replaces '.' with '/' and appends '.js'. |
|String||resolveScriptUrl (String rel_url)|
|Return the full url to use to fetch the specified top-level script file from the installation. |
|String||resolveImageUrl (String rel_url)|
|Return the full url to use to fetch the specified image file from the installation. |
|String||resolveHtmlUrl (String rel_url)|
|Return the full url to use to fetch the specified html file from the installation. |
Static Public Member Functions
|The bootstrap instance of ScriptLoader. |
If url is an absolute file or URI, then it is used as is for the base_url. If it is relative, then the base url is determined in an environment-specific way. In the case of a browser environment, the base url is determined by looking in the document object's script elements for the one that loaded a src ending the provided url.
Indicates that the specified module has completed loading.
Also lists what remaining modules must be loaded before any functions in this module can be called, and a handler for events.
In modulename, replaces '.' with '/' and appends '.js'.
Then it calls resolveScriptUrl, then bu_Runtime.readEval.
Called automatically for load and call dependencies. May also be called directly.
Indicates that the specified modules in load_deps must be loaded now, because they must be available prior to the code about to evaluated (presumably because it is part of module modulename).
This function will throw if some module in load_deps has not yet been loaded and the runtime can't do it synchronously.
Return the full url to use to fetch the specified html file from the installation.
Return the full url to use to fetch the specified image file from the installation.
Return the full url to use to fetch the specified top-level script file from the installation.