/*jslint plusplus: true, white: true, indent: 2, maxlen: 90, continue: true, unparam: true */
/*global window */
/**
* @module $wk$
* @author Wolfgang Kowarschick
* @copyright 2012-2013, Wolfgang Kowarschick
* <br/>
* Redistribution and use in source and binary forms, with or without
* modification, are permitted under the terms of the
* Creative Commons License Attribution-NonCommercial-ShareAlike 3.0 Unported
* (CC BY-NC-SA 3.0: http://creativecommons.org/licenses/by-nc-sa/3.0/).
*/
////////////////////////////////////////////////////////////////////////////////
// Extensions of the top level object Object
////////////////////////////////////////////////////////////////////////////////
// Generalized version of Object.getOwnPropertyDescriptor
// @see http://wiki.ecmascript.org/doku.php?id=harmony:extended_object_api
if (!Object.getPropertyDescriptor)
{ Object.getPropertyDescriptor =
function(p_object, p_property)
{ "use strict";
var l_pd = Object.getOwnPropertyDescriptor(p_object, p_property),
l_proto = Object.getPrototypeOf(p_object);
while (l_pd === undefined && l_proto !== null)
{ l_pd = Object.getOwnPropertyDescriptor(l_proto, p_property);
l_proto = Object.getPrototypeOf(l_proto);
}
return l_pd;
};
}
////////////////////////////////////////////////////////////////////////////////
// The $wk$ object
////////////////////////////////////////////////////////////////////////////////
/**
* The sandbox pattern: When this function is evaluated,
* it first loads all modules stated in <code>p_modules</code>
* and then calls the callback function passed as last argument:
* <pre>function(p_wk, p_env_1, ... p_env_n)
* { // code running in a sandbox
* }</pre>
*
* @see Stoyan Stefanov, JavaScript Patterns. O'Reilly, 2010
*
* @function
* @global
* @name $wk$
*
* @param {String[]|Array} [p_modules]
* A module name or a list of module names. A module name is a string
* <conde>"p1.p2.p3.M"</code>, consiting of zero ore more package/subpackage
* names <code>p_i</code> and the module name <code>M</code> proper.
* If <code>M === "*"</code>, all modules within the package are loaded.
* If <code>M === "**"</code>, all modules within the package and all
* subpackages, subsubpackages etc. are loaded.
* @param {Object} [p_environment_i]
* Zero or more environments objects, where the content of the modules are
* stored.
* @param {Function} [p_sandbox]
* The sandbox, a callback function:
* <code>function(p_wk, p_env_1, ... p_env_n){ ...}</code>
* <code>pwk</code> is an instance of <code>$wk$</code>. The environment
* object contain the <code>p_modules</code> loaded. If there are more
* modules stated than environment objects, the last environment object
* contains the surplus modules. If no environment objects are passed,
* all modules are stored within <code>p_wk</code>.
* @param {String|Array|Object} p_annotations
* Annotations that control the behaviour of the code executed within the
* sandbox. Plaes note: An annotation object may only be stated, if
* <code>p_sandbox</code> has been stated. Otherwise an object is
* interpreted as environment object.
*/
/*jslint browser: true*/
var $wk$;
(function(p_root_wk)
{ "use strict";
var i, n,
l_class_props = ["classFullName", "classID", "classPath"],
l_class_prop_values,
l_prototype,
l_async,
l_head = document.getElementsByTagName("head")[0],
l_wk_modules = {},
l_annotations = { wk: {}, cls: {}, prop: {}, meth: {}},
l_wk_annotations = l_annotations.wk,
l_uid = -1,
l_library_name,
l_version,
ANNOTATION_WK = "wk",
PACKAGE_INIT_FILE = "/package.json",
PACKAGE_LOADING = 0, PACKAGE_INITIALIZED = 1, PACKAGE_NO_INIT_FILE= 2,
JS = "js", JSON$ = "json", XML = "xml", CSS = "css",
MODULE_NOT_LOADED = 0, MODULE_LOADING = 1, MODULE_LOADED = 2,
EMPTY = {};
function f_error_getter()
{ throw new Error("Getter method undefined"); }
function f_error_setter()
{ throw new Error("Setter method undefined"); }
function f_annotation_add(p_type, p_name, p_anno)
{ Object.defineProperty(l_annotations[p_type], p_name,
{ value: p_anno, writable : false }
);
}
function f_annotations_get(p_type) // TBD: use it
{ return l_annotations[p_type]; }
f_annotation_add
(ANNOTATION_WK, "@synchronous",
function(p_value) { this["@synchronous"] = p_value; }
);
f_annotation_add
(ANNOTATION_WK, "@asynchronous",
function(p_value) { this["@synchronous"] = !p_value; }
);
/**
* The $root$ object of the application.
*
* @name $wk$.$root$
*/
/**
* The name of the library: WKlib
*
* @member {String} $name$
* @readonly
* @name $wk$.$name$
*/
l_library_name = "WKlib";
/**
* The version of the library: 001.00
*
* @member {Object} $version$
* @readonly
* @property {String} $version$.version The version number.
* @property {String} $version$.subversion The subversion number.
* @property {String} $version$.name The version name.
* @name $wk$.$version$
*/
l_version = {version: "001", subversion: "00", name: "v001.00"};
/**
* An object created by calling <code>$new$(Class, [1, 2, 3])</code>
* is identical to an object created with <code>new Class(1, 2, 3)</code>.
*
* @method
* @name $wk$.$new$
*
* @param {Function} p_constructor A function to be used as constructor.
* @param {Array} p_arguments An array of arguments to be passed
* to the constructor
* @returns {Object} The newly constructed object.
* @see http://stackoverflow.com/questions/1606797/use-of-apply-with-new-operator-is-this-possible
*/
function f_new(p_constructor, p_arguments)
{ function F_C()
{ return p_constructor.apply(this, p_arguments); }
F_C.prototype = p_constructor.prototype;
return new F_C();
}
/**
* <code>$wk$.WKcAsync</code> is a class the objects of which
* serialize asynchronous tasks.
* <p>
* Each object of this class defines a queue and a pool where
* asynchrounous tasks can be put.
* </p>
* <p>
* By means of the method <code>push</code> a asynchronous task is
* enqueue. It is executed as soon as all tasks that have been enqueued
* earlier have finished.
* </p>
* <p>
* By means of the method <code>add</code> a named asynchronous task is
* added to the task pool. Additionally, the ids of all tasks, the
* new task depends on, have to be stated. The task is executed
* as soon as all tasks it depends on have finished.
* </p>
*
* @class
* @name $wk$.WKcAysnc
* @returns {Object} A new async object.
*/
function WKcAsync()
{ if (!(this instanceof WKcAsync))
{ return f_new(WKcAsync, arguments); }
var self = this,
l_queue = [],
l_running_queue = false,
l_pool = {};
/**
* Enqueues new asynchronous tasks.
* That task is executed as soons as all tasks that have been
* enqueue before have finished.
*
* @method
* @name $wk$.WKcAysnc#push
*
* @param {Object} [p_env] The environment which can be accessed
* via <code>this</code>.
* @param {Function} p_doit The function that performs a task
* asynchronously
* @param {Object[]} p_args The arguments passed to <code>p_doit</code>
* when executed. Every function for which
* the property <code>NO_CALLBACK</code>
* has either not been set or is not equal to
* <code>true</code> is considered
* to be a callback function.
* <code>p_doit</code> must call at least one
* callback function. If no function has been
* stated, an callback function is created
* automatically and passed as last argument
* to <code>doit</code>.
*
* @returns {Object} <code>this</code>
*/
function f_next_queue()
{ var l_f_next;
if (l_queue.length > 0)
{ l_running_queue = true;
l_f_next = l_queue.shift();
l_f_next[1].apply(l_f_next[0], l_f_next[2]);
}
else
{ l_running_queue = false; }
}
this.push =
function()
{ var l_args = Array.prototype.slice.call(arguments),
l_arg = l_args[0],
p_env = (l_arg instanceof Function)?undefined:l_args.shift(),
p_doit = l_args.shift(),
i, n,
l_args_new = [],
l_cb_exists = false;
function f_call()
{ this.apply(p_env, arguments);
f_next_queue();
}
for (i = 0, n = l_args.length; i < n; i++)
{ l_arg = l_args[i];
if (l_arg instanceof Function && !(l_arg.NO_CALLBACK))
{ l_cb_exists = true;
l_args_new.push(f_call.bind(l_arg));
}
else
{ l_args_new.push(l_arg); }
}
if (!l_cb_exists)
{ l_args_new.push(f_next_queue); }
l_queue.push([p_env, p_doit, l_args_new]);
if (!l_running_queue)
{ f_next_queue(); }
return self;
};
/**
* Adds new asynchronous tasks to the task pool.
* That task is executed as soons as all tasks it depends on have finished.
*
* @method
* @name $wk$.WKcAysnc#add
*
* @param {String} p_id The id of the task.
* It is not possible to add a second
* task with the same id as an elder task.
* In that case an error is dispatched;
* @param {String[]|Array}
* [p_ids] The ids of those tasks, the new task
* depends on.
* @param {Object} [p_env] The environment which can be accessed
* via <code>this</code>.
* @param {Function} p_doit The function that performs a task
* asynchronously
* @param {Object[]} p_args The arguments passed to <code>p_doit</code>
* when executed. Every function for which
* the property <code>NO_CALLBACK</code>
* has either not been set or is not equal to
* <code>true</code> is considered
* to be a callback function.
* <code>p_doit</code> must call at least one
* callback function. If no function has been
* stated, an callback function is created
* automatically and passed as last argument
* to <code>doit</code>.
*
* @returns {Object} <code>this</code>
*/
this.add =
function()
{ var l_args = Array.prototype.slice.call(arguments),
p_id = l_args[0],
p_env,
p_doit,
i, n,
state = 0,
l_arg,
l_args_new = [],
l_task = { state: 0, waiting: [] },
l_task_pre,
l_task_pre_nr = 1,
l_cb_exists = false;
if (l_pool[p_id])
{ throw new Error("WKcAsync: Task "+p_id+" does already exist!"); }
l_pool[p_id] = l_task;
function f_next()
{ var l_w = l_task.waiting, n = l_w.length;
l_task.state = 1;
while (n>0)
{ (l_w.shift())(); }
}
function f_call()
{ this.apply(p_env, arguments);
f_next();
}
function f_doit()
{ l_task_pre_nr--;
if (l_task_pre_nr === 0)
{ p_doit.apply(p_env, l_args_new); }
}
for (i = 1, n = l_args.length; i < n; i++)
{ l_arg = l_args[i];
if (state === 0)
{ if (l_arg.constructor === String)
{ l_task_pre = l_pool[l_arg];
if (!l_task_pre)
{ throw new Error("WKcAsync: Task "+l_arg+" doesn't exist!"); }
if ( l_task_pre.state === 0)
{ l_task_pre.waiting.push(f_doit);
l_task_pre_nr++;
}
continue;
}
else
{ state = 1; }
}
if (state === 1)
{ if (l_arg instanceof Function)
{ p_doit = l_arg; }
else
{ p_env = l_arg;
p_doit = l_args[++i];
}
state = 2;
}
else // state === 2
{ if (l_arg instanceof Function && !(l_arg.NO_CALLBACK))
{ l_cb_exists = true;
l_args_new.push(f_call.bind(p_env));
}
else
{ l_args_new.push(l_arg); }
}
}
if (!l_cb_exists)
{ l_args_new.push(f_next); }
f_doit();
return self;
};
}
WKcAsync.noCalllback =
function(p_function)
{ p_function.NO_CALLBACK = true;
return p_function;
};
l_prototype = WKcAsync.prototype;
l_prototype.constructor = WKcAsync;
l_prototype.noCalllback = WKcAsync.noCalllback;
l_class_prop_values = ["$wk$.WKcAsync", "WKcAsync", "$wk$"];
WKcAsync.fullName = l_class_prop_values[0];
WKcAsync.id = l_class_prop_values[1];
WKcAsync.path = l_class_prop_values[2];
for (i=0, n=l_class_props.length; i < n; i++)
{ Object.defineProperty(l_prototype,
l_class_props[i],
{ configurable: false,
enumerable: false,
value: l_class_prop_values[i],
writable: false
}
);
}
l_async = new WKcAsync();
/**
* <code>$C$</code> is an object that contains constant values.
*
* @method
* @name $wk$.$C$
*
* @param {String} p_name The name of the constant to be stored.
* @param {Object} p_value The value of the constant to be stored.
* @param {Boolean} [p_getter = false]
* If <code>p_value</code> contains a
* (parameterless) function and
* <code>p_getter===true</code> then for
* <code>p_name</code> a getter method is
* stored that calls that function within
* the scope of <code>$C$</code>.
*/
function $C$(p_name, p_value, p_getter)
{ Object.defineProperty($C$, p_name,
p_getter && p_value instanceof Function
? { configurable: false,
enumerable: true,
get: function()
{ return p_value.call($C$); },
set: f_error_setter
}
: { configurable: false,
enumerable: true,
value: p_value || null,
writable: false
}
);
}
/**
* <code>$E$</code> is an object that contains event types.
*
* @method
* @name $wk$.$E$
*
* @param {String} p_event_type
* The event type to be stored in <code>$E$</code>.
*/
function $E$(p_event_type)
{ l_uid--;
Object.defineProperty($E$, p_event_type,
{ configurable: false,
enumerable: true,
value: l_uid--,
writable: false
}
);
}
/**
* Creates a chain of nested objects.
* <p>
* For instance: The following function call
* <pre> $wk$.pkg("ldvcs.view");</pre>
* results in the creation of two objects (if they do not already exist):
* <pre>
* ldvcs
* ldvcs.view</pre>
* </p>
*
* @method
* @name $wk$.pkg
*
* @param {String} p_name A package name, i.e. one or more name literals
* separated by dots.
* @param {String} [p_create = true]
* Indicates, whether or not to create the package,
* if it does not exists
* @returns {Object|null} The last package object of the package
* object chain or <code>null</code>
* if the package does not exist.
*/
function f_pkg(p_name, p_create)
{ p_create =
(p_create === undefined || p_create === null) ? true : p_create;
var l_subpackages = p_name.split("."), // all literal names
l_root_name = l_subpackages[0],
l_name,
l_subpackage_object,
l_modules = l_wk_modules[l_root_name] || p_root_wk,
l_module_new,
l_root_package = l_modules["*p"] || p_root_wk,
l_package_object = l_root_package.root || p_root_wk,
i, n;
if (!p_create && !l_modules)
{ return false; }
if (p_name === "")
{ return p_root_wk; }
for (i = 0, n = l_subpackages.length; i < n; i++) // for each literal name
{ l_name = l_subpackages[i]; // the current literal name
// Fetch the associated package object.
l_subpackage_object = l_package_object[l_name];
// If it does not exist:
// create it or, if p_create === false, return false.
if (!l_subpackage_object)
{ if (p_create)
{ l_package_object[l_name] = l_subpackage_object = {};
if (i>0)
{ l_module_new = l_modules[l_name];
if (!l_module_new)
{ l_modules[l_name] = l_module_new = {}; }
l_modules = l_module_new;
}
}
else
{ return false; }
}
// Set the current subpackage object to be the new package object,
// for which the next subpackage object hast to be created.
l_package_object = l_subpackage_object;
}
return l_package_object;
}
/**
* An extended version of <code>Object.getOwnPropertyDescriptor</code>.
* @method
* @name $wk$.getOwnPropertyDescriptor
*
* @param {String} p_object The object a property descriptor of which
* is either to be read or to be defined.
* @param {String} p_property The property of <code>p_object</code>
* which is to be accessed.
* @returns {Object} The property descriptor of
* <code>p_object.p_property</code>, if defined.
*/
function f_get_own_property_descriptor(p_object, p_property)
{ var l_pd_wk,
l_pd = Object.getOwnPropertyDescriptor(p_object, p_property);
if (l_pd && p_object["$*!"] && p_object["$*!"])
{ l_pd_wk = p_object["$*!"][p_property];
if (l_pd_wk && l_pd_wk.overridable)
{ l_pd.overridable = l_pd_wk.overridable; }
}
return l_pd;
}
/**
* An extended version of <code>Object.getPropertyDescriptor</code>.
* @method
* @name $wk$.getPropertyDescriptor
*
* @param {String} p_object The object a property descriptor of which
* is either to be read or to be defined.
* @param {String} p_property The property of <code>p_object</code>
* which is to be accessed.
* @returns {Object} The property descriptor of
* <code>p_object.p_property</code>, if defined.
*/
function f_get_property_descriptor(p_object, p_property)
{ var l_pd = f_get_own_property_descriptor(p_object, p_property),
l_proto = Object.getPrototypeOf(p_object);
while (l_pd === undefined && l_proto !== null)
{ l_pd = f_get_own_property_descriptor(l_proto, p_property);
l_proto = Object.getPrototypeOf(l_proto);
}
return l_pd;
}
/**
* An extended version of <code>Object.defineProperty</code>.
* <p>
* The property descriptor set via <code>p_descriptor</code> or returned
* by <code>$wk$.setProperty</code> may contain several attributes:
* <dl>
* <dt><code>configurable</code>
* (<code>boolean</code>, default: <code>false</code>)</dt>
* <dd>see <code>Object.defineProperty</code> or
* <code>Object.getOwnPropertyDescriptor</code></dd>
* <dt><code>enumerable</code>
* (<code>boolean</code>, default: <code>false</code>)</dt>
* <dd>see <code>Object.defineProperty</code> or
* <code>Object.getOwnPropertyDescriptor</code></dd>
* <dt><code>value</code>
* (any JavaScript value, default: <code>undefined</code>)</dt>
* <dd>see <code>Object.defineProperty</code> or
* <code>Object.getOwnPropertyDescriptor</code></dd>
* <dt><code>writable</code>
* (<code>boolean</code>, default: <code>true</code>)</dt>
* <dd>see <code>Object.defineProperty</code> or
* <code>Object.getOwnPropertyDescriptor</code></dd>
* <dt><code>get</code>
* (<code>Function</code>, default: <code>undefined</code>)</dt>
* <dd>see <code>Object.defineProperty</code> or
* <code>Object.getOwnPropertyDescriptor</code></dd>
* <dt><code>set</code>
* (<code>Function</code>, default: <code>undefined</code>)</dt>
* <dd>see <code>Object.defineProperty</code> or
* <code>Object.getOwnPropertyDescriptor</code></dd>
* <dt><code>overridable</code>
* (<code>boolean</code>, default: <code>false</code>)</dt>
* <dd>Indicates whether the property value may be overriden by
* <code>$wk$.mixin</code></dd>
* </dl>
* If a setter or getter function has been defined but not both,
* then an error is thrown whenever the undefined method is tried
* to be invoked.
* </p>
* @method
* @name $wk$.setProperty
*
* @param {String} p_object The object a property descriptor of which
* is either to be read or to be defined.
* @param {String} p_property The property of <code>p_object</code>
* which is to be accessed.
* @param {Object} p_descriptor The descriptor to be asigned to or to be
* mixed into <code>p_object.p_property</code>
* @param {Object} [p_mixin = false]
* If <code>false</code> an existing desctiptor
* is overridden, otherwise the new descriptor
* is mixed into the old one.
*
* @returns {Object} <code>this</code>
*/
function f_set_property (p_object, p_property, p_descriptor, p_mixin)
{ var l_default = p_mixin
? f_get_own_property_descriptor(p_object, p_property)
|| EMPTY
: EMPTY,
l_pdesc = { configurable: l_default.configurable ||
p_descriptor.configurable || false,
enumerable: l_default.configurable ||
p_descriptor.enumerable || true
};
if (l_default.get === f_error_getter)
{ delete l_default.get; }
if (l_default.set === f_error_setter)
{ delete l_default.set; }
if (p_descriptor.value)
{ l_pdesc.value = l_default.value || p_descriptor.value;
l_pdesc.writable = l_default.writable || p_descriptor.writable || true;
}
else if (p_descriptor.get)
{ l_pdesc.get = l_default.get || p_descriptor.get;
l_pdesc.set = l_default.set || p_descriptor.set || f_error_setter;
}
else if (p_descriptor.set)
{ l_pdesc.get = l_default.get || f_error_getter;
l_pdesc.set = l_default.set || p_descriptor.set;
}
else
{ l_pdesc.value = l_default.value || undefined;
l_pdesc.writable = l_default.writable || p_descriptor.writable || true;
}
Object.defineProperty(p_object, p_property, l_pdesc);
if (p_descriptor.overridable)
{ l_pdesc = p_object["$*!"];
if (!l_pdesc)
{ Object.defineProperty(p_object, "$*!",
{ enumerable: false,
configurable: false,
writable: false,
value: {}
}
);
l_pdesc = p_object["$*!"];
}
if (!l_pdesc[p_property])
{ l_pdesc[p_property] = { overridable: p_descriptor.overridable }; }
else
{ l_pdesc[p_property].overridable = p_descriptor.overridable; }
}
}
/**
* Recursively mixes properties of a source object into a target object.
* Overridable properties are overridden, others not.
*
* @method
* @name $wk$.mixin
*
* @param {Object|String} p_target An arbitray object.
* @param {Object|String} p_source
* Another arbitray object (or a path name of an object)
* the properties of which are to be recursively mixed into
* <code>p_target</code>.
* @param {Function} p_filter
* A function code>function(p_source [,p_target])</code>
* that filters <code>p_source</code> before mixing it into
* <code>p_target</code>.
*/
function f_mixin(p_source, p_target, p_filter)
{ var l_key = null,
l_desc_target,
l_desc_source,
l_target,
l_value;
p_source = (p_source && p_source.constructor === String)
? f_pkg(p_source, false)
: p_source;
p_target = (p_target.constructor === String)
? f_pkg(p_target, false)
: p_target;
if (p_filter)
{ p_source = p_filter(p_source, p_target); }
if (p_source === p_target)
{ return; }
for (l_key in p_source)
{ if (p_source.hasOwnProperty(l_key))
{ if (p_target.hasOwnProperty(l_key))
{ l_desc_target = f_get_own_property_descriptor(p_target, l_key);
if ( (l_desc_target.get || l_desc_target.set)
&& !(l_desc_target.get && l_desc_target.set)
)
{ l_desc_source = f_get_own_property_descriptor(p_source, l_key);
if (!l_desc_target.get)
{ l_desc_target.get = l_desc_source.get; }
if (!l_desc_target.set)
{ l_desc_target.set = l_desc_source.set; }
f_set_property(p_target, l_key, l_desc_target, true);
}
else
{ l_target = p_target[l_key];
if (l_target instanceof Object && !(l_target instanceof Function))
{ f_mixin(p_source[l_key], l_target); }
}
}
else
{ l_desc_source = f_get_own_property_descriptor(p_source, l_key);
l_value = l_desc_source.value;
if (l_value instanceof Function && l_value.$original$)
{ l_desc_source.value = l_value.$original$; }
else
{ if ( (l_desc_source.get || l_desc_source.set)
&& !(l_desc_source.get && l_desc_source.set)
)
{ l_desc_target =
Object.getPropertyDescriptor(Object.getPrototypeOf(p_target),
l_key
); //TBD
if (l_desc_target)
{ if (!l_desc_source.get)
{ l_desc_source.get = l_desc_target.get; }
if (!l_desc_source.set)
{ l_desc_source.set = l_desc_target.set; }
}
}
}
Object.defineProperty(p_target, l_key, l_desc_source); //TBD
}
}
}
}
/**
* Creates root packages.
*
* @method
* @name $wk$.rootPackage
*
* @param {Object} p_roots
* An object containing one or many descriptors of root packages.
* @param {Object} p_roots.NAME
* Each attribute of <code>p_root</code> describes the root package
* named <code>NAME</code>.
* @param {Object} [p_roots.NAME.root = <system root object>]
* The top level object where the package modules
* can be found.
* @param {Object} [p_roots.NAME.url]
* The top level URL where the source files of the package modules
* can be found. If no url is passed, the modules of that
* package cannot be loaded but must be created directly.
* @param {Function} [p_roots.NAME.packagePath = ppath]
* A function that maps a package name to a path name that is appended
* to <code>p_root.NAME.url</code>. The default function
* <code>ppath</code> deletes all <code>$</code>-symbols from
* the name, replaces dots (<code>"."</code>) by slashes
* (<code>"/"</code>).
* @param {String} [p_roots.NAME.packageIndexFile = "index.json"]
* The name of the json file, where the list of al
* @param {Function} [p_roots.NAME.moduleFile = mfile]
* A function <code>f(p_name, p_type)</code> that maps a module name
* to a file name. The default function <code>mfile</code>
* applies <code>ppath</code> (see above) to <code>p_name</code>
* and then adds <code>"." + p_type</code> to the path name.
*/
function f_root_pkg(p_roots)
{ var l_root_name = null,
l_root,
l_root_2,
l_root_default,
l_module_new;
function f_ppath(p_name)
{ return p_name.replace(/\$/g, "").replace(/\./g, "/"); }
function f_mfile(p_name, p_extension)
{ return f_ppath(p_name) + "." + p_extension; }
l_root_default
= { packagePath: f_ppath,
moduleFile: f_mfile,
loaders:
{ // Should I try the following solution?
// http://stackoverflow.com/questions/7718935/load-scripts-asynchronously
js:
function(p_wk, p_callback)
{ // TBD: node.js
var l_script = document.createElement("script");
l_script.type = "text/javascript";
l_script.defer = true;
// l_script.async = false;
l_script.src = this.url;
l_script.onload = p_callback();
l_script.onerror =
function()
{ throw new Error(l_script.src + " not found"); };
l_head.appendChild(l_script);
},
json:
function(p_wk, p_callback)
{ var l_xhr = new XMLHttpRequest(),
self = this,
l_module = this.module,
l_filter = this.filter,
l_sync = p_wk["@synchronous"],
f_onload = function()
{ var l_json = JSON.parse(this.response);
if (!l_module && !l_filter)
{ self.module = l_json; }
else
{ if (!l_module)
{ l_module = self.module = {}; }
f_mixin(l_json, l_module, l_filter);
}
p_callback();
},
f_onerror = function()
{ throw new Error(l_module.url + " not found"); };
l_xhr.overrideMimeType("application/json");
l_xhr.open("GET", this.url, !l_sync);
if (!l_sync)
{ l_xhr.onload = f_onload;
l_xhr.onerror = f_onerror;
}
l_xhr.send();
if (l_sync)
{ if (l_xhr.status === 200)
{ f_onload.call({ response: l_xhr.responseText}); }
else
{ f_onerror(); }
}
},
css:
function(p_module, p_callback)
{ throw new Error("The CSS loader has not been implemented jet!"); },
xml:
function(p_module, p_callback)
{ throw new Error("The CSS loader has not been implemented jet!"); }
}
};
for (l_root_name in p_roots)
{ if (p_roots.hasOwnProperty(l_root_name))
{ l_root = p_roots[l_root_name];
f_mixin(l_root_default, l_root);
//l_root.url = (l_root.url || "");
if (l_root.root)
{ l_root_2 = (l_root.root)[l_root_name];
if (!l_root_2)
{ (l_root.root)[l_root_name] = { "$root$" : l_root.root }; }
else
{ l_root_2.$root$ = l_root_2.$root$ || l_root.root; }
}
else
{ l_root.root = {}; }
l_module_new = l_wk_modules[l_root_name] = { "*p": l_root };
/*--*/Object.defineProperty(l_module_new, "*p", { enumerable : false });
}
}
}
/**
* Adds one ore more modules to the module list of <code>$wk$</code>.
* Those modules can be loaded and added to
* <code>$wk$</code> sandbox environments lateron.
*
* @method
* @name $wk$.moduleAdd
*
* @param {Array} arguments
* Default parameter
* @param {String} arguments[i].name
* The unique name of the module (including the
* name of the module package).
* @param {String} [arguments[i].type]
* The type of the module (<code>$wk$.MODULE_TYPE</code>):
* <code>js</code>, <code>css</code>, <code>json</code>,
* <code>xml</code>, ...
* @param {String} [arguments[i].url]
* The URL, where the definition of the module can be
* found. Usually computed automatically
* (determined by the package root, package name ...)
* @param {Function} [arguments[i].loader]
* A function <code>function(p_callback)</code>
* to load the module (<code>this</code>)
* from <code>url</code>. Finally the
* callback function must be invoked.
* For <code>js</code>, <code>css</code>,
* <code>json</code> and <code>xml</code>
* there exists default loaders. (TBD: css, xml)
* @param {Object} [arguments[i].module]
* The module itself. If it is not stated explicitly, it
* is loaded, when the module is accessed for the
* first time.
* @param {Function} [arguments[i].init]
* A function <code>function(p_callback)</code>
* that is called, when the module has been loaded.
* The callback function passed <strong>must</strong>
* be called, when the module has been initialized.
* @param {Array} [arguments[i].required]
* A list of module that must be loaded before the
* <code>p_module</code> can be loaded.
*/
function f_module_add()
{ var i, j, n,
l_source,
l_name,
l_name_array,
l_length,
l_modules, l_module_new,
l_type,
l_root_package,
l_loaders,
l_package_name = "",
l_package_url = "",
l_target,
l_callbacks,
f_mfile,
l_key = null;
for (i = 0, n = arguments.length; i < n; i++)
{ l_source = arguments[i];
l_name_array = l_source.name.split(".");
l_length = l_name_array.length-1;
l_modules = l_wk_modules;
if (l_length !== 0)
{ l_name = l_name_array[0];
l_modules = l_modules[l_name];
if (!l_modules)
{ throw new Error("Root package " + l_name + " does not exist!");}
l_root_package = l_modules["*p"];
f_mfile = l_root_package.moduleFile;
l_loaders = l_root_package.loaders;
l_package_name = l_name;
l_package_url = l_modules["*p"].url;
}
for (j = 1; j < l_length; j++)
{ l_name = l_name_array[j];
l_module_new = l_modules[l_name];
l_package_name += "." + l_name;
if (!l_module_new)
{ l_modules[l_name] = l_module_new = { "*p": { name: l_package_name } };
Object.defineProperty(l_module_new, "*p", { enumerable : false });
}
l_modules = l_module_new;
}
l_name = l_name_array[l_length];
l_target = l_modules[l_name];
l_source.required = l_source.required || [];
l_type = l_source.type = l_source.type || JS;
if (l_length !== 0)
{ l_source.url = l_source.url
|| f_mfile(l_package_name + "." + l_name, l_type);
if (l_package_url)
{ l_source.url = l_package_url + "/" + l_source.url; }
l_source.loader = l_source.loader || l_loaders[l_type];
if (!l_source.loader)
{ throw new Error( "Loader for module '" + l_name + " is missing!");}
}
if (!l_target)
{ l_modules[l_name] = l_target = l_source; }
else
{ for (l_key in l_source)
{ if (l_source.hasOwnProperty(l_key))
{ l_target[l_key] = l_source[l_key]; }
}
}
l_target.state = l_target.module ? MODULE_LOADED : MODULE_NOT_LOADED;
if (l_target.module)
{ l_callbacks = l_target.$cb$;
if (l_callbacks)
{ while (l_callbacks.length > 0)
{ (l_callbacks.shift())(); }
}
}
}
}
/**
* Initializes a root package (which must have been created by
* <code>$wk$.rootPackage</code> before) by adding information about
* <strong>all</strong> modules and subpackages that are
* directly or indirectly members of that package.
*
* TBD: Description of the syntax of <code>p_init</code>.
*
* @method
* @name $wk$.rootPackageInit
*
* @param {String} p_root_name
* The name of the root package.
* @param {JSON} [p_init]
* The initialization data describing the modules to be added.
* <code>p_init</code> is an object each property of which describes
* a module or a subpackage that is to be loaded. If no initialization
* object is stated, it is tried to load the JSON file
* <code>package.json</code> from the URL where the root
* package is stored. If this file exists, its content is used
* to initialize the root package.
*/
function f_root_pkg_init_aux(p_path, p_init)
{ var l_key = null, l_prop, l_type;
for (l_key in p_init)
{ if (p_init.hasOwnProperty(l_key))
{ l_prop = p_init[l_key];
l_type = l_prop.type;
if ( l_type // i.e. l_prop describes a module but no package
|| (!l_type && l_key.match(/^[A-Z]/))
)
{ l_prop.name = l_prop.name || p_path + l_key;
f_module_add(l_prop);
}
else
{ f_root_pkg_init_aux(p_path + l_key + ".", l_prop); }
}
}
}
function f_root_pkg_init(p_root_name, p_init)
{ var l_root, l_root_pkg_desc, l_root_pkg_url, l_xhr;
if (p_init)
{ f_root_pkg_init_aux(p_root_name + ".", p_init); }
else
{ l_root = l_wk_modules[p_root_name];
if (l_root)
{ l_root_pkg_desc = l_root["*p"];
if ( l_root_pkg_desc
&& !(l_root_pkg_desc.state)
)
{ l_async
.push(function(p_callback)
{ l_root_pkg_desc.state = PACKAGE_LOADING;
l_root_pkg_url = l_root_pkg_desc.url;
if (l_root_pkg_url !== undefined)
{ l_xhr = new XMLHttpRequest();
if (l_root_pkg_url)
{ l_root_pkg_url += "/"; }
l_xhr.open("GET",
l_root_pkg_url
+ l_root_pkg_desc.packagePath(p_root_name)
+ PACKAGE_INIT_FILE,
false // TBD????: true
);
l_xhr.send();
if (l_xhr.status === 200)
{ f_root_pkg_init(p_root_name,
JSON.parse(l_xhr.responseText)
);
l_root_pkg_desc.state = PACKAGE_INITIALIZED;
}
else
{ l_root_pkg_desc.state = PACKAGE_NO_INIT_FILE; }
}
p_callback();
}
);
}
}
}
}
//////////////////////////////////////////////////////////////////////////////
// Definition of $wk$
//////////////////////////////////////////////////////////////////////////////
$wk$ =
function WK()
{ // Ensure that the function is called as a constructor.
if (!(this instanceof WK))
{ return f_new(WK, arguments); }
var self = this,
p_modules = [],
p_environments = [self],
l_environment = self,
l_environments = [self],
p_sandbox = null,
p_annotations = {},
l_arg,
l_arg_state = 0,
i, j, n, l_key = null,
l_length,
l_package_name = null,
l_module_name = null,
l_module_fullname = null,
l_module_name_split = null,
l_modules = [],
l_module,
l_modules_waiting_for = 1,
l_next_env = true;
function f_annotation(p_name, p_value)
{ var l_anno_fkt = l_wk_annotations[p_name];
if (!l_anno_fkt)
{ throw new Error("$wk$: The annotation " + p_name + " is undefined"); }
l_anno_fkt.call(self, p_value);
}
for (i = 0, n = arguments.length; i < n; i++)
{ l_arg = arguments[i];
if (l_arg_state === 0) // Modules (optional)
{ if (l_arg instanceof Array)
{ l_arg_state = 1;
p_modules = l_arg;
continue;
}
else if (l_arg.constructor === String)
{ if (l_arg.charAt(0) === "@")
{ l_arg_state = 2; }
else
{ p_modules.push(l_arg); }
continue;
}
else
{ l_arg_state = 1; }
}
if (l_arg_state === 1) // Sandbox (optional)
{ if (l_arg instanceof Function)
{ p_sandbox = l_arg;
l_arg_state = 2;
}
else
{ if (l_arg instanceof Array|| l_arg.constructor === String)
{ l_arg_state = 2;}
else
{ p_environments.push(l_arg); continue; }
}
}
if (l_arg_state === 2)
{ if (l_arg instanceof Array)
{ p_annotations[l_arg[0]] = l_arg[1]; }
else if (l_arg.constructor === String)
{ p_annotations[l_arg] = true; }
else if (l_arg instanceof Object)
{ for (l_key in l_arg)
{ if (l_arg.hasOwnProperty(l_key))
{ p_annotations[l_key] = l_arg[l_key]; }
}
}
}
}
/*jslint forin: true*/
for (l_key in p_annotations)
{ f_annotation(l_key, p_annotations[l_key]); }
if (p_environments.length > 1)
{ l_environments = p_environments.slice(0);
l_environments.shift();
}
if (p_sandbox === undefined)
{ throw new Error("new $wk$: Sandbox is missing!"); }
this.$wk$ =
function()
{ var l_proto = WK.prototype,
l_result;
WK.prototype = self;
l_result = f_new(WK, arguments);
WK.prototype = l_proto;
return l_result;
};
function f_module_find(p_module_name)
{ var i, n,
l_module_name_split = p_module_name.split("."),
l_module = l_wk_modules,
l_name = "";
for (i = 0, n = l_module_name_split.length; i < n; i++)
{ l_name = l_module_name_split[i];
if (!l_module[l_name])
{ if (i===0 && f_pkg(p_module_name, false))
{ l_module = { module: f_pkg(p_module_name),
state: MODULE_LOADED
};
break;
}
else
{ throw new Error("moduleLoad: Module "+l_name+" does not exist."); }
}
else
{ l_module = l_module[l_name]; }
}
return l_module;
}
function f_module_fetch(p_module, p_callback)
{ function f_callback()
{ var l_callbacks;
if (p_module.type !== JS)
{ p_module.state = MODULE_LOADED;
l_callbacks = p_module.$cb$;
if (l_callbacks)
{ while (l_callbacks.length > 0)
{ (l_callbacks.shift())(); }
}
}
p_callback();
}
if (p_module.state === MODULE_LOADED)
{ if (p_module.init)
{ p_module.init(p_callback);
delete p_module.init;
}
else
{ p_callback(); }
}
else if (p_module.state === MODULE_LOADING)
// As the module is alereday being load, nothing has to be done.
{ p_callback(); }
else
// Load and/or initialize the module.
{ if (!p_module.url && !p_module.init)
{ throw new Error( "URL or 'init' is missing for module '"
+ p_module.name
+ "'"
);
}
p_module.state = MODULE_LOADING;
if (p_module.url)
{ if (p_module.init)
{ p_module.loader(self,
function(){p_module.init(f_callback);
delete p_module.init;
}
);
}
else
{ p_module.loader(self, f_callback); }
}
else
{ p_module.init(f_callback);
delete p_module.init;
}
}
}
function f_star_begin()
{ if (l_environments.length > 0)
{ l_environment = l_environments.shift(); }
l_next_env = false;
}
function f_star_end()
{ l_next_env = true; }
function f_sandbox()
{ var l_module;
//self.$trace$ = (self.$trace$ || "") + l_modules_waiting_for + ", ";
if (l_modules_waiting_for === 0)
{ while (l_modules.length > 0)
{ l_module = l_modules.shift();
if (l_module instanceof Function) // ===f_star_begin || ===f_star_end
{ l_module(); }
else
{ if (l_next_env && l_environments.length > 0)
{ l_environment = l_environments.shift(); }
f_mixin(l_module.module, l_environment);
}
}
if (p_sandbox !== null)
{ p_sandbox.apply(self, p_environments); }
}
}
function f_observer_add(p_module)
{ function f_callback()
{ l_modules_waiting_for--; f_sandbox(); }
l_modules.push(p_module);
if (p_module.state !== MODULE_LOADED)
{ l_modules_waiting_for++;
if (!p_module.$cb$)
{ p_module.$cb$ = []; }
p_module.$cb$.push(f_callback);
}
else
{ f_sandbox(); }
}
function f_module_add_deep(p_package, p_deep)
{ var l_module_or_package, l_key = null;
/*jslint forin: true*/
for (l_key in p_package)
{ l_module_or_package = p_package[l_key];
if (l_module_or_package.url || l_module_or_package.module)
{ f_observer_add(l_module_or_package);
l_async.push(f_module_fetch, l_module_or_package);
}
else if (p_deep && l_module_or_package["*p"])
{ f_module_add_deep(l_module_or_package, p_deep); }
}
}
for (i = 0, n = p_modules.length; i < n; i++)
{ l_module_fullname = p_modules[i];
l_module_name_split = l_module_fullname.split(".");
l_length = l_module_name_split.length-1;
l_module_name = l_module_name_split[l_length];
f_root_pkg_init(l_module_name_split[0]);
if (l_module_name === "*" || l_module_name === "**")
{ l_module = l_wk_modules;
for (j = 0; j < l_length; j++)
{ l_package_name = l_module_name_split[j];
l_module = l_module[l_package_name];
if (!l_module)
{ throw new Error("Package " + l_package_name + " does not exist."); }
}
l_modules.push(f_star_begin);
f_module_add_deep(l_module, l_module_name === "**");
l_modules.push(f_star_end);
}
else
{ l_module = f_module_find(l_module_fullname);
f_observer_add(l_module);
l_async.push(f_module_fetch, l_module);
}
}
l_modules_waiting_for--;
f_sandbox();
//console.log(l_wk_modules);
return this;
};
//////////////////////////////////////////////////////////////////////////////
// Definition of $wk$ properties.
//////////////////////////////////////////////////////////////////////////////
Object.defineProperties
($wk$,
{"$name$":
{ enumerable: true,
configurable: false,
get: function(){ return l_library_name; },
set: f_error_setter
},
"$version$":
{ enumerable: true,
configurable: false,
get: function(){ return l_version; },
set: f_error_setter
},
"$__wk_document_complete__$":
{ enumerable: false,
configurable: false,
writable: false,
value: function(p_callback)
{ function f_aux()
{ f_module_add({ name: "WaitUntilDocumentComplete",
module: {}
}
);
p_callback();
}
if ( document.readyState === "complete" )
{ f_aux(); }
else
{ p_root_wk.addEventListener("load", f_aux); }
}
},
"$__wk_document_ready__$":
{ enumerable: false,
configurable: false,
writable: false,
value: function(p_callback)
{ function f_aux()
{ f_module_add({ name: "WaitUntilDocumentReady",
module: {}
}
);
p_callback();
}
if ( document.readyState === "interactive"
|| document.readyState === "complete"
)
{ f_aux(); }
else
{ document.addEventListener("DOMContentLoaded", f_aux); }
}
}
});
//////////////////////////////////////////////////////////////////////////////
// Pseudo-modules
//////////////////////////////////////////////////////////////////////////////
$wk$.$root$ = p_root_wk;
$wk$.$C$ = $C$;
$wk$.$E$ = $E$;
$wk$.$new$ = f_new;
$wk$.getOwnPropertyDescriptor = f_get_property_descriptor;
$wk$.getPropertyDescriptor = f_get_property_descriptor;
$wk$.setProperty = f_set_property;
$wk$.mixin = f_mixin;
$wk$.moduleAdd = f_module_add;
$wk$.rootPackage = f_root_pkg;
$wk$.rootPackageInit = f_root_pkg_init;
$wk$.pkg = f_pkg;
$wk$.JS = JS;
$wk$.JSON = JSON$;
$wk$.XML = XML;
$wk$.CSS = CSS;
f_mixin($wk$, $wk$.prototype);
$wk$.WKcAsync = WKcAsync;
}(this));
////////////////////////////////////////////////////////////////////////////////
// The list of ALL $wk$ modules that may be loaded
////////////////////////////////////////////////////////////////////////////////
$wk$.rootPackage({ "$wk$": { root: this, url: "" } });
$wk$.moduleAdd
({ name: "WaitUntilDocumentReady", init: $wk$.$__wk_document_ready__$ },
{ name: "WaitUntilDocumentComplete", init: $wk$.$__wk_document_complete__$ },
{ name: "$wk$.WKcAsync", module: { WKcAsync: $wk$.WKcAsync } }
);
////////////////////////////////////////////////////////////////////////////////
// End of the $wk$ object
////////////////////////////////////////////////////////////////////////////////